Feature: Получение данных для тренировки, фоторедактор, изменение хранения изображений на локальный формат файлов а не байтов в бд #8

Merged
Dimkov966 merged 7 commits from feature/training into develop 2025-09-08 19:46:19 +00:00
14 changed files with 1591 additions and 58 deletions
Showing only changes of commit 845a380fbf - Show all commits

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -28,6 +28,18 @@ class CollectionsDao extends DatabaseAccessor<AppDatabase>
} }
} }
Future<Collection?> getCollectionById(String? id) {
try {
return db.managers.collections
.filter((f) => f.id.equals(id))
.getSingleOrNull();
} catch (e, st) {
logger.logError('Ошибка в методе getCollectionById', e, st);
throw ('EXEPTION');
}
}
/// Создание коллекции /// Создание коллекции
Future<void> createCollection(CrudCollectionDto dto) async { Future<void> createCollection(CrudCollectionDto dto) async {
try { try {

View File

@@ -6,7 +6,7 @@
/// Locales: 2 /// Locales: 2
/// Strings: 20 (10 per locale) /// Strings: 20 (10 per locale)
/// ///
/// Built on 2025-09-08 at 14:50 UTC /// Built on 2025-09-08 at 19:38 UTC
// coverage:ignore-file // coverage:ignore-file
// ignore_for_file: type=lint, unused_import // ignore_for_file: type=lint, unused_import

View File

@@ -23,6 +23,8 @@ import 'services/core/theme_service.dart' as _i84;
import 'services/logs/logs_service.dart' as _i393; import 'services/logs/logs_service.dart' as _i393;
import 'services/tickets/tickets_interface.dart' as _i147; import 'services/tickets/tickets_interface.dart' as _i147;
import 'services/tickets/tickets_service.dart' as _i548; import 'services/tickets/tickets_service.dart' as _i548;
import 'services/training/training_interface.dart' as _i813;
import 'services/training/training_service.dart' as _i162;
import 'services/warmup_service.dart' as _i564; import 'services/warmup_service.dart' as _i564;
extension GetItInjectableX on _i174.GetIt { extension GetItInjectableX on _i174.GetIt {
@@ -37,6 +39,7 @@ extension GetItInjectableX on _i174.GetIt {
gh.factory<_i84.ThemeService>(() => _i84.ThemeService()); gh.factory<_i84.ThemeService>(() => _i84.ThemeService());
gh.singleton<_i565.AppDatabase>(() => _i565.AppDatabase()); gh.singleton<_i565.AppDatabase>(() => _i565.AppDatabase());
gh.singleton<_i393.LogsService>(() => _i393.LogsService()); gh.singleton<_i393.LogsService>(() => _i393.LogsService());
gh.singleton<_i813.TrainingInterface>(() => _i162.TrainingService());
gh.singleton<_i147.TicketsInterface>(() => _i548.TicketsService()); gh.singleton<_i147.TicketsInterface>(() => _i548.TicketsService());
gh.singleton<_i764.CollectionsInterface>(() => _i1001.CollectionsService()); gh.singleton<_i764.CollectionsInterface>(() => _i1001.CollectionsService());
gh.singleton<_i580.AuthInterface>(() => _i975.AuthService()); gh.singleton<_i580.AuthInterface>(() => _i975.AuthService());

View File

@@ -0,0 +1,14 @@
// To parse this JSON data, do
//
// final collectionDto = collectionDtoFromJson(jsonString);
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:remever/database/database.dart';
part 'training_dto.freezed.dart';
@Freezed(copyWith: true, equal: true, fromJson: false, toJson: false)
abstract class TrainingDto with _$TrainingDto {
const factory TrainingDto({Collection? collection, required Ticket ticket}) =
_TrainingDto;
}

View File

@@ -0,0 +1,176 @@
// 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 'training_dto.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 _$TrainingDto {
Collection? get collection => throw _privateConstructorUsedError;
Ticket get ticket => throw _privateConstructorUsedError;
/// Create a copy of TrainingDto
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$TrainingDtoCopyWith<TrainingDto> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $TrainingDtoCopyWith<$Res> {
factory $TrainingDtoCopyWith(
TrainingDto value,
$Res Function(TrainingDto) then,
) = _$TrainingDtoCopyWithImpl<$Res, TrainingDto>;
@useResult
$Res call({Collection? collection, Ticket ticket});
}
/// @nodoc
class _$TrainingDtoCopyWithImpl<$Res, $Val extends TrainingDto>
implements $TrainingDtoCopyWith<$Res> {
_$TrainingDtoCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of TrainingDto
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({Object? collection = freezed, Object? ticket = freezed}) {
return _then(
_value.copyWith(
collection:
freezed == collection
? _value.collection
: collection // ignore: cast_nullable_to_non_nullable
as Collection?,
ticket:
freezed == ticket
? _value.ticket
: ticket // ignore: cast_nullable_to_non_nullable
as Ticket,
)
as $Val,
);
}
}
/// @nodoc
abstract class _$$TrainingDtoImplCopyWith<$Res>
implements $TrainingDtoCopyWith<$Res> {
factory _$$TrainingDtoImplCopyWith(
_$TrainingDtoImpl value,
$Res Function(_$TrainingDtoImpl) then,
) = __$$TrainingDtoImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({Collection? collection, Ticket ticket});
}
/// @nodoc
class __$$TrainingDtoImplCopyWithImpl<$Res>
extends _$TrainingDtoCopyWithImpl<$Res, _$TrainingDtoImpl>
implements _$$TrainingDtoImplCopyWith<$Res> {
__$$TrainingDtoImplCopyWithImpl(
_$TrainingDtoImpl _value,
$Res Function(_$TrainingDtoImpl) _then,
) : super(_value, _then);
/// Create a copy of TrainingDto
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({Object? collection = freezed, Object? ticket = freezed}) {
return _then(
_$TrainingDtoImpl(
collection:
freezed == collection
? _value.collection
: collection // ignore: cast_nullable_to_non_nullable
as Collection?,
ticket:
freezed == ticket
? _value.ticket
: ticket // ignore: cast_nullable_to_non_nullable
as Ticket,
),
);
}
}
/// @nodoc
class _$TrainingDtoImpl implements _TrainingDto {
const _$TrainingDtoImpl({this.collection, required this.ticket});
@override
final Collection? collection;
@override
final Ticket ticket;
@override
String toString() {
return 'TrainingDto(collection: $collection, ticket: $ticket)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$TrainingDtoImpl &&
const DeepCollectionEquality().equals(
other.collection,
collection,
) &&
const DeepCollectionEquality().equals(other.ticket, ticket));
}
@override
int get hashCode => Object.hash(
runtimeType,
const DeepCollectionEquality().hash(collection),
const DeepCollectionEquality().hash(ticket),
);
/// Create a copy of TrainingDto
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$TrainingDtoImplCopyWith<_$TrainingDtoImpl> get copyWith =>
__$$TrainingDtoImplCopyWithImpl<_$TrainingDtoImpl>(this, _$identity);
}
abstract class _TrainingDto implements TrainingDto {
const factory _TrainingDto({
final Collection? collection,
required final Ticket ticket,
}) = _$TrainingDtoImpl;
@override
Collection? get collection;
@override
Ticket get ticket;
/// Create a copy of TrainingDto
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$TrainingDtoImplCopyWith<_$TrainingDtoImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@@ -1,11 +1,17 @@
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:remever/database/database.dart';
import 'package:remever/inject.dart';
import 'package:remever/models/training_dto.dart';
import 'package:remever/services/training/training_interface.dart';
part 'training_state.dart'; part 'training_state.dart';
part 'training_cubit.freezed.dart'; part 'training_cubit.freezed.dart';
class TrainingCubit extends Cubit<TrainingState> { class TrainingCubit extends Cubit<TrainingState> {
TrainingCubit() : super(TrainingState.data()); TrainingCubit() : super(TrainingState.loading()) {
init();
}
Future<void> toLoading() async { Future<void> toLoading() async {
emit(TrainingState.loading()); emit(TrainingState.loading());
@@ -16,10 +22,34 @@ class TrainingCubit extends Cubit<TrainingState> {
} }
Future<void> toDataState() async { Future<void> toDataState() async {
emit(TrainingState.data()); emit(TrainingState.data([]));
} }
Future<void> toResultState() async { Future<void> toResultState() async {
emit(TrainingState.result()); emit(TrainingState.result());
} }
Future<void> init() async {
final List<Ticket> data = await getIt<TrainingInterface>().getTraining();
if (data.isEmpty) {
emit(TrainingState.empty());
return;
}
List<TrainingDto> dto = [];
for (final ticket in data) {
dto.add(
TrainingDto(
ticket: ticket,
collection: await getIt<AppDatabase>().collectionsDao
.getCollectionById(ticket.collectionId),
),
);
}
emit(TrainingState.data(dto));
}
} }

View File

@@ -21,21 +21,21 @@ mixin _$TrainingState {
TResult when<TResult extends Object?>({ TResult when<TResult extends Object?>({
required TResult Function() loading, required TResult Function() loading,
required TResult Function() empty, required TResult Function() empty,
required TResult Function() data, required TResult Function(List<TrainingDto> data) data,
required TResult Function() result, required TResult Function() result,
}) => throw _privateConstructorUsedError; }) => throw _privateConstructorUsedError;
@optionalTypeArgs @optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({ TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? loading, TResult? Function()? loading,
TResult? Function()? empty, TResult? Function()? empty,
TResult? Function()? data, TResult? Function(List<TrainingDto> data)? data,
TResult? Function()? result, TResult? Function()? result,
}) => throw _privateConstructorUsedError; }) => throw _privateConstructorUsedError;
@optionalTypeArgs @optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({ TResult maybeWhen<TResult extends Object?>({
TResult Function()? loading, TResult Function()? loading,
TResult Function()? empty, TResult Function()? empty,
TResult Function()? data, TResult Function(List<TrainingDto> data)? data,
TResult Function()? result, TResult Function()? result,
required TResult orElse(), required TResult orElse(),
}) => throw _privateConstructorUsedError; }) => throw _privateConstructorUsedError;
@@ -130,7 +130,7 @@ class _$LoadingImpl implements _Loading {
TResult when<TResult extends Object?>({ TResult when<TResult extends Object?>({
required TResult Function() loading, required TResult Function() loading,
required TResult Function() empty, required TResult Function() empty,
required TResult Function() data, required TResult Function(List<TrainingDto> data) data,
required TResult Function() result, required TResult Function() result,
}) { }) {
return loading(); return loading();
@@ -141,7 +141,7 @@ class _$LoadingImpl implements _Loading {
TResult? whenOrNull<TResult extends Object?>({ TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? loading, TResult? Function()? loading,
TResult? Function()? empty, TResult? Function()? empty,
TResult? Function()? data, TResult? Function(List<TrainingDto> data)? data,
TResult? Function()? result, TResult? Function()? result,
}) { }) {
return loading?.call(); return loading?.call();
@@ -152,7 +152,7 @@ class _$LoadingImpl implements _Loading {
TResult maybeWhen<TResult extends Object?>({ TResult maybeWhen<TResult extends Object?>({
TResult Function()? loading, TResult Function()? loading,
TResult Function()? empty, TResult Function()? empty,
TResult Function()? data, TResult Function(List<TrainingDto> data)? data,
TResult Function()? result, TResult Function()? result,
required TResult orElse(), required TResult orElse(),
}) { }) {
@@ -249,7 +249,7 @@ class _$EmptyImpl implements _Empty {
TResult when<TResult extends Object?>({ TResult when<TResult extends Object?>({
required TResult Function() loading, required TResult Function() loading,
required TResult Function() empty, required TResult Function() empty,
required TResult Function() data, required TResult Function(List<TrainingDto> data) data,
required TResult Function() result, required TResult Function() result,
}) { }) {
return empty(); return empty();
@@ -260,7 +260,7 @@ class _$EmptyImpl implements _Empty {
TResult? whenOrNull<TResult extends Object?>({ TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? loading, TResult? Function()? loading,
TResult? Function()? empty, TResult? Function()? empty,
TResult? Function()? data, TResult? Function(List<TrainingDto> data)? data,
TResult? Function()? result, TResult? Function()? result,
}) { }) {
return empty?.call(); return empty?.call();
@@ -271,7 +271,7 @@ class _$EmptyImpl implements _Empty {
TResult maybeWhen<TResult extends Object?>({ TResult maybeWhen<TResult extends Object?>({
TResult Function()? loading, TResult Function()? loading,
TResult Function()? empty, TResult Function()? empty,
TResult Function()? data, TResult Function(List<TrainingDto> data)? data,
TResult Function()? result, TResult Function()? result,
required TResult orElse(), required TResult orElse(),
}) { }) {
@@ -329,6 +329,8 @@ abstract class _$$DataImplCopyWith<$Res> {
_$DataImpl value, _$DataImpl value,
$Res Function(_$DataImpl) then, $Res Function(_$DataImpl) then,
) = __$$DataImplCopyWithImpl<$Res>; ) = __$$DataImplCopyWithImpl<$Res>;
@useResult
$Res call({List<TrainingDto> data});
} }
/// @nodoc /// @nodoc
@@ -340,36 +342,67 @@ class __$$DataImplCopyWithImpl<$Res>
/// Create a copy of TrainingState /// Create a copy of TrainingState
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({Object? data = null}) {
return _then(
_$DataImpl(
null == data
? _value._data
: data // ignore: cast_nullable_to_non_nullable
as List<TrainingDto>,
),
);
}
} }
/// @nodoc /// @nodoc
class _$DataImpl implements _Data { class _$DataImpl implements _Data {
const _$DataImpl(); const _$DataImpl(final List<TrainingDto> data) : _data = data;
final List<TrainingDto> _data;
@override
List<TrainingDto> get data {
if (_data is EqualUnmodifiableListView) return _data;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_data);
}
@override @override
String toString() { String toString() {
return 'TrainingState.data()'; return 'TrainingState.data(data: $data)';
} }
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$DataImpl); (other.runtimeType == runtimeType &&
other is _$DataImpl &&
const DeepCollectionEquality().equals(other._data, _data));
} }
@override @override
int get hashCode => runtimeType.hashCode; int get hashCode =>
Object.hash(runtimeType, const DeepCollectionEquality().hash(_data));
/// Create a copy of TrainingState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$DataImplCopyWith<_$DataImpl> get copyWith =>
__$$DataImplCopyWithImpl<_$DataImpl>(this, _$identity);
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult when<TResult extends Object?>({ TResult when<TResult extends Object?>({
required TResult Function() loading, required TResult Function() loading,
required TResult Function() empty, required TResult Function() empty,
required TResult Function() data, required TResult Function(List<TrainingDto> data) data,
required TResult Function() result, required TResult Function() result,
}) { }) {
return data(); return data(this.data);
} }
@override @override
@@ -377,10 +410,10 @@ class _$DataImpl implements _Data {
TResult? whenOrNull<TResult extends Object?>({ TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? loading, TResult? Function()? loading,
TResult? Function()? empty, TResult? Function()? empty,
TResult? Function()? data, TResult? Function(List<TrainingDto> data)? data,
TResult? Function()? result, TResult? Function()? result,
}) { }) {
return data?.call(); return data?.call(this.data);
} }
@override @override
@@ -388,12 +421,12 @@ class _$DataImpl implements _Data {
TResult maybeWhen<TResult extends Object?>({ TResult maybeWhen<TResult extends Object?>({
TResult Function()? loading, TResult Function()? loading,
TResult Function()? empty, TResult Function()? empty,
TResult Function()? data, TResult Function(List<TrainingDto> data)? data,
TResult Function()? result, TResult Function()? result,
required TResult orElse(), required TResult orElse(),
}) { }) {
if (data != null) { if (data != null) {
return data(); return data(this.data);
} }
return orElse(); return orElse();
} }
@@ -437,7 +470,15 @@ class _$DataImpl implements _Data {
} }
abstract class _Data implements TrainingState { abstract class _Data implements TrainingState {
const factory _Data() = _$DataImpl; const factory _Data(final List<TrainingDto> data) = _$DataImpl;
List<TrainingDto> get data;
/// Create a copy of TrainingState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$DataImplCopyWith<_$DataImpl> get copyWith =>
throw _privateConstructorUsedError;
} }
/// @nodoc /// @nodoc
@@ -485,7 +526,7 @@ class _$ResultImpl implements _Result {
TResult when<TResult extends Object?>({ TResult when<TResult extends Object?>({
required TResult Function() loading, required TResult Function() loading,
required TResult Function() empty, required TResult Function() empty,
required TResult Function() data, required TResult Function(List<TrainingDto> data) data,
required TResult Function() result, required TResult Function() result,
}) { }) {
return result(); return result();
@@ -496,7 +537,7 @@ class _$ResultImpl implements _Result {
TResult? whenOrNull<TResult extends Object?>({ TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? loading, TResult? Function()? loading,
TResult? Function()? empty, TResult? Function()? empty,
TResult? Function()? data, TResult? Function(List<TrainingDto> data)? data,
TResult? Function()? result, TResult? Function()? result,
}) { }) {
return result?.call(); return result?.call();
@@ -507,7 +548,7 @@ class _$ResultImpl implements _Result {
TResult maybeWhen<TResult extends Object?>({ TResult maybeWhen<TResult extends Object?>({
TResult Function()? loading, TResult Function()? loading,
TResult Function()? empty, TResult Function()? empty,
TResult Function()? data, TResult Function(List<TrainingDto> data)? data,
TResult Function()? result, TResult Function()? result,
required TResult orElse(), required TResult orElse(),
}) { }) {

View File

@@ -4,6 +4,6 @@ part of 'training_cubit.dart';
class TrainingState with _$TrainingState { class TrainingState with _$TrainingState {
const factory TrainingState.loading() = _Loading; const factory TrainingState.loading() = _Loading;
const factory TrainingState.empty() = _Empty; const factory TrainingState.empty() = _Empty;
const factory TrainingState.data() = _Data; const factory TrainingState.data(List<TrainingDto> data) = _Data;
const factory TrainingState.result() = _Result; const factory TrainingState.result() = _Result;
} }

View File

@@ -1,16 +1,13 @@
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:remever/common/resources.dart'; import 'package:remever/common/resources.dart';
import 'package:remever/common/typography.dart';
import 'package:remever/common/widgets/typography.dart'; import 'package:remever/common/widgets/typography.dart';
import 'package:remever/common/widgets/w_if.dart'; import 'package:remever/common/widgets/w_if.dart';
import 'package:remever/common/widgets/wspace.dart'; import 'package:remever/common/widgets/wspace.dart';
import 'package:remever/components/extensions/context.dart'; import 'package:remever/components/extensions/context.dart';
import 'package:remever/components/extensions/state.dart'; import 'package:remever/components/extensions/state.dart';
import 'package:remever/gen/assets.gen.dart'; import 'package:remever/models/training_dto.dart';
import 'package:remever/router.gr.dart';
import 'package:remever/screens/training/cubit/training_cubit.dart'; import 'package:remever/screens/training/cubit/training_cubit.dart';
import 'package:remever/screens/training/states/empty.dart'; import 'package:remever/screens/training/states/empty.dart';
import 'package:remever/screens/training/states/loading.dart'; import 'package:remever/screens/training/states/loading.dart';
@@ -40,7 +37,7 @@ class TrainingScreen extends StatelessWidget {
return state.when( return state.when(
loading: () => TrainingLoading(), loading: () => TrainingLoading(),
empty: () => TrainingEmpty(), empty: () => TrainingEmpty(),
data: () => TrainingData(), data: (data) => TrainingData(data: data),
result: () => Placeholder(), result: () => Placeholder(),
); );
}, },
@@ -50,8 +47,9 @@ class TrainingScreen extends StatelessWidget {
} }
class TrainingData extends StatefulWidget { class TrainingData extends StatefulWidget {
const TrainingData({super.key}); const TrainingData({super.key, required this.data});
final List<TrainingDto> data;
@override @override
State<TrainingData> createState() => _TrainingDataState(); State<TrainingData> createState() => _TrainingDataState();
} }
@@ -59,6 +57,8 @@ class TrainingData extends StatefulWidget {
class _TrainingDataState extends State<TrainingData> { class _TrainingDataState extends State<TrainingData> {
bool _showAnswer = false; bool _showAnswer = false;
int _currentTicketIndex = 0;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@@ -97,7 +97,12 @@ class _TrainingDataState extends State<TrainingData> {
), ),
child: Padding( child: Padding(
padding: const EdgeInsets.all(3).r, padding: const EdgeInsets.all(3).r,
child: Center(child: AppTypography('1 из 9', type: Regular14px())), child: Center(
child: AppTypography(
'${_currentTicketIndex + 1} из ${widget.data.length}',
type: Regular14px(),
),
),
), ),
), ),
); );
@@ -117,7 +122,13 @@ class _TrainingDataState extends State<TrainingData> {
children: [ children: [
Flexible( Flexible(
child: PrimaryButton( child: PrimaryButton(
onTap: () {}, onTap: () {
if (widget.data.length == _currentTicketIndex + 1) {
context.read<TrainingCubit>().toResultState();
return;
}
safeSetState(() => _currentTicketIndex++);
},
color: AppColors.danger, color: AppColors.danger,
child: AppTypography( child: AppTypography(
'Не помню', 'Не помню',
@@ -133,7 +144,13 @@ class _TrainingDataState extends State<TrainingData> {
type: Medium14px(), type: Medium14px(),
color: Colors.white, color: Colors.white,
), ),
onTap: () {}, onTap: () {
if (widget.data.length == _currentTicketIndex + 1) {
context.read<TrainingCubit>().toResultState();
return;
}
safeSetState(() => _currentTicketIndex++);
},
), ),
), ),
], ],
@@ -171,10 +188,14 @@ class _TrainingDataState extends State<TrainingData> {
child: Column( child: Column(
spacing: 8.r, spacing: 8.r,
children: [ children: [
TrainingTicket(), TrainingTicket(trainingDto: widget.data[_currentTicketIndex]),
Wif( Wif(
condition: _showAnswer, condition: _showAnswer,
builder: (context) => TrainingTicket(isAnswer: true), builder:
(context) => TrainingTicket(
trainingDto: widget.data[_currentTicketIndex],
isAnswer: true,
),
), ),
], ],
), ),

View File

@@ -1,13 +1,22 @@
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:remever/common/resources.dart'; import 'package:remever/common/resources.dart';
import 'package:remever/common/widgets/typography.dart'; import 'package:remever/common/widgets/typography.dart';
import 'package:remever/common/widgets/w_if.dart'; import 'package:remever/common/widgets/w_if.dart';
import 'package:remever/common/widgets/wspace.dart'; import 'package:remever/common/widgets/wspace.dart';
import 'package:remever/components/extensions/context.dart'; import 'package:remever/components/extensions/context.dart';
import 'package:remever/database/database.dart';
import 'package:remever/models/training_dto.dart';
class TrainingTicket extends StatelessWidget { class TrainingTicket extends StatelessWidget {
const TrainingTicket({super.key, this.isAnswer = false}); const TrainingTicket({
super.key,
this.isAnswer = false,
required this.trainingDto,
});
final TrainingDto trainingDto;
final bool isAnswer; final bool isAnswer;
@override @override
@@ -43,8 +52,10 @@ class TrainingTicket extends StatelessWidget {
} }
Widget _buildCollectionInfo() { Widget _buildCollectionInfo() {
final collection = trainingDto.collection;
return Wif( return Wif(
condition: !isAnswer, condition: !isAnswer && collection != null,
builder: (context) { builder: (context) {
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: 8).r, padding: const EdgeInsets.only(bottom: 8).r,
@@ -53,16 +64,28 @@ class TrainingTicket extends StatelessWidget {
SizedBox( SizedBox(
height: 24.h, height: 24.h,
width: 24.w, width: 24.w,
child: ClipOval( child: Wif(
child: Image.network( condition: collection!.image != null,
'https://avatars.mds.yandex.net/i?id=56429b65e9098a58fcd538387d43bcbb_l-5384017-images-thumbs&n=13', builder:
fit: BoxFit.cover, (context) => ClipOval(
), child: Image.file(
File(collection.image!),
fit: BoxFit.cover,
),
),
fallback:
(context) => Center(
child: AppTypography(
collection.title.substring(0, 1),
type: Bold34px(),
),
),
), ),
), ),
WSpace(4), WSpace(4),
AppTypography( AppTypography(
'Астрология и астрофизика', collection.title,
type: Regular14px(), type: Regular14px(),
color: AppColors.disabled, color: AppColors.disabled,
), ),
@@ -70,6 +93,7 @@ class TrainingTicket extends StatelessWidget {
), ),
); );
}, },
fallback: (context) => Row(children: []),
); );
} }
@@ -90,20 +114,21 @@ class TrainingTicket extends StatelessWidget {
} }
Widget _buildText(BuildContext context) { Widget _buildText(BuildContext context) {
final ticket = trainingDto.ticket;
return AppTypography( return AppTypography(
'Родился 19 февраля 1473 года в Торуне в семье купца. После смерти отца воспитывался у дяди, епископа Вармийской епархии. Коперник изложил свои идеи в сочинении «Commentariolus» («Малый комментарий»), в котором сформулировал основные положения гелиоцентрической системы мира в виде 6 аксиом. Их смысл состоит в том, что Земля, как и другие планеты,' isAnswer ? ticket.answer : ticket.question,
'Родился 19 февраля 1473 года в Торуне в семье купца. После смерти отца воспитывался у дяди, епископа Вармийской епархии. Коперник изложил свои идеи в сочинении «Commentariolus» («Малый комментарий»), в котором сформулировал основные положения гелиоцентрической системы мира в виде 6 аксиом. Их смысл состоит в том, что Земля, как и другие планеты,',
maxLines: 99, maxLines: 99,
type: Regular14px(), type: Regular14px(),
); );
} }
Widget _buildImage() { Widget _buildImage() {
// final imageBytes = final ticket = trainingDto.ticket;
// isAnswer ? ticket.answerImage : ticket.questionImage; final String? imagePath =
isAnswer ? ticket.answerImage : ticket.questionImage;
return Wif( return Wif(
condition: true, // imageBytes != null, condition: imagePath != null,
builder: builder:
(context) => Padding( (context) => Padding(
padding: const EdgeInsets.only(right: 8).r, padding: const EdgeInsets.only(right: 8).r,
@@ -111,11 +136,7 @@ class TrainingTicket extends StatelessWidget {
dimension: 100.r, dimension: 100.r,
child: ClipRRect( child: ClipRRect(
borderRadius: BorderRadius.circular(8).r, borderRadius: BorderRadius.circular(8).r,
// child: Image.memory(imageBytes!, fit: BoxFit.cover), child: Image.file(File(imagePath!), fit: BoxFit.cover),
child: Image.network(
'https://avatars.mds.yandex.net/i?id=56429b65e9098a58fcd538387d43bcbb_l-5384017-images-thumbs&n=13',
fit: BoxFit.cover,
),
), ),
), ),
), ),

View File

@@ -0,0 +1,10 @@
import 'package:remever/database/database.dart';
import 'package:remever/models/crud_collection_dto.dart';
///
/// Интерфейс взаимодействия с тренировкой
///
abstract interface class TrainingInterface {
/// Получение списка тикетов для тренировки
Future<List<Ticket>> getTraining();
}

View File

@@ -0,0 +1,49 @@
import 'dart:math';
import 'package:injectable/injectable.dart';
import 'package:remever/database/database.dart';
import 'package:remever/inject.dart';
import 'package:remever/services/training/training_interface.dart';
@Singleton(as: TrainingInterface)
final class TrainingService implements TrainingInterface {
// @override
// Future<void> removeTicket(String ticketId) async {
// return await getIt<AppDatabase>().ticketsDao.removeTicket(ticketId);
// }
@override
Future<List<Ticket>> getTraining() async {
try {
final db = getIt<AppDatabase>();
final rows =
await db
.customSelect('SELECT * FROM tickets ORDER BY RANDOM() LIMIT 10')
.get();
return rows.map((row) {
final data = row.data;
print("ALARMA $data");
// Преобразуем snake_case ключи в camelCase для Moor сериализатора
final camelCaseData = {
'id': data['id'],
'createdAt': data['created_at'],
'updatedAt': data['updated_at'],
'question': data['question'],
'questionImage': data['question_image'],
'answer': data['answer'],
'answerImage': data['answer_image'],
'collectionId': data['collection_id'],
'progress': data['progress'],
};
return Ticket.fromJson(camelCaseData);
}).toList();
} catch (e) {
print('Error in training $e');
return [];
}
}
}