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 {
try {

View File

@@ -6,7 +6,7 @@
/// Locales: 2
/// 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
// 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/tickets/tickets_interface.dart' as _i147;
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;
extension GetItInjectableX on _i174.GetIt {
@@ -37,6 +39,7 @@ extension GetItInjectableX on _i174.GetIt {
gh.factory<_i84.ThemeService>(() => _i84.ThemeService());
gh.singleton<_i565.AppDatabase>(() => _i565.AppDatabase());
gh.singleton<_i393.LogsService>(() => _i393.LogsService());
gh.singleton<_i813.TrainingInterface>(() => _i162.TrainingService());
gh.singleton<_i147.TicketsInterface>(() => _i548.TicketsService());
gh.singleton<_i764.CollectionsInterface>(() => _i1001.CollectionsService());
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: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_cubit.freezed.dart';
class TrainingCubit extends Cubit<TrainingState> {
TrainingCubit() : super(TrainingState.data());
TrainingCubit() : super(TrainingState.loading()) {
init();
}
Future<void> toLoading() async {
emit(TrainingState.loading());
@@ -16,10 +22,34 @@ class TrainingCubit extends Cubit<TrainingState> {
}
Future<void> toDataState() async {
emit(TrainingState.data());
emit(TrainingState.data([]));
}
Future<void> toResultState() async {
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?>({
required TResult Function() loading,
required TResult Function() empty,
required TResult Function() data,
required TResult Function(List<TrainingDto> data) data,
required TResult Function() result,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? loading,
TResult? Function()? empty,
TResult? Function()? data,
TResult? Function(List<TrainingDto> data)? data,
TResult? Function()? result,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? loading,
TResult Function()? empty,
TResult Function()? data,
TResult Function(List<TrainingDto> data)? data,
TResult Function()? result,
required TResult orElse(),
}) => throw _privateConstructorUsedError;
@@ -130,7 +130,7 @@ class _$LoadingImpl implements _Loading {
TResult when<TResult extends Object?>({
required TResult Function() loading,
required TResult Function() empty,
required TResult Function() data,
required TResult Function(List<TrainingDto> data) data,
required TResult Function() result,
}) {
return loading();
@@ -141,7 +141,7 @@ class _$LoadingImpl implements _Loading {
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? loading,
TResult? Function()? empty,
TResult? Function()? data,
TResult? Function(List<TrainingDto> data)? data,
TResult? Function()? result,
}) {
return loading?.call();
@@ -152,7 +152,7 @@ class _$LoadingImpl implements _Loading {
TResult maybeWhen<TResult extends Object?>({
TResult Function()? loading,
TResult Function()? empty,
TResult Function()? data,
TResult Function(List<TrainingDto> data)? data,
TResult Function()? result,
required TResult orElse(),
}) {
@@ -249,7 +249,7 @@ class _$EmptyImpl implements _Empty {
TResult when<TResult extends Object?>({
required TResult Function() loading,
required TResult Function() empty,
required TResult Function() data,
required TResult Function(List<TrainingDto> data) data,
required TResult Function() result,
}) {
return empty();
@@ -260,7 +260,7 @@ class _$EmptyImpl implements _Empty {
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? loading,
TResult? Function()? empty,
TResult? Function()? data,
TResult? Function(List<TrainingDto> data)? data,
TResult? Function()? result,
}) {
return empty?.call();
@@ -271,7 +271,7 @@ class _$EmptyImpl implements _Empty {
TResult maybeWhen<TResult extends Object?>({
TResult Function()? loading,
TResult Function()? empty,
TResult Function()? data,
TResult Function(List<TrainingDto> data)? data,
TResult Function()? result,
required TResult orElse(),
}) {
@@ -329,6 +329,8 @@ abstract class _$$DataImplCopyWith<$Res> {
_$DataImpl value,
$Res Function(_$DataImpl) then,
) = __$$DataImplCopyWithImpl<$Res>;
@useResult
$Res call({List<TrainingDto> data});
}
/// @nodoc
@@ -340,36 +342,67 @@ class __$$DataImplCopyWithImpl<$Res>
/// Create a copy of TrainingState
/// 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
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
String toString() {
return 'TrainingState.data()';
return 'TrainingState.data(data: $data)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$DataImpl);
(other.runtimeType == runtimeType &&
other is _$DataImpl &&
const DeepCollectionEquality().equals(other._data, _data));
}
@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
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() loading,
required TResult Function() empty,
required TResult Function() data,
required TResult Function(List<TrainingDto> data) data,
required TResult Function() result,
}) {
return data();
return data(this.data);
}
@override
@@ -377,10 +410,10 @@ class _$DataImpl implements _Data {
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? loading,
TResult? Function()? empty,
TResult? Function()? data,
TResult? Function(List<TrainingDto> data)? data,
TResult? Function()? result,
}) {
return data?.call();
return data?.call(this.data);
}
@override
@@ -388,12 +421,12 @@ class _$DataImpl implements _Data {
TResult maybeWhen<TResult extends Object?>({
TResult Function()? loading,
TResult Function()? empty,
TResult Function()? data,
TResult Function(List<TrainingDto> data)? data,
TResult Function()? result,
required TResult orElse(),
}) {
if (data != null) {
return data();
return data(this.data);
}
return orElse();
}
@@ -437,7 +470,15 @@ class _$DataImpl implements _Data {
}
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
@@ -485,7 +526,7 @@ class _$ResultImpl implements _Result {
TResult when<TResult extends Object?>({
required TResult Function() loading,
required TResult Function() empty,
required TResult Function() data,
required TResult Function(List<TrainingDto> data) data,
required TResult Function() result,
}) {
return result();
@@ -496,7 +537,7 @@ class _$ResultImpl implements _Result {
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? loading,
TResult? Function()? empty,
TResult? Function()? data,
TResult? Function(List<TrainingDto> data)? data,
TResult? Function()? result,
}) {
return result?.call();
@@ -507,7 +548,7 @@ class _$ResultImpl implements _Result {
TResult maybeWhen<TResult extends Object?>({
TResult Function()? loading,
TResult Function()? empty,
TResult Function()? data,
TResult Function(List<TrainingDto> data)? data,
TResult Function()? result,
required TResult orElse(),
}) {

View File

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

View File

@@ -1,16 +1,13 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.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/w_if.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/models/training_dto.dart';
import 'package:remever/screens/training/cubit/training_cubit.dart';
import 'package:remever/screens/training/states/empty.dart';
import 'package:remever/screens/training/states/loading.dart';
@@ -40,7 +37,7 @@ class TrainingScreen extends StatelessWidget {
return state.when(
loading: () => TrainingLoading(),
empty: () => TrainingEmpty(),
data: () => TrainingData(),
data: (data) => TrainingData(data: data),
result: () => Placeholder(),
);
},
@@ -50,8 +47,9 @@ class TrainingScreen extends StatelessWidget {
}
class TrainingData extends StatefulWidget {
const TrainingData({super.key});
const TrainingData({super.key, required this.data});
final List<TrainingDto> data;
@override
State<TrainingData> createState() => _TrainingDataState();
}
@@ -59,6 +57,8 @@ class TrainingData extends StatefulWidget {
class _TrainingDataState extends State<TrainingData> {
bool _showAnswer = false;
int _currentTicketIndex = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
@@ -97,7 +97,12 @@ class _TrainingDataState extends State<TrainingData> {
),
child: Padding(
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: [
Flexible(
child: PrimaryButton(
onTap: () {},
onTap: () {
if (widget.data.length == _currentTicketIndex + 1) {
context.read<TrainingCubit>().toResultState();
return;
}
safeSetState(() => _currentTicketIndex++);
},
color: AppColors.danger,
child: AppTypography(
'Не помню',
@@ -133,7 +144,13 @@ class _TrainingDataState extends State<TrainingData> {
type: Medium14px(),
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(
spacing: 8.r,
children: [
TrainingTicket(),
TrainingTicket(trainingDto: widget.data[_currentTicketIndex]),
Wif(
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: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/database/database.dart';
import 'package:remever/models/training_dto.dart';
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;
@override
@@ -43,8 +52,10 @@ class TrainingTicket extends StatelessWidget {
}
Widget _buildCollectionInfo() {
final collection = trainingDto.collection;
return Wif(
condition: !isAnswer,
condition: !isAnswer && collection != null,
builder: (context) {
return Padding(
padding: const EdgeInsets.only(bottom: 8).r,
@@ -53,16 +64,28 @@ class TrainingTicket extends StatelessWidget {
SizedBox(
height: 24.h,
width: 24.w,
child: ClipOval(
child: Image.network(
'https://avatars.mds.yandex.net/i?id=56429b65e9098a58fcd538387d43bcbb_l-5384017-images-thumbs&n=13',
fit: BoxFit.cover,
),
child: Wif(
condition: collection!.image != null,
builder:
(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),
AppTypography(
'Астрология и астрофизика',
collection.title,
type: Regular14px(),
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) {
final ticket = trainingDto.ticket;
return AppTypography(
'Родился 19 февраля 1473 года в Торуне в семье купца. После смерти отца воспитывался у дяди, епископа Вармийской епархии. Коперник изложил свои идеи в сочинении «Commentariolus» («Малый комментарий»), в котором сформулировал основные положения гелиоцентрической системы мира в виде 6 аксиом. Их смысл состоит в том, что Земля, как и другие планеты,'
'Родился 19 февраля 1473 года в Торуне в семье купца. После смерти отца воспитывался у дяди, епископа Вармийской епархии. Коперник изложил свои идеи в сочинении «Commentariolus» («Малый комментарий»), в котором сформулировал основные положения гелиоцентрической системы мира в виде 6 аксиом. Их смысл состоит в том, что Земля, как и другие планеты,',
isAnswer ? ticket.answer : ticket.question,
maxLines: 99,
type: Regular14px(),
);
}
Widget _buildImage() {
// final imageBytes =
// isAnswer ? ticket.answerImage : ticket.questionImage;
final ticket = trainingDto.ticket;
final String? imagePath =
isAnswer ? ticket.answerImage : ticket.questionImage;
return Wif(
condition: true, // imageBytes != null,
condition: imagePath != null,
builder:
(context) => Padding(
padding: const EdgeInsets.only(right: 8).r,
@@ -111,11 +136,7 @@ class TrainingTicket extends StatelessWidget {
dimension: 100.r,
child: ClipRRect(
borderRadius: BorderRadius.circular(8).r,
// child: Image.memory(imageBytes!, fit: BoxFit.cover),
child: Image.network(
'https://avatars.mds.yandex.net/i?id=56429b65e9098a58fcd538387d43bcbb_l-5384017-images-thumbs&n=13',
fit: BoxFit.cover,
),
child: Image.file(File(imagePath!), 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 [];
}
}
}