feature(training): отбражение данных для тренировки

This commit is contained in:
2025-09-08 22:44:03 +03:00
parent 90531e6e4e
commit 845a380fbf
14 changed files with 1591 additions and 58 deletions

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 [];
}
}
}