Merge pull request 'Feature: Получение данных для тренировки, фоторедактор, изменение хранения изображений на локальный формат файлов а не байтов в бд' (#8) from feature/training into develop

Reviewed-on: #8
This commit was merged in pull request #8.
This commit is contained in:
2025-09-08 22:46:19 +03:00
47 changed files with 1282 additions and 588 deletions

View File

@@ -529,6 +529,24 @@
"packageUri": "lib/", "packageUri": "lib/",
"languageVersion": "3.0" "languageVersion": "3.0"
}, },
{
"name": "image_cropper",
"rootUri": "file:///Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/image_cropper-9.1.0",
"packageUri": "lib/",
"languageVersion": "3.3"
},
{
"name": "image_cropper_for_web",
"rootUri": "file:///Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/image_cropper_for_web-6.1.0",
"packageUri": "lib/",
"languageVersion": "3.3"
},
{
"name": "image_cropper_platform_interface",
"rootUri": "file:///Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/image_cropper_platform_interface-7.1.0",
"packageUri": "lib/",
"languageVersion": "3.3"
},
{ {
"name": "image_size_getter", "name": "image_size_getter",
"rootUri": "file:///Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/image_size_getter-2.4.0", "rootUri": "file:///Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/image_size_getter-2.4.0",
@@ -1130,7 +1148,7 @@
"languageVersion": "3.7" "languageVersion": "3.7"
} }
], ],
"generated": "2025-06-17T18:40:18.603782Z", "generated": "2025-09-08T19:38:33.805712Z",
"generator": "pub", "generator": "pub",
"generatorVersion": "3.7.0", "generatorVersion": "3.7.0",
"flutterRoot": "file:///Users/vitalijnecitajlo/fvm/versions/3.29.0", "flutterRoot": "file:///Users/vitalijnecitajlo/fvm/versions/3.29.0",

View File

@@ -334,6 +334,18 @@ image
3.0 3.0
file:///Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/image-4.5.4/ file:///Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/image-4.5.4/
file:///Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/image-4.5.4/lib/ file:///Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/image-4.5.4/lib/
image_cropper
3.3
file:///Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/image_cropper-9.1.0/
file:///Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/image_cropper-9.1.0/lib/
image_cropper_for_web
3.3
file:///Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/image_cropper_for_web-6.1.0/
file:///Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/image_cropper_for_web-6.1.0/lib/
image_cropper_platform_interface
3.3
file:///Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/image_cropper_platform_interface-7.1.0/
file:///Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/image_cropper_platform_interface-7.1.0/lib/
image_size_getter image_size_getter
2.12 2.12
file:///Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/image_size_getter-2.4.0/ file:///Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/image_size_getter-2.4.0/

View File

@@ -8,6 +8,8 @@ flutter_secure_storage_macos=/Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/f
flutter_secure_storage_web=/Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/flutter_secure_storage_web-1.2.1/ flutter_secure_storage_web=/Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/flutter_secure_storage_web-1.2.1/
flutter_secure_storage_windows=/Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/flutter_secure_storage_windows-3.1.2/ flutter_secure_storage_windows=/Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/flutter_secure_storage_windows-3.1.2/
fluttertoast=/Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/fluttertoast-8.2.12/ fluttertoast=/Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/fluttertoast-8.2.12/
image_cropper=/Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/image_cropper-9.1.0/
image_cropper_for_web=/Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/image_cropper_for_web-6.1.0/
package_info_plus=/Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/package_info_plus-8.3.0/ package_info_plus=/Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/package_info_plus-8.3.0/
path_provider=/Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/path_provider-2.1.5/ path_provider=/Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/path_provider-2.1.5/
path_provider_android=/Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/path_provider_android-2.2.16/ path_provider_android=/Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/path_provider_android-2.2.16/

File diff suppressed because one or more lines are too long

View File

@@ -8,10 +8,6 @@ clean:
codegen: clean codegen: clean
fvm flutter pub run build_runner build --delete-conflicting-outputs fvm flutter pub run build_runner build --delete-conflicting-outputs
watchgen: clean
fvm flutter pub run build_runner watch --delete-conflicting-outputs
iosPod: iosPod:
cd ios && rm -rf Podfile.lock Pods Runner.xcworkspace && fvm flutter clean && fvm flutter pub get && pod install cd ios && rm -rf Podfile.lock Pods Runner.xcworkspace && fvm flutter clean && fvm flutter pub get && pod install
@@ -31,8 +27,8 @@ macosPodUpdate:
cd macos && pod install --repo-update cd macos && pod install --repo-update
androidBuild: clean androidBuild: clean
fvm flutter build apk --split-per-abi fvm flutter build apk
mv `pwd`/build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk /tmp/$(PROJECT_NAME)-$(env)-$(ver).apk mv `pwd`/build/app/outputs/flutter-apk/app-release.apk /tmp/$(PROJECT_NAME)-$(env)-$(ver).apk
echo /tmp/$(PROJECT_NAME)-$(env)-$(ver).apk echo /tmp/$(PROJECT_NAME)-$(env)-$(ver).apk
open /tmp open /tmp

View File

@@ -38,6 +38,11 @@
<meta-data <meta-data
android:name="io.flutter.embedding.android.EnableImpeller" android:name="io.flutter.embedding.android.EnableImpeller"
android:value="false" /> android:value="false" />
<activity
android:name="com.yalantis.ucrop.UCropActivity"
android:screenOrientation="portrait"
android:theme="@style/Ucrop.CropTheme" />
</application> </application>
<!-- Required to query activities that can process text, see: <!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and https://developer.android.com/training/package-visibility and

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Ucrop.CropTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
</style>
</resources>

View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off --> <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode
setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar"> <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when <!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame --> the Flutter engine draws its first frame -->
@@ -15,4 +16,8 @@
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar"> <style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item> <item name="android:windowBackground">?android:colorBackground</item>
</style> </style>
<style name="Ucrop.CropTheme" parent="Theme.AppCompat.Light.NoActionBar" /> <!--add
this line-->
</resources> </resources>

View File

@@ -2,6 +2,8 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:dio_smart_retry/dio_smart_retry.dart'; import 'package:dio_smart_retry/dio_smart_retry.dart';
import 'package:remever/common/functions.dart'; import 'package:remever/common/functions.dart';
import 'package:remever/common/resources.dart';
import 'package:remever/common/storage.dart';
import 'package:talker_dio_logger/talker_dio_logger_interceptor.dart'; import 'package:talker_dio_logger/talker_dio_logger_interceptor.dart';
import 'package:talker_dio_logger/talker_dio_logger_settings.dart'; import 'package:talker_dio_logger/talker_dio_logger_settings.dart';
@@ -17,17 +19,17 @@ InterceptorsWrapper get _auth {
RequestOptions options, RequestOptions options,
RequestInterceptorHandler handler, RequestInterceptorHandler handler,
) async { ) async {
// try { try {
// String? token = await authSecStorage.read(key: StorageKeys.authToken); String? token = await authSecStorage.read(key: StorageKeys.accessToken);
// if (token != null) { if (token != null) {
// options.headers['Authorization'] = 'Bearer $token'; options.headers['Authorization'] = 'Bearer $token';
// } }
// } catch (e) { } catch (e) {
// getIt<LogService>().log( // getIt<LogService>().log(
// entity: LogEntity.error(message: 'Error to load access token $e'), // entity: LogEntity.error(message: 'Error to load access token $e'),
// ); // );
// } }
return handler.next(options); return handler.next(options);
}, },

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 {
@@ -36,7 +48,7 @@ class CollectionsDao extends DatabaseAccessor<AppDatabase>
title: dto.title, title: dto.title,
desc: dto.desc, desc: dto.desc,
isPublic: Value<bool>(dto.isPublic), isPublic: Value<bool>(dto.isPublic),
image: Value<Uint8List?>(dto.avatar), image: Value<String?>(dto.avatar),
), ),
); );
} catch (e, st) { } catch (e, st) {
@@ -54,7 +66,7 @@ class CollectionsDao extends DatabaseAccessor<AppDatabase>
title: Value<String>(dto.title), title: Value<String>(dto.title),
desc: Value<String>(dto.desc), desc: Value<String>(dto.desc),
isPublic: Value<bool>(dto.isPublic), isPublic: Value<bool>(dto.isPublic),
image: Value<Uint8List?>(dto.avatar), image: Value<String?>(dto.avatar),
), ),
); );
} catch (e, st) { } catch (e, st) {

View File

@@ -27,6 +27,20 @@ class TicketsDao extends DatabaseAccessor<AppDatabase> with _$TicketsDaoMixin {
} }
} }
/// Получение билетов из базы данных
Future<int> getTicketsInCollectionCount(String collectionId) async {
try {
return (await db.managers.tickets
.filter((f) => f.collectionId.id(collectionId))
.get())
.length;
} catch (e, st) {
logger.logError('Ошибка в методе getTicketsInCollectionCount', e, st);
throw ('EXEPTION');
}
}
/// Удаление билета /// Удаление билета
Future<void> removeTicket(String id) async { Future<void> removeTicket(String id) async {
try { try {
@@ -45,8 +59,8 @@ class TicketsDao extends DatabaseAccessor<AppDatabase> with _$TicketsDaoMixin {
question: dto.question!, question: dto.question!,
answer: dto.answer!, answer: dto.answer!,
collectionId: dto.collection!.id, collectionId: dto.collection!.id,
questionImage: Value<Uint8List?>(dto.questionImage), questionImage: Value<String?>(dto.questionImage),
answerImage: Value<Uint8List?>(dto.answerImage), answerImage: Value<String?>(dto.answerImage),
); );
await db.managers.tickets.create((o) => companion); await db.managers.tickets.create((o) => companion);
@@ -58,8 +72,8 @@ class TicketsDao extends DatabaseAccessor<AppDatabase> with _$TicketsDaoMixin {
answer: dto.question!, answer: dto.question!,
question: dto.answer!, question: dto.answer!,
collectionId: dto.collection!.id, collectionId: dto.collection!.id,
answerImage: Value<Uint8List?>(dto.questionImage), answerImage: Value<String?>(dto.questionImage),
questionImage: Value<Uint8List?>(dto.answerImage), questionImage: Value<String?>(dto.answerImage),
); );
await db.managers.tickets.create((o) => revertCompanion); await db.managers.tickets.create((o) => revertCompanion);

View File

@@ -75,11 +75,11 @@ class $CollectionsTable extends Collections
); );
static const VerificationMeta _imageMeta = const VerificationMeta('image'); static const VerificationMeta _imageMeta = const VerificationMeta('image');
@override @override
late final GeneratedColumn<Uint8List> image = GeneratedColumn<Uint8List>( late final GeneratedColumn<String> image = GeneratedColumn<String>(
'image', 'image',
aliasedName, aliasedName,
true, true,
type: DriftSqlType.blob, type: DriftSqlType.string,
requiredDuringInsert: false, requiredDuringInsert: false,
); );
static const VerificationMeta _payloadMeta = const VerificationMeta( static const VerificationMeta _payloadMeta = const VerificationMeta(
@@ -281,7 +281,7 @@ class $CollectionsTable extends Collections
data['${effectivePrefix}desc'], data['${effectivePrefix}desc'],
)!, )!,
image: attachedDatabase.typeMapping.read( image: attachedDatabase.typeMapping.read(
DriftSqlType.blob, DriftSqlType.string,
data['${effectivePrefix}image'], data['${effectivePrefix}image'],
), ),
payload: attachedDatabase.typeMapping.read( payload: attachedDatabase.typeMapping.read(
@@ -328,7 +328,7 @@ class Collection extends DataClass implements Insertable<Collection> {
final DateTime updatedAt; final DateTime updatedAt;
final String title; final String title;
final String desc; final String desc;
final Uint8List? image; final String? image;
final String? payload; final String? payload;
final int likesCount; final int likesCount;
final bool isLiked; final bool isLiked;
@@ -356,7 +356,7 @@ class Collection extends DataClass implements Insertable<Collection> {
map['title'] = Variable<String>(title); map['title'] = Variable<String>(title);
map['desc'] = Variable<String>(desc); map['desc'] = Variable<String>(desc);
if (!nullToAbsent || image != null) { if (!nullToAbsent || image != null) {
map['image'] = Variable<Uint8List>(image); map['image'] = Variable<String>(image);
} }
if (!nullToAbsent || payload != null) { if (!nullToAbsent || payload != null) {
map['payload'] = Variable<String>(payload); map['payload'] = Variable<String>(payload);
@@ -399,7 +399,7 @@ class Collection extends DataClass implements Insertable<Collection> {
updatedAt: serializer.fromJson<DateTime>(json['updatedAt']), updatedAt: serializer.fromJson<DateTime>(json['updatedAt']),
title: serializer.fromJson<String>(json['title']), title: serializer.fromJson<String>(json['title']),
desc: serializer.fromJson<String>(json['desc']), desc: serializer.fromJson<String>(json['desc']),
image: serializer.fromJson<Uint8List?>(json['image']), image: serializer.fromJson<String?>(json['image']),
payload: serializer.fromJson<String?>(json['payload']), payload: serializer.fromJson<String?>(json['payload']),
likesCount: serializer.fromJson<int>(json['likesCount']), likesCount: serializer.fromJson<int>(json['likesCount']),
isLiked: serializer.fromJson<bool>(json['isLiked']), isLiked: serializer.fromJson<bool>(json['isLiked']),
@@ -416,7 +416,7 @@ class Collection extends DataClass implements Insertable<Collection> {
'updatedAt': serializer.toJson<DateTime>(updatedAt), 'updatedAt': serializer.toJson<DateTime>(updatedAt),
'title': serializer.toJson<String>(title), 'title': serializer.toJson<String>(title),
'desc': serializer.toJson<String>(desc), 'desc': serializer.toJson<String>(desc),
'image': serializer.toJson<Uint8List?>(image), 'image': serializer.toJson<String?>(image),
'payload': serializer.toJson<String?>(payload), 'payload': serializer.toJson<String?>(payload),
'likesCount': serializer.toJson<int>(likesCount), 'likesCount': serializer.toJson<int>(likesCount),
'isLiked': serializer.toJson<bool>(isLiked), 'isLiked': serializer.toJson<bool>(isLiked),
@@ -431,7 +431,7 @@ class Collection extends DataClass implements Insertable<Collection> {
DateTime? updatedAt, DateTime? updatedAt,
String? title, String? title,
String? desc, String? desc,
Value<Uint8List?> image = const Value.absent(), Value<String?> image = const Value.absent(),
Value<String?> payload = const Value.absent(), Value<String?> payload = const Value.absent(),
int? likesCount, int? likesCount,
bool? isLiked, bool? isLiked,
@@ -495,7 +495,7 @@ class Collection extends DataClass implements Insertable<Collection> {
updatedAt, updatedAt,
title, title,
desc, desc,
$driftBlobEquality.hash(image), image,
payload, payload,
likesCount, likesCount,
isLiked, isLiked,
@@ -511,7 +511,7 @@ class Collection extends DataClass implements Insertable<Collection> {
other.updatedAt == this.updatedAt && other.updatedAt == this.updatedAt &&
other.title == this.title && other.title == this.title &&
other.desc == this.desc && other.desc == this.desc &&
$driftBlobEquality.equals(other.image, this.image) && other.image == this.image &&
other.payload == this.payload && other.payload == this.payload &&
other.likesCount == this.likesCount && other.likesCount == this.likesCount &&
other.isLiked == this.isLiked && other.isLiked == this.isLiked &&
@@ -525,7 +525,7 @@ class CollectionsCompanion extends UpdateCompanion<Collection> {
final Value<DateTime> updatedAt; final Value<DateTime> updatedAt;
final Value<String> title; final Value<String> title;
final Value<String> desc; final Value<String> desc;
final Value<Uint8List?> image; final Value<String?> image;
final Value<String?> payload; final Value<String?> payload;
final Value<int> likesCount; final Value<int> likesCount;
final Value<bool> isLiked; final Value<bool> isLiked;
@@ -567,7 +567,7 @@ class CollectionsCompanion extends UpdateCompanion<Collection> {
Expression<DateTime>? updatedAt, Expression<DateTime>? updatedAt,
Expression<String>? title, Expression<String>? title,
Expression<String>? desc, Expression<String>? desc,
Expression<Uint8List>? image, Expression<String>? image,
Expression<String>? payload, Expression<String>? payload,
Expression<int>? likesCount, Expression<int>? likesCount,
Expression<bool>? isLiked, Expression<bool>? isLiked,
@@ -597,7 +597,7 @@ class CollectionsCompanion extends UpdateCompanion<Collection> {
Value<DateTime>? updatedAt, Value<DateTime>? updatedAt,
Value<String>? title, Value<String>? title,
Value<String>? desc, Value<String>? desc,
Value<Uint8List?>? image, Value<String?>? image,
Value<String?>? payload, Value<String?>? payload,
Value<int>? likesCount, Value<int>? likesCount,
Value<bool>? isLiked, Value<bool>? isLiked,
@@ -640,7 +640,7 @@ class CollectionsCompanion extends UpdateCompanion<Collection> {
map['desc'] = Variable<String>(desc.value); map['desc'] = Variable<String>(desc.value);
} }
if (image.present) { if (image.present) {
map['image'] = Variable<Uint8List>(image.value); map['image'] = Variable<String>(image.value);
} }
if (payload.present) { if (payload.present) {
map['payload'] = Variable<String>(payload.value); map['payload'] = Variable<String>(payload.value);
@@ -749,14 +749,13 @@ class $TicketsTable extends Tickets with TableInfo<$TicketsTable, Ticket> {
'questionImage', 'questionImage',
); );
@override @override
late final GeneratedColumn<Uint8List> questionImage = late final GeneratedColumn<String> questionImage = GeneratedColumn<String>(
GeneratedColumn<Uint8List>( 'question_image',
'question_image', aliasedName,
aliasedName, true,
true, type: DriftSqlType.string,
type: DriftSqlType.blob, requiredDuringInsert: false,
requiredDuringInsert: false, );
);
static const VerificationMeta _answerMeta = const VerificationMeta('answer'); static const VerificationMeta _answerMeta = const VerificationMeta('answer');
@override @override
late final GeneratedColumn<String> answer = GeneratedColumn<String>( late final GeneratedColumn<String> answer = GeneratedColumn<String>(
@@ -770,14 +769,13 @@ class $TicketsTable extends Tickets with TableInfo<$TicketsTable, Ticket> {
'answerImage', 'answerImage',
); );
@override @override
late final GeneratedColumn<Uint8List> answerImage = late final GeneratedColumn<String> answerImage = GeneratedColumn<String>(
GeneratedColumn<Uint8List>( 'answer_image',
'answer_image', aliasedName,
aliasedName, true,
true, type: DriftSqlType.string,
type: DriftSqlType.blob, requiredDuringInsert: false,
requiredDuringInsert: false, );
);
static const VerificationMeta _collectionIdMeta = const VerificationMeta( static const VerificationMeta _collectionIdMeta = const VerificationMeta(
'collectionId', 'collectionId',
); );
@@ -924,7 +922,7 @@ class $TicketsTable extends Tickets with TableInfo<$TicketsTable, Ticket> {
data['${effectivePrefix}question'], data['${effectivePrefix}question'],
)!, )!,
questionImage: attachedDatabase.typeMapping.read( questionImage: attachedDatabase.typeMapping.read(
DriftSqlType.blob, DriftSqlType.string,
data['${effectivePrefix}question_image'], data['${effectivePrefix}question_image'],
), ),
answer: answer:
@@ -933,7 +931,7 @@ class $TicketsTable extends Tickets with TableInfo<$TicketsTable, Ticket> {
data['${effectivePrefix}answer'], data['${effectivePrefix}answer'],
)!, )!,
answerImage: attachedDatabase.typeMapping.read( answerImage: attachedDatabase.typeMapping.read(
DriftSqlType.blob, DriftSqlType.string,
data['${effectivePrefix}answer_image'], data['${effectivePrefix}answer_image'],
), ),
collectionId: collectionId:
@@ -965,9 +963,9 @@ class Ticket extends DataClass implements Insertable<Ticket> {
/// Дата последней модификации /// Дата последней модификации
final DateTime updatedAt; final DateTime updatedAt;
final String question; final String question;
final Uint8List? questionImage; final String? questionImage;
final String answer; final String answer;
final Uint8List? answerImage; final String? answerImage;
final String collectionId; final String collectionId;
final double progress; final double progress;
const Ticket({ const Ticket({
@@ -989,11 +987,11 @@ class Ticket extends DataClass implements Insertable<Ticket> {
map['updated_at'] = Variable<DateTime>(updatedAt); map['updated_at'] = Variable<DateTime>(updatedAt);
map['question'] = Variable<String>(question); map['question'] = Variable<String>(question);
if (!nullToAbsent || questionImage != null) { if (!nullToAbsent || questionImage != null) {
map['question_image'] = Variable<Uint8List>(questionImage); map['question_image'] = Variable<String>(questionImage);
} }
map['answer'] = Variable<String>(answer); map['answer'] = Variable<String>(answer);
if (!nullToAbsent || answerImage != null) { if (!nullToAbsent || answerImage != null) {
map['answer_image'] = Variable<Uint8List>(answerImage); map['answer_image'] = Variable<String>(answerImage);
} }
map['collection_id'] = Variable<String>(collectionId); map['collection_id'] = Variable<String>(collectionId);
map['progress'] = Variable<double>(progress); map['progress'] = Variable<double>(progress);
@@ -1030,9 +1028,9 @@ class Ticket extends DataClass implements Insertable<Ticket> {
createdAt: serializer.fromJson<DateTime>(json['createdAt']), createdAt: serializer.fromJson<DateTime>(json['createdAt']),
updatedAt: serializer.fromJson<DateTime>(json['updatedAt']), updatedAt: serializer.fromJson<DateTime>(json['updatedAt']),
question: serializer.fromJson<String>(json['question']), question: serializer.fromJson<String>(json['question']),
questionImage: serializer.fromJson<Uint8List?>(json['questionImage']), questionImage: serializer.fromJson<String?>(json['questionImage']),
answer: serializer.fromJson<String>(json['answer']), answer: serializer.fromJson<String>(json['answer']),
answerImage: serializer.fromJson<Uint8List?>(json['answerImage']), answerImage: serializer.fromJson<String?>(json['answerImage']),
collectionId: serializer.fromJson<String>(json['collectionId']), collectionId: serializer.fromJson<String>(json['collectionId']),
progress: serializer.fromJson<double>(json['progress']), progress: serializer.fromJson<double>(json['progress']),
); );
@@ -1045,9 +1043,9 @@ class Ticket extends DataClass implements Insertable<Ticket> {
'createdAt': serializer.toJson<DateTime>(createdAt), 'createdAt': serializer.toJson<DateTime>(createdAt),
'updatedAt': serializer.toJson<DateTime>(updatedAt), 'updatedAt': serializer.toJson<DateTime>(updatedAt),
'question': serializer.toJson<String>(question), 'question': serializer.toJson<String>(question),
'questionImage': serializer.toJson<Uint8List?>(questionImage), 'questionImage': serializer.toJson<String?>(questionImage),
'answer': serializer.toJson<String>(answer), 'answer': serializer.toJson<String>(answer),
'answerImage': serializer.toJson<Uint8List?>(answerImage), 'answerImage': serializer.toJson<String?>(answerImage),
'collectionId': serializer.toJson<String>(collectionId), 'collectionId': serializer.toJson<String>(collectionId),
'progress': serializer.toJson<double>(progress), 'progress': serializer.toJson<double>(progress),
}; };
@@ -1058,9 +1056,9 @@ class Ticket extends DataClass implements Insertable<Ticket> {
DateTime? createdAt, DateTime? createdAt,
DateTime? updatedAt, DateTime? updatedAt,
String? question, String? question,
Value<Uint8List?> questionImage = const Value.absent(), Value<String?> questionImage = const Value.absent(),
String? answer, String? answer,
Value<Uint8List?> answerImage = const Value.absent(), Value<String?> answerImage = const Value.absent(),
String? collectionId, String? collectionId,
double? progress, double? progress,
}) => Ticket( }) => Ticket(
@@ -1118,9 +1116,9 @@ class Ticket extends DataClass implements Insertable<Ticket> {
createdAt, createdAt,
updatedAt, updatedAt,
question, question,
$driftBlobEquality.hash(questionImage), questionImage,
answer, answer,
$driftBlobEquality.hash(answerImage), answerImage,
collectionId, collectionId,
progress, progress,
); );
@@ -1132,9 +1130,9 @@ class Ticket extends DataClass implements Insertable<Ticket> {
other.createdAt == this.createdAt && other.createdAt == this.createdAt &&
other.updatedAt == this.updatedAt && other.updatedAt == this.updatedAt &&
other.question == this.question && other.question == this.question &&
$driftBlobEquality.equals(other.questionImage, this.questionImage) && other.questionImage == this.questionImage &&
other.answer == this.answer && other.answer == this.answer &&
$driftBlobEquality.equals(other.answerImage, this.answerImage) && other.answerImage == this.answerImage &&
other.collectionId == this.collectionId && other.collectionId == this.collectionId &&
other.progress == this.progress); other.progress == this.progress);
} }
@@ -1144,9 +1142,9 @@ class TicketsCompanion extends UpdateCompanion<Ticket> {
final Value<DateTime> createdAt; final Value<DateTime> createdAt;
final Value<DateTime> updatedAt; final Value<DateTime> updatedAt;
final Value<String> question; final Value<String> question;
final Value<Uint8List?> questionImage; final Value<String?> questionImage;
final Value<String> answer; final Value<String> answer;
final Value<Uint8List?> answerImage; final Value<String?> answerImage;
final Value<String> collectionId; final Value<String> collectionId;
final Value<double> progress; final Value<double> progress;
final Value<int> rowid; final Value<int> rowid;
@@ -1181,9 +1179,9 @@ class TicketsCompanion extends UpdateCompanion<Ticket> {
Expression<DateTime>? createdAt, Expression<DateTime>? createdAt,
Expression<DateTime>? updatedAt, Expression<DateTime>? updatedAt,
Expression<String>? question, Expression<String>? question,
Expression<Uint8List>? questionImage, Expression<String>? questionImage,
Expression<String>? answer, Expression<String>? answer,
Expression<Uint8List>? answerImage, Expression<String>? answerImage,
Expression<String>? collectionId, Expression<String>? collectionId,
Expression<double>? progress, Expression<double>? progress,
Expression<int>? rowid, Expression<int>? rowid,
@@ -1207,9 +1205,9 @@ class TicketsCompanion extends UpdateCompanion<Ticket> {
Value<DateTime>? createdAt, Value<DateTime>? createdAt,
Value<DateTime>? updatedAt, Value<DateTime>? updatedAt,
Value<String>? question, Value<String>? question,
Value<Uint8List?>? questionImage, Value<String?>? questionImage,
Value<String>? answer, Value<String>? answer,
Value<Uint8List?>? answerImage, Value<String?>? answerImage,
Value<String>? collectionId, Value<String>? collectionId,
Value<double>? progress, Value<double>? progress,
Value<int>? rowid, Value<int>? rowid,
@@ -1244,13 +1242,13 @@ class TicketsCompanion extends UpdateCompanion<Ticket> {
map['question'] = Variable<String>(question.value); map['question'] = Variable<String>(question.value);
} }
if (questionImage.present) { if (questionImage.present) {
map['question_image'] = Variable<Uint8List>(questionImage.value); map['question_image'] = Variable<String>(questionImage.value);
} }
if (answer.present) { if (answer.present) {
map['answer'] = Variable<String>(answer.value); map['answer'] = Variable<String>(answer.value);
} }
if (answerImage.present) { if (answerImage.present) {
map['answer_image'] = Variable<Uint8List>(answerImage.value); map['answer_image'] = Variable<String>(answerImage.value);
} }
if (collectionId.present) { if (collectionId.present) {
map['collection_id'] = Variable<String>(collectionId.value); map['collection_id'] = Variable<String>(collectionId.value);
@@ -1318,7 +1316,7 @@ typedef $$CollectionsTableCreateCompanionBuilder =
Value<DateTime> updatedAt, Value<DateTime> updatedAt,
required String title, required String title,
required String desc, required String desc,
Value<Uint8List?> image, Value<String?> image,
Value<String?> payload, Value<String?> payload,
Value<int> likesCount, Value<int> likesCount,
Value<bool> isLiked, Value<bool> isLiked,
@@ -1333,7 +1331,7 @@ typedef $$CollectionsTableUpdateCompanionBuilder =
Value<DateTime> updatedAt, Value<DateTime> updatedAt,
Value<String> title, Value<String> title,
Value<String> desc, Value<String> desc,
Value<Uint8List?> image, Value<String?> image,
Value<String?> payload, Value<String?> payload,
Value<int> likesCount, Value<int> likesCount,
Value<bool> isLiked, Value<bool> isLiked,
@@ -1400,7 +1398,7 @@ class $$CollectionsTableFilterComposer
builder: (column) => ColumnFilters(column), builder: (column) => ColumnFilters(column),
); );
ColumnFilters<Uint8List> get image => $composableBuilder( ColumnFilters<String> get image => $composableBuilder(
column: $table.image, column: $table.image,
builder: (column) => ColumnFilters(column), builder: (column) => ColumnFilters(column),
); );
@@ -1490,7 +1488,7 @@ class $$CollectionsTableOrderingComposer
builder: (column) => ColumnOrderings(column), builder: (column) => ColumnOrderings(column),
); );
ColumnOrderings<Uint8List> get image => $composableBuilder( ColumnOrderings<String> get image => $composableBuilder(
column: $table.image, column: $table.image,
builder: (column) => ColumnOrderings(column), builder: (column) => ColumnOrderings(column),
); );
@@ -1545,7 +1543,7 @@ class $$CollectionsTableAnnotationComposer
GeneratedColumn<String> get desc => GeneratedColumn<String> get desc =>
$composableBuilder(column: $table.desc, builder: (column) => column); $composableBuilder(column: $table.desc, builder: (column) => column);
GeneratedColumn<Uint8List> get image => GeneratedColumn<String> get image =>
$composableBuilder(column: $table.image, builder: (column) => column); $composableBuilder(column: $table.image, builder: (column) => column);
GeneratedColumn<String> get payload => GeneratedColumn<String> get payload =>
@@ -1627,7 +1625,7 @@ class $$CollectionsTableTableManager
Value<DateTime> updatedAt = const Value.absent(), Value<DateTime> updatedAt = const Value.absent(),
Value<String> title = const Value.absent(), Value<String> title = const Value.absent(),
Value<String> desc = const Value.absent(), Value<String> desc = const Value.absent(),
Value<Uint8List?> image = const Value.absent(), Value<String?> image = const Value.absent(),
Value<String?> payload = const Value.absent(), Value<String?> payload = const Value.absent(),
Value<int> likesCount = const Value.absent(), Value<int> likesCount = const Value.absent(),
Value<bool> isLiked = const Value.absent(), Value<bool> isLiked = const Value.absent(),
@@ -1655,7 +1653,7 @@ class $$CollectionsTableTableManager
Value<DateTime> updatedAt = const Value.absent(), Value<DateTime> updatedAt = const Value.absent(),
required String title, required String title,
required String desc, required String desc,
Value<Uint8List?> image = const Value.absent(), Value<String?> image = const Value.absent(),
Value<String?> payload = const Value.absent(), Value<String?> payload = const Value.absent(),
Value<int> likesCount = const Value.absent(), Value<int> likesCount = const Value.absent(),
Value<bool> isLiked = const Value.absent(), Value<bool> isLiked = const Value.absent(),
@@ -1743,9 +1741,9 @@ typedef $$TicketsTableCreateCompanionBuilder =
Value<DateTime> createdAt, Value<DateTime> createdAt,
Value<DateTime> updatedAt, Value<DateTime> updatedAt,
required String question, required String question,
Value<Uint8List?> questionImage, Value<String?> questionImage,
required String answer, required String answer,
Value<Uint8List?> answerImage, Value<String?> answerImage,
required String collectionId, required String collectionId,
Value<double> progress, Value<double> progress,
Value<int> rowid, Value<int> rowid,
@@ -1756,9 +1754,9 @@ typedef $$TicketsTableUpdateCompanionBuilder =
Value<DateTime> createdAt, Value<DateTime> createdAt,
Value<DateTime> updatedAt, Value<DateTime> updatedAt,
Value<String> question, Value<String> question,
Value<Uint8List?> questionImage, Value<String?> questionImage,
Value<String> answer, Value<String> answer,
Value<Uint8List?> answerImage, Value<String?> answerImage,
Value<String> collectionId, Value<String> collectionId,
Value<double> progress, Value<double> progress,
Value<int> rowid, Value<int> rowid,
@@ -1817,7 +1815,7 @@ class $$TicketsTableFilterComposer
builder: (column) => ColumnFilters(column), builder: (column) => ColumnFilters(column),
); );
ColumnFilters<Uint8List> get questionImage => $composableBuilder( ColumnFilters<String> get questionImage => $composableBuilder(
column: $table.questionImage, column: $table.questionImage,
builder: (column) => ColumnFilters(column), builder: (column) => ColumnFilters(column),
); );
@@ -1827,7 +1825,7 @@ class $$TicketsTableFilterComposer
builder: (column) => ColumnFilters(column), builder: (column) => ColumnFilters(column),
); );
ColumnFilters<Uint8List> get answerImage => $composableBuilder( ColumnFilters<String> get answerImage => $composableBuilder(
column: $table.answerImage, column: $table.answerImage,
builder: (column) => ColumnFilters(column), builder: (column) => ColumnFilters(column),
); );
@@ -1890,7 +1888,7 @@ class $$TicketsTableOrderingComposer
builder: (column) => ColumnOrderings(column), builder: (column) => ColumnOrderings(column),
); );
ColumnOrderings<Uint8List> get questionImage => $composableBuilder( ColumnOrderings<String> get questionImage => $composableBuilder(
column: $table.questionImage, column: $table.questionImage,
builder: (column) => ColumnOrderings(column), builder: (column) => ColumnOrderings(column),
); );
@@ -1900,7 +1898,7 @@ class $$TicketsTableOrderingComposer
builder: (column) => ColumnOrderings(column), builder: (column) => ColumnOrderings(column),
); );
ColumnOrderings<Uint8List> get answerImage => $composableBuilder( ColumnOrderings<String> get answerImage => $composableBuilder(
column: $table.answerImage, column: $table.answerImage,
builder: (column) => ColumnOrderings(column), builder: (column) => ColumnOrderings(column),
); );
@@ -1955,7 +1953,7 @@ class $$TicketsTableAnnotationComposer
GeneratedColumn<String> get question => GeneratedColumn<String> get question =>
$composableBuilder(column: $table.question, builder: (column) => column); $composableBuilder(column: $table.question, builder: (column) => column);
GeneratedColumn<Uint8List> get questionImage => $composableBuilder( GeneratedColumn<String> get questionImage => $composableBuilder(
column: $table.questionImage, column: $table.questionImage,
builder: (column) => column, builder: (column) => column,
); );
@@ -1963,7 +1961,7 @@ class $$TicketsTableAnnotationComposer
GeneratedColumn<String> get answer => GeneratedColumn<String> get answer =>
$composableBuilder(column: $table.answer, builder: (column) => column); $composableBuilder(column: $table.answer, builder: (column) => column);
GeneratedColumn<Uint8List> get answerImage => $composableBuilder( GeneratedColumn<String> get answerImage => $composableBuilder(
column: $table.answerImage, column: $table.answerImage,
builder: (column) => column, builder: (column) => column,
); );
@@ -2027,9 +2025,9 @@ class $$TicketsTableTableManager
Value<DateTime> createdAt = const Value.absent(), Value<DateTime> createdAt = const Value.absent(),
Value<DateTime> updatedAt = const Value.absent(), Value<DateTime> updatedAt = const Value.absent(),
Value<String> question = const Value.absent(), Value<String> question = const Value.absent(),
Value<Uint8List?> questionImage = const Value.absent(), Value<String?> questionImage = const Value.absent(),
Value<String> answer = const Value.absent(), Value<String> answer = const Value.absent(),
Value<Uint8List?> answerImage = const Value.absent(), Value<String?> answerImage = const Value.absent(),
Value<String> collectionId = const Value.absent(), Value<String> collectionId = const Value.absent(),
Value<double> progress = const Value.absent(), Value<double> progress = const Value.absent(),
Value<int> rowid = const Value.absent(), Value<int> rowid = const Value.absent(),
@@ -2051,9 +2049,9 @@ class $$TicketsTableTableManager
Value<DateTime> createdAt = const Value.absent(), Value<DateTime> createdAt = const Value.absent(),
Value<DateTime> updatedAt = const Value.absent(), Value<DateTime> updatedAt = const Value.absent(),
required String question, required String question,
Value<Uint8List?> questionImage = const Value.absent(), Value<String?> questionImage = const Value.absent(),
required String answer, required String answer,
Value<Uint8List?> answerImage = const Value.absent(), Value<String?> answerImage = const Value.absent(),
required String collectionId, required String collectionId,
Value<double> progress = const Value.absent(), Value<double> progress = const Value.absent(),
Value<int> rowid = const Value.absent(), Value<int> rowid = const Value.absent(),

View File

@@ -64,7 +64,7 @@ mixin _Deletable on Table {
class Collections extends Table with _UuidPrimaryKey, _Timestampable { class Collections extends Table with _UuidPrimaryKey, _Timestampable {
TextColumn get title => text()(); TextColumn get title => text()();
TextColumn get desc => text()(); TextColumn get desc => text()();
BlobColumn get image => blob().nullable()(); TextColumn get image => text().nullable()();
TextColumn get payload => text().nullable()(); TextColumn get payload => text().nullable()();
IntColumn get likesCount => integer().withDefault(Constant(0))(); IntColumn get likesCount => integer().withDefault(Constant(0))();
BoolColumn get isLiked => boolean().withDefault(Constant(false))(); BoolColumn get isLiked => boolean().withDefault(Constant(false))();
@@ -78,9 +78,9 @@ class Collections extends Table with _UuidPrimaryKey, _Timestampable {
@DataClassName('Ticket') @DataClassName('Ticket')
class Tickets extends Table with _UuidPrimaryKey, _Timestampable { class Tickets extends Table with _UuidPrimaryKey, _Timestampable {
TextColumn get question => text()(); TextColumn get question => text()();
BlobColumn get questionImage => blob().named('question_image').nullable()(); TextColumn get questionImage => text().named('question_image').nullable()();
TextColumn get answer => text()(); TextColumn get answer => text()();
BlobColumn get answerImage => blob().named('answer_image').nullable()(); TextColumn get answerImage => text().named('answer_image').nullable()();
TextColumn get collectionId => TextColumn get collectionId =>
text().references(Collections, #id, onDelete: KeyAction.cascade)(); text().references(Collections, #id, onDelete: KeyAction.cascade)();
RealColumn get progress => real().withDefault(Constant(0))(); RealColumn get progress => real().withDefault(Constant(0))();

View File

@@ -6,7 +6,7 @@
/// Locales: 2 /// Locales: 2
/// Strings: 20 (10 per locale) /// Strings: 20 (10 per locale)
/// ///
/// Built on 2025-06-17 at 18:40 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

@@ -2,8 +2,6 @@
// //
// final collectionDto = collectionDtoFromJson(jsonString); // final collectionDto = collectionDtoFromJson(jsonString);
import 'dart:convert';
import 'dart:typed_data';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:remever/database/database.dart'; import 'package:remever/database/database.dart';
@@ -14,9 +12,9 @@ abstract class CreateTicketDto with _$CreateTicketDto {
const factory CreateTicketDto({ const factory CreateTicketDto({
Collection? collection, Collection? collection,
String? question, String? question,
Uint8List? questionImage, String? questionImage,
String? answer, String? answer,
Uint8List? answerImage, String? answerImage,
bool? needRevert, bool? needRevert,
}) = _CreateTicketDto; }) = _CreateTicketDto;
} }

View File

@@ -19,9 +19,9 @@ final _privateConstructorUsedError = UnsupportedError(
mixin _$CreateTicketDto { mixin _$CreateTicketDto {
Collection? get collection => throw _privateConstructorUsedError; Collection? get collection => throw _privateConstructorUsedError;
String? get question => throw _privateConstructorUsedError; String? get question => throw _privateConstructorUsedError;
Uint8List? get questionImage => throw _privateConstructorUsedError; String? get questionImage => throw _privateConstructorUsedError;
String? get answer => throw _privateConstructorUsedError; String? get answer => throw _privateConstructorUsedError;
Uint8List? get answerImage => throw _privateConstructorUsedError; String? get answerImage => throw _privateConstructorUsedError;
bool? get needRevert => throw _privateConstructorUsedError; bool? get needRevert => throw _privateConstructorUsedError;
/// Create a copy of CreateTicketDto /// Create a copy of CreateTicketDto
@@ -41,9 +41,9 @@ abstract class $CreateTicketDtoCopyWith<$Res> {
$Res call({ $Res call({
Collection? collection, Collection? collection,
String? question, String? question,
Uint8List? questionImage, String? questionImage,
String? answer, String? answer,
Uint8List? answerImage, String? answerImage,
bool? needRevert, bool? needRevert,
}); });
} }
@@ -86,7 +86,7 @@ class _$CreateTicketDtoCopyWithImpl<$Res, $Val extends CreateTicketDto>
freezed == questionImage freezed == questionImage
? _value.questionImage ? _value.questionImage
: questionImage // ignore: cast_nullable_to_non_nullable : questionImage // ignore: cast_nullable_to_non_nullable
as Uint8List?, as String?,
answer: answer:
freezed == answer freezed == answer
? _value.answer ? _value.answer
@@ -96,7 +96,7 @@ class _$CreateTicketDtoCopyWithImpl<$Res, $Val extends CreateTicketDto>
freezed == answerImage freezed == answerImage
? _value.answerImage ? _value.answerImage
: answerImage // ignore: cast_nullable_to_non_nullable : answerImage // ignore: cast_nullable_to_non_nullable
as Uint8List?, as String?,
needRevert: needRevert:
freezed == needRevert freezed == needRevert
? _value.needRevert ? _value.needRevert
@@ -120,9 +120,9 @@ abstract class _$$CreateTicketDtoImplCopyWith<$Res>
$Res call({ $Res call({
Collection? collection, Collection? collection,
String? question, String? question,
Uint8List? questionImage, String? questionImage,
String? answer, String? answer,
Uint8List? answerImage, String? answerImage,
bool? needRevert, bool? needRevert,
}); });
} }
@@ -164,7 +164,7 @@ class __$$CreateTicketDtoImplCopyWithImpl<$Res>
freezed == questionImage freezed == questionImage
? _value.questionImage ? _value.questionImage
: questionImage // ignore: cast_nullable_to_non_nullable : questionImage // ignore: cast_nullable_to_non_nullable
as Uint8List?, as String?,
answer: answer:
freezed == answer freezed == answer
? _value.answer ? _value.answer
@@ -174,7 +174,7 @@ class __$$CreateTicketDtoImplCopyWithImpl<$Res>
freezed == answerImage freezed == answerImage
? _value.answerImage ? _value.answerImage
: answerImage // ignore: cast_nullable_to_non_nullable : answerImage // ignore: cast_nullable_to_non_nullable
as Uint8List?, as String?,
needRevert: needRevert:
freezed == needRevert freezed == needRevert
? _value.needRevert ? _value.needRevert
@@ -202,11 +202,11 @@ class _$CreateTicketDtoImpl implements _CreateTicketDto {
@override @override
final String? question; final String? question;
@override @override
final Uint8List? questionImage; final String? questionImage;
@override @override
final String? answer; final String? answer;
@override @override
final Uint8List? answerImage; final String? answerImage;
@override @override
final bool? needRevert; final bool? needRevert;
@@ -226,15 +226,11 @@ class _$CreateTicketDtoImpl implements _CreateTicketDto {
) && ) &&
(identical(other.question, question) || (identical(other.question, question) ||
other.question == question) && other.question == question) &&
const DeepCollectionEquality().equals( (identical(other.questionImage, questionImage) ||
other.questionImage, other.questionImage == questionImage) &&
questionImage,
) &&
(identical(other.answer, answer) || other.answer == answer) && (identical(other.answer, answer) || other.answer == answer) &&
const DeepCollectionEquality().equals( (identical(other.answerImage, answerImage) ||
other.answerImage, other.answerImage == answerImage) &&
answerImage,
) &&
(identical(other.needRevert, needRevert) || (identical(other.needRevert, needRevert) ||
other.needRevert == needRevert)); other.needRevert == needRevert));
} }
@@ -244,9 +240,9 @@ class _$CreateTicketDtoImpl implements _CreateTicketDto {
runtimeType, runtimeType,
const DeepCollectionEquality().hash(collection), const DeepCollectionEquality().hash(collection),
question, question,
const DeepCollectionEquality().hash(questionImage), questionImage,
answer, answer,
const DeepCollectionEquality().hash(answerImage), answerImage,
needRevert, needRevert,
); );
@@ -266,9 +262,9 @@ abstract class _CreateTicketDto implements CreateTicketDto {
const factory _CreateTicketDto({ const factory _CreateTicketDto({
final Collection? collection, final Collection? collection,
final String? question, final String? question,
final Uint8List? questionImage, final String? questionImage,
final String? answer, final String? answer,
final Uint8List? answerImage, final String? answerImage,
final bool? needRevert, final bool? needRevert,
}) = _$CreateTicketDtoImpl; }) = _$CreateTicketDtoImpl;
@@ -277,11 +273,11 @@ abstract class _CreateTicketDto implements CreateTicketDto {
@override @override
String? get question; String? get question;
@override @override
Uint8List? get questionImage; String? get questionImage;
@override @override
String? get answer; String? get answer;
@override @override
Uint8List? get answerImage; String? get answerImage;
@override @override
bool? get needRevert; bool? get needRevert;

View File

@@ -2,7 +2,6 @@
// //
// final collectionDto = collectionDtoFromJson(jsonString); // final collectionDto = collectionDtoFromJson(jsonString);
import 'dart:convert';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
@@ -14,6 +13,6 @@ abstract class CrudCollectionDto with _$CrudCollectionDto {
required String desc, required String desc,
required String title, required String title,
required bool isPublic, required bool isPublic,
Uint8List? avatar, String? avatar,
}) = _CrudCollectionDto; }) = _CrudCollectionDto;
} }

View File

@@ -20,7 +20,7 @@ mixin _$CrudCollectionDto {
String get desc => throw _privateConstructorUsedError; String get desc => throw _privateConstructorUsedError;
String get title => throw _privateConstructorUsedError; String get title => throw _privateConstructorUsedError;
bool get isPublic => throw _privateConstructorUsedError; bool get isPublic => throw _privateConstructorUsedError;
Uint8List? get avatar => throw _privateConstructorUsedError; String? get avatar => throw _privateConstructorUsedError;
/// Create a copy of CrudCollectionDto /// Create a copy of CrudCollectionDto
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@@ -36,7 +36,7 @@ abstract class $CrudCollectionDtoCopyWith<$Res> {
$Res Function(CrudCollectionDto) then, $Res Function(CrudCollectionDto) then,
) = _$CrudCollectionDtoCopyWithImpl<$Res, CrudCollectionDto>; ) = _$CrudCollectionDtoCopyWithImpl<$Res, CrudCollectionDto>;
@useResult @useResult
$Res call({String desc, String title, bool isPublic, Uint8List? avatar}); $Res call({String desc, String title, bool isPublic, String? avatar});
} }
/// @nodoc /// @nodoc
@@ -80,7 +80,7 @@ class _$CrudCollectionDtoCopyWithImpl<$Res, $Val extends CrudCollectionDto>
freezed == avatar freezed == avatar
? _value.avatar ? _value.avatar
: avatar // ignore: cast_nullable_to_non_nullable : avatar // ignore: cast_nullable_to_non_nullable
as Uint8List?, as String?,
) )
as $Val, as $Val,
); );
@@ -96,7 +96,7 @@ abstract class _$$CrudCollectionDtoImplCopyWith<$Res>
) = __$$CrudCollectionDtoImplCopyWithImpl<$Res>; ) = __$$CrudCollectionDtoImplCopyWithImpl<$Res>;
@override @override
@useResult @useResult
$Res call({String desc, String title, bool isPublic, Uint8List? avatar}); $Res call({String desc, String title, bool isPublic, String? avatar});
} }
/// @nodoc /// @nodoc
@@ -139,7 +139,7 @@ class __$$CrudCollectionDtoImplCopyWithImpl<$Res>
freezed == avatar freezed == avatar
? _value.avatar ? _value.avatar
: avatar // ignore: cast_nullable_to_non_nullable : avatar // ignore: cast_nullable_to_non_nullable
as Uint8List?, as String?,
), ),
); );
} }
@@ -162,7 +162,7 @@ class _$CrudCollectionDtoImpl implements _CrudCollectionDto {
@override @override
final bool isPublic; final bool isPublic;
@override @override
final Uint8List? avatar; final String? avatar;
@override @override
String toString() { String toString() {
@@ -178,17 +178,11 @@ class _$CrudCollectionDtoImpl implements _CrudCollectionDto {
(identical(other.title, title) || other.title == title) && (identical(other.title, title) || other.title == title) &&
(identical(other.isPublic, isPublic) || (identical(other.isPublic, isPublic) ||
other.isPublic == isPublic) && other.isPublic == isPublic) &&
const DeepCollectionEquality().equals(other.avatar, avatar)); (identical(other.avatar, avatar) || other.avatar == avatar));
} }
@override @override
int get hashCode => Object.hash( int get hashCode => Object.hash(runtimeType, desc, title, isPublic, avatar);
runtimeType,
desc,
title,
isPublic,
const DeepCollectionEquality().hash(avatar),
);
/// Create a copy of CrudCollectionDto /// Create a copy of CrudCollectionDto
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@@ -207,7 +201,7 @@ abstract class _CrudCollectionDto implements CrudCollectionDto {
required final String desc, required final String desc,
required final String title, required final String title,
required final bool isPublic, required final bool isPublic,
final Uint8List? avatar, final String? avatar,
}) = _$CrudCollectionDtoImpl; }) = _$CrudCollectionDtoImpl;
@override @override
@@ -217,7 +211,7 @@ abstract class _CrudCollectionDto implements CrudCollectionDto {
@override @override
bool get isPublic; bool get isPublic;
@override @override
Uint8List? get avatar; String? get avatar;
/// Create a copy of CrudCollectionDto /// Create a copy of CrudCollectionDto
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.

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,4 +1,5 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'dart:math'; import 'dart:math';
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
@@ -152,7 +153,7 @@ class CollectionDetailScreen extends StatelessWidget {
condition: collection.image != null, condition: collection.image != null,
builder: builder:
(context) => ClipOval( (context) => ClipOval(
child: Image.memory(collection.image!, fit: BoxFit.cover), child: Image.file(File(collection.image!), fit: BoxFit.cover),
), ),
fallback: fallback:
(context) => Center( (context) => Center(

View File

@@ -1,3 +1,5 @@
import 'dart:io';
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -211,20 +213,34 @@ class _Collection extends StatelessWidget {
children: <Widget>[ children: <Widget>[
_buildTitle(), _buildTitle(),
const HSpace(4), const HSpace(4),
Row( FutureBuilder(
children: <Widget>[ future: getIt<AppDatabase>().ticketsDao.getTicketsInCollectionCount(
Assets.icons.typeCards.image( collection.id,
height: 18.h, ),
width: 18.w, builder: (context, snapshot) {
color: AppColors.disabled, if (snapshot.connectionState == ConnectionState.waiting) {
), return SizedBox.square(
const WSpace(2), dimension: 18.r,
AppTypography( child: CircularProgressIndicator(),
'${collection.likesCount.toString()} ${Utils.declOfNum(collection.likesCount, ['карточек', 'карточки', 'карточек'])}', );
type: Regular14px(), }
color: AppColors.disabled,
), return Row(
], children: <Widget>[
Assets.icons.typeCards.image(
height: 18.h,
width: 18.w,
color: AppColors.disabled,
),
const WSpace(2),
AppTypography(
'${snapshot.data.toString()} ${Utils.declOfNum(snapshot.data ?? 0, ['карточек', 'карточки', 'карточек'])}',
type: Regular14px(),
color: AppColors.disabled,
),
],
);
},
), ),
const HSpace(6), const HSpace(6),
const CollectionProgressBar(), const CollectionProgressBar(),
@@ -258,7 +274,7 @@ class _Collection extends StatelessWidget {
condition: collection.image != null, condition: collection.image != null,
builder: builder:
(context) => ClipOval( (context) => ClipOval(
child: Image.memory(collection.image!, fit: BoxFit.cover), child: Image.file(File(collection.image!), fit: BoxFit.cover),
), ),
fallback: fallback:
(context) => Center( (context) => Center(

View File

@@ -12,6 +12,8 @@ import 'package:remever/screens/collections/widgets/collection_card.dart';
import 'package:remever/screens/collections/widgets/collections_app_bar.dart'; import 'package:remever/screens/collections/widgets/collections_app_bar.dart';
import 'package:remever/screens/collections/widgets/collections_filters.dart'; import 'package:remever/screens/collections/widgets/collections_filters.dart';
import 'package:remever/services/collection/collections_interface.dart'; import 'package:remever/services/collection/collections_interface.dart';
import 'package:remever/services/collection/collections_service.dart';
import 'package:remever/services/tickets/tickets_interface.dart';
@RoutePage() @RoutePage()
class CollectionScreen extends StatefulWidget { class CollectionScreen extends StatefulWidget {

View File

@@ -1,4 +1,5 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -11,6 +12,7 @@ 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/database/database.dart';
import 'package:remever/gen/assets.gen.dart'; import 'package:remever/gen/assets.gen.dart';
import 'package:remever/inject.dart';
import 'package:remever/router.gr.dart'; import 'package:remever/router.gr.dart';
import 'package:remever/screens/collections/widgets/collection_progress_bar.dart'; import 'package:remever/screens/collections/widgets/collection_progress_bar.dart';
import 'package:remever/screens/dialogs/action_dialog.dart'; import 'package:remever/screens/dialogs/action_dialog.dart';
@@ -129,10 +131,24 @@ class CollectionCard extends StatelessWidget {
Widget _buildLikeAndCardsLength() { Widget _buildLikeAndCardsLength() {
return Row( return Row(
children: <Widget>[ children: <Widget>[
_buildIconWithText( FutureBuilder(
icon: Assets.icons.typeCards, future: getIt<AppDatabase>().ticketsDao.getTicketsInCollectionCount(
color: AppColors.disabled, collection.id,
text: collection.likesCount.toString(), ),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return SizedBox.square(
dimension: 18.r,
child: CircularProgressIndicator(),
);
}
return _buildIconWithText(
icon: Assets.icons.typeCards,
color: AppColors.disabled,
text: snapshot.data.toString(),
);
},
), ),
const WSpace(8), const WSpace(8),
_buildIconWithText( _buildIconWithText(
@@ -172,7 +188,7 @@ class CollectionCard extends StatelessWidget {
condition: collection.image != null, condition: collection.image != null,
builder: builder:
(context) => ClipOval( (context) => ClipOval(
child: Image.memory(collection.image!, fit: BoxFit.cover), child: Image.file(File(collection.image!), fit: BoxFit.cover),
), ),
fallback: fallback:
(context) => Center( (context) => Center(

View File

@@ -62,7 +62,20 @@ class CollectionsAppBar extends StatelessWidget implements PreferredSizeWidget {
if (!isDebug) return SizedBox(); if (!isDebug) return SizedBox();
return Row( return Row(
children: [ children: [
AppBarIconButton(icon: Assets.icons.typeSearch, onTap: () {}), AppBarIconButton(
icon: Assets.icons.typeSearch,
onTap: () {
context.pushRoute(
CollectionSearchRoute(
onCollectionSelect: (collection) {
context.pushRoute(
CollectionDetailRoute(collection: collection),
);
},
),
);
},
),
AppBarIconButton(icon: Assets.icons.typeDownload, onTap: () {}), AppBarIconButton(icon: Assets.icons.typeDownload, onTap: () {}),
AppBarIconButton( AppBarIconButton(
icon: Assets.icons.typeSort, icon: Assets.icons.typeSort,

View File

@@ -1,12 +1,8 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.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/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/widgets/debug/app_debug.dart';
class CollectionsFilters extends StatefulWidget { class CollectionsFilters extends StatefulWidget {
const CollectionsFilters({super.key}); const CollectionsFilters({super.key});
@@ -16,37 +12,52 @@ class CollectionsFilters extends StatefulWidget {
} }
class _CollectionsFiltersState extends State<CollectionsFilters> { class _CollectionsFiltersState extends State<CollectionsFilters> {
static const List<String> _filterOptions = ['Все', 'Публичные', 'Подписки'];
String _selected = 'Все';
void _onFilterSelected(String title) {
safeSetState(() {
_selected = title;
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AppDebug( return Padding(
builder: (context, isDebug) { padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 28).r,
if (!isDebug) return HSpace(20); child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
return Padding( children: _buildFilterButtons(),
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 28).r, ),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
_buildFilterButton(AppColors.white, 'Все', () {
safeSetState(() {});
}),
_buildFilterButton(AppColors.gray_bg, 'Публичные', () {
safeSetState(() {});
}),
_buildFilterButton(AppColors.gray_bg, 'Подписки', () {
safeSetState(() {});
}),
],
),
);
},
); );
} }
/// List<Widget> _buildFilterButtons() {
/// Построение кнопки фильтра return _filterOptions
/// .map(
Widget _buildFilterButton(Color color, String title, void Function()? onTap) { (title) => _FilterButton(
title: title,
isSelected: _selected == title,
onTap: () => _onFilterSelected(title),
),
)
.toList();
}
}
class _FilterButton extends StatelessWidget {
final String title;
final bool isSelected;
final VoidCallback onTap;
const _FilterButton({
required this.title,
required this.isSelected,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return GestureDetector( return GestureDetector(
onTap: onTap, onTap: onTap,
child: Container( child: Container(
@@ -54,7 +65,7 @@ class _CollectionsFiltersState extends State<CollectionsFilters> {
height: 36.h, height: 36.h,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(16)).r, borderRadius: const BorderRadius.all(Radius.circular(16)).r,
color: color, color: isSelected ? AppColors.white : AppColors.bg,
), ),
child: Center(child: AppTypography(title, type: SemiBold14px())), child: Center(child: AppTypography(title, type: SemiBold14px())),
), ),

View File

@@ -1,3 +1,5 @@
import 'dart:io';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:flutter_slidable/flutter_slidable.dart';
@@ -181,7 +183,7 @@ class _TicketCardState extends State<TicketCard> {
dimension: 64.r, dimension: 64.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(imageBytes!), fit: BoxFit.cover),
), ),
), ),
), ),

View File

@@ -3,6 +3,8 @@ import 'package:auto_route/auto_route.dart';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:image_cropper/image_cropper.dart';
import 'package:path_provider/path_provider.dart';
import 'package:remever/common/functions.dart'; import 'package:remever/common/functions.dart';
import 'package:remever/common/resources.dart'; import 'package:remever/common/resources.dart';
import 'package:remever/common/utils.dart'; import 'package:remever/common/utils.dart';
@@ -21,6 +23,7 @@ import 'package:remever/screens/create_card/widgets/crud_ticket.dart';
import 'package:remever/services/tickets/tickets_interface.dart'; import 'package:remever/services/tickets/tickets_interface.dart';
import 'package:remever/widgets/debug/app_debug.dart'; import 'package:remever/widgets/debug/app_debug.dart';
import 'package:remever/widgets/primary_button.dart'; import 'package:remever/widgets/primary_button.dart';
import 'package:path/path.dart' as path;
@RoutePage() @RoutePage()
class CreateScreen extends StatefulWidget { class CreateScreen extends StatefulWidget {
@@ -34,6 +37,7 @@ class CreateScreen extends StatefulWidget {
class _CreateScreenState extends State<CreateScreen> { class _CreateScreenState extends State<CreateScreen> {
CreateTicketDto _dto = CreateTicketDto(); CreateTicketDto _dto = CreateTicketDto();
String _selectedFilter = 'Запомнить';
// Constants for spacing and dimensions // Constants for spacing and dimensions
static const double _horizontalPadding = 16; static const double _horizontalPadding = 16;
@@ -50,13 +54,45 @@ class _CreateScreenState extends State<CreateScreen> {
showErrorToast('Не удалось получить путь к файлу'); showErrorToast('Не удалось получить путь к файлу');
return; return;
} }
final file = File(filePath);
final bytes = await file.readAsBytes(); // Получаем директорию документов
final Directory directory = await getApplicationDocumentsDirectory();
final String ticketsDirPath = path.join(directory.path, 'tickets');
final Directory ticketsDir = Directory(ticketsDirPath);
// Создаём директорию рекурсивно
if (!(await ticketsDir.exists())) {
await ticketsDir.create(recursive: true);
}
final String fileName = path.basename(filePath);
final String destinationPath = path.join(
ticketsDirPath,
'${DateTime.now()}$fileName',
);
final croppedFile = await ImageCropper().cropImage(
sourcePath: filePath,
aspectRatio: const CropAspectRatio(ratioX: 1, ratioY: 1),
uiSettings: [
AndroidUiSettings(
toolbarTitle: '',
initAspectRatio: CropAspectRatioPreset.square,
lockAspectRatio: true,
),
],
);
if (croppedFile == null) return;
await File(croppedFile.path).copy(destinationPath);
safeSetState(() { safeSetState(() {
_dto = _dto =
isQuestion isQuestion
? _dto.copyWith(questionImage: bytes) ? _dto.copyWith(questionImage: destinationPath)
: _dto.copyWith(answerImage: bytes); : _dto.copyWith(answerImage: destinationPath);
}); });
} }
@@ -284,7 +320,10 @@ class _CreateScreenState extends State<CreateScreen> {
condition: _dto.collection?.image != null, condition: _dto.collection?.image != null,
builder: builder:
(context) => ClipOval( (context) => ClipOval(
child: Image.memory(_dto.collection!.image!, fit: BoxFit.cover), child: Image.file(
File(_dto.collection!.image!),
fit: BoxFit.cover,
),
), ),
fallback: fallback:
(context) => Center( (context) => Center(
@@ -347,22 +386,25 @@ class _CreateScreenState extends State<CreateScreen> {
} }
Widget _filters() { Widget _filters() {
return AppDebug( return Row(
builder: (context, isDebug) { mainAxisAlignment: MainAxisAlignment.center,
if (!isDebug) return const SizedBox(); children: [
return Row( _buildFilterButton('Запомнить', () {
mainAxisAlignment: MainAxisAlignment.center, safeSetState(() {
children: [ _selectedFilter = 'Запомнить';
_buildFilterButton(AppColors.gray_bg, 'Запомнить', () {}), });
const WSpace(10), }),
_buildFilterButton(AppColors.white, 'Держать в фокусе', () {}), const WSpace(10),
], _buildFilterButton('Держать в фокусе', () {
); safeSetState(() {
}, _selectedFilter = 'Держать в фокусе';
});
}),
],
); );
} }
Widget _buildFilterButton(Color color, String title, VoidCallback onTap) { Widget _buildFilterButton(String title, VoidCallback onTap) {
return GestureDetector( return GestureDetector(
onTap: onTap, onTap: onTap,
child: Container( child: Container(
@@ -370,7 +412,7 @@ class _CreateScreenState extends State<CreateScreen> {
height: 36.h, height: 36.h,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16).r, borderRadius: BorderRadius.circular(16).r,
color: color, color: _selectedFilter == title ? AppColors.white : AppColors.gray_bg,
), ),
child: Center(child: AppTypography(title, type: SemiBold14px())), child: Center(child: AppTypography(title, type: SemiBold14px())),
), ),

View File

@@ -1,3 +1,5 @@
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:readmore/readmore.dart'; import 'package:readmore/readmore.dart';
import 'package:remever/common/resources.dart'; import 'package:remever/common/resources.dart';
@@ -100,8 +102,8 @@ class CrudTicket extends StatelessWidget {
builder: (context) { builder: (context) {
return ClipRRect( return ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)).r, borderRadius: const BorderRadius.all(Radius.circular(8)).r,
child: Image.memory( child: Image.file(
isQuestion ? dto.questionImage! : dto.answerImage!, File(isQuestion ? dto.questionImage! : dto.answerImage!),
fit: BoxFit.cover, fit: BoxFit.cover,
), ),
); );

View File

@@ -1,10 +1,12 @@
import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:typed_data';
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:image_cropper/image_cropper.dart';
import 'package:path_provider/path_provider.dart';
import 'package:remever/common/functions.dart'; import 'package:remever/common/functions.dart';
import 'package:remever/common/resources.dart'; import 'package:remever/common/resources.dart';
import 'package:remever/common/widgets/bottom_safe_space.dart'; import 'package:remever/common/widgets/bottom_safe_space.dart';
@@ -24,6 +26,7 @@ import 'package:remever/services/collection/collections_interface.dart';
import 'package:remever/widgets/primary_button.dart'; import 'package:remever/widgets/primary_button.dart';
import '../../../components/extensions/state.dart'; import '../../../components/extensions/state.dart';
import 'package:path/path.dart' as path;
@RoutePage() @RoutePage()
class CrudCollectionScreen extends StatefulWidget { class CrudCollectionScreen extends StatefulWidget {
@@ -36,88 +39,119 @@ class CrudCollectionScreen extends StatefulWidget {
} }
class _CrudCollectionScreenState extends State<CrudCollectionScreen> { class _CrudCollectionScreenState extends State<CrudCollectionScreen> {
/// Флаг публичности коллекции late CrudCollectionDto _collection;
bool _isPublic = false; bool _isPublic = false;
CrudCollectionDto? _collection;
/// Смена публичности
void _setPublic(bool public) {
_collection = _collection?.copyWith(isPublic: public);
safeSetState(() => _isPublic = public);
}
@override @override
void initState() { void initState() {
super.initState();
_initializeCollection();
}
void _initializeCollection() {
_collection = CrudCollectionDto( _collection = CrudCollectionDto(
desc: widget.editedCollection?.desc ?? '', desc: widget.editedCollection?.desc ?? '',
title: widget.editedCollection?.title ?? '', title: widget.editedCollection?.title ?? '',
isPublic: widget.editedCollection?.isPublic ?? false, isPublic: widget.editedCollection?.isPublic ?? false,
avatar: widget.editedCollection?.image, avatar: widget.editedCollection?.image,
); );
_isPublic = _collection.isPublic;
super.initState();
} }
void _pickImage() async { Future<void> _pickImage() async {
final result = await FilePicker.platform.pickFiles(); final result = await FilePicker.platform.pickFiles();
if (result == null || result.files.isEmpty) { if (result?.files.single.path case final String? originPath?) {
try {
// Получаем директорию документов
final Directory directory = await getApplicationDocumentsDirectory();
final String collectionsDirPath = path.join(
directory.path,
'collections',
);
final Directory collectionsDir = Directory(collectionsDirPath);
// Создаём директорию рекурсивно
if (!(await collectionsDir.exists())) {
await collectionsDir.create(recursive: true);
}
final String fileName = path.basename(originPath!);
final String destinationPath = path.join(
collectionsDirPath,
'${DateTime.now()}$fileName',
);
final croppedFile = await ImageCropper().cropImage(
sourcePath: originPath,
aspectRatio: const CropAspectRatio(ratioX: 1, ratioY: 1),
uiSettings: [
AndroidUiSettings(
toolbarTitle: '',
initAspectRatio: CropAspectRatioPreset.square,
lockAspectRatio: true,
),
],
);
if (croppedFile == null) return;
await File(croppedFile.path).copy(destinationPath);
_updateCollection(avatar: destinationPath);
} catch (e) {
showErrorToast('Не удалось загрузить изображение');
}
} else {
showErrorToast('Файл не выбран'); showErrorToast('Файл не выбран');
return;
} }
}
final filePath = result.files.single.path; void _updateCollection({
String? title,
if (filePath == null) { String? desc,
showErrorToast('Не удалось получить путь к файлу'); bool? isPublic,
String? avatar,
return; }) {
} _collection = _collection.copyWith(
title: title ?? _collection.title,
final file = File(filePath); desc: desc ?? _collection.desc,
final bytes = await file.readAsBytes(); isPublic: isPublic ?? _collection.isPublic,
avatar: avatar ?? _collection.avatar,
// final base64String = base64Encode(bytes); );
_collection = _collection?.copyWith(avatar: bytes);
safeSetState(() {}); safeSetState(() {});
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
logger.logBuild('build create screen');
return Scaffold( return Scaffold(
backgroundColor: AppColors.gray_bg, backgroundColor: AppColors.gray_bg,
appBar: _buildAppBar(context), appBar: _buildAppBar(),
body: _buildMainBody(context), body: _buildMainBody(),
); );
} }
/// Основное тело экрана Widget _buildMainBody() {
Widget _buildMainBody(BuildContext context) {
return Padding( return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16).r, padding: const EdgeInsets.symmetric(horizontal: 16).r,
child: SingleChildScrollView( child: SingleChildScrollView(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: [
const HSpace(16), const HSpace(16),
_buildPhotoAndTitle(context), _buildPhotoAndTitle(),
const HSpace(16), const HSpace(16),
..._buildDescription(context), ..._buildDescription(),
const HSpace(16), const HSpace(16),
// _buildPublickSwitch(), // _buildPublicSwitch(),
const HSpace(16), const HSpace(16),
AnimatedOpacity( AnimatedOpacity(
// opacity: _isPublic ? 1 : 0, // opacity: _isPublic ? 1 : 0,
opacity: 0, opacity: 0,
duration: const Duration(seconds: 1), duration: const Duration(milliseconds: 300),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: [
..._buildTagButton(), ..._buildTagButton(),
const HSpace(16), const HSpace(16),
_buildTagsList(), _buildTagsList(),
@@ -125,7 +159,7 @@ class _CrudCollectionScreenState extends State<CrudCollectionScreen> {
], ],
), ),
), ),
_buildCreateBtn(context), _buildCreateBtn(),
const BottomSafeSpace(), const BottomSafeSpace(),
], ],
), ),
@@ -133,30 +167,10 @@ class _CrudCollectionScreenState extends State<CrudCollectionScreen> {
); );
} }
///Кнопка создания Widget _buildCreateBtn() {
Widget _buildCreateBtn(BuildContext context) {
return PrimaryButton( return PrimaryButton(
height: 52, height: 52,
onTap: () async { onTap: _handleCreateOrUpdate,
if (_collection!.desc.isEmpty && _collection!.title.isEmpty) {
showErrorToast(
'Для создания публичной коллекции добавьте описание и тэги',
);
return;
}
widget.editedCollection != null
? await getIt<CollectionsInterface>().updateCollection(
_collection!,
widget.editedCollection!.id,
)
: await getIt<CollectionsInterface>().createCollection(
_collection!,
);
context.back();
},
color: AppColors.primary, color: AppColors.primary,
child: AppTypography( child: AppTypography(
widget.editedCollection == null widget.editedCollection == null
@@ -168,94 +182,124 @@ class _CrudCollectionScreenState extends State<CrudCollectionScreen> {
); );
} }
/// Построение списка тегов Future<void> _handleCreateOrUpdate() async {
if (!_isCollectionValid()) return;
if (!_hasChanges()) {
context.back();
}
try {
final collectionService = getIt<CollectionsInterface>();
widget.editedCollection != null
? await collectionService.updateCollection(
_collection,
widget.editedCollection!.id,
)
: await collectionService.createCollection(_collection);
context.back();
} catch (e) {
showErrorToast(
'Ошибка при ${widget.editedCollection != null ? 'обновлении' : 'создании'} коллекции',
);
}
}
bool _isCollectionValid() {
if (_collection.title.isEmpty && _collection.desc.isEmpty) {
showErrorToast('Для создания коллекции добавьте название и описание');
return false;
}
if (_isPublic && _collection.desc.isEmpty) {
showErrorToast(
'Для создания публичной коллекции добавьте описание и тэги',
);
return false;
}
return true;
}
Widget _buildTagsList() { Widget _buildTagsList() {
return SizedBox( return SizedBox(
height: 68.h, height: 68.h,
child: Row( child: Row(
children: <Widget>[ children: [
Expanded( Expanded(
child: Wrap( child: Wrap(
runSpacing: 8.r, runSpacing: 8.r,
spacing: 8.r, spacing: 8.r,
children: List<Widget>.generate(6, (int index) { children: List.generate(6, _buildTagItem),
return GestureDetector(
onTap: () {},
child: Container(
height: 30,
decoration: BoxDecoration(
borderRadius:
const BorderRadius.all(Radius.circular(6)).r,
color: const Color(0xFFFFE4E6),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
AppTypography(
'tag $index',
type: Regular14px(),
height: 0.95,
color: AppColors.danger,
),
const WSpace(8),
Center(
child: Icon(
Icons.close,
size: 14.r,
color: AppColors.danger,
),
),
],
),
),
),
);
}),
), ),
), ),
const WSpace(9), const WSpace(9),
GestureDetector( _buildAddTagButton(),
onTap: () {
showCuperModalBottomSheet(
context: context,
height: 270.h,
builder: (BuildContext context) => const TagsDialog(),
);
},
child: AppTypography(
'+13',
type: Medium16px(),
color: AppColors.primary,
),
),
], ],
), ),
); );
} }
/// Построение кнопки добавления тега Widget _buildTagItem(int index) {
return GestureDetector(
onTap: () {},
child: Container(
height: 30,
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(6)).r,
color: const Color(0xFFFFE4E6),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
AppTypography(
'tag $index',
type: Regular14px(),
height: 0.95,
color: AppColors.danger,
),
const WSpace(8),
Icon(Icons.close, size: 14.r, color: AppColors.danger),
],
),
),
),
);
}
Widget _buildAddTagButton() {
return GestureDetector(
onTap: _showTagsDialog,
child: AppTypography('+13', type: Medium16px(), color: AppColors.primary),
);
}
void _showTagsDialog() {
showCuperModalBottomSheet(
context: context,
height: 270.h,
builder: (_) => const TagsDialog(),
);
}
List<Widget> _buildTagButton() { List<Widget> _buildTagButton() {
return <Widget>[ return [
AppTypography('Тэги', type: SemiBold14px()), AppTypography('Тэги', type: SemiBold14px()),
const HSpace(4), const HSpace(4),
CrudCollectionField( CrudCollectionField(height: 42, width: 348, hint: 'Добавить тэг'),
height: 42,
width: 348,
hint: 'Добавить тэг',
// onTap: () => context.pushRoute(const AddTagsRoute()),
),
]; ];
} }
/// Построение свитчера на публичность коллекции Widget _buildPublicSwitch() {
Widget _buildPublickSwitch() {
return GestureDetector( return GestureDetector(
onTap: () => _setPublic(!_isPublic), onTap: _togglePublic,
child: Row( child: Row(
children: <Widget>[ children: [
SizedBox.square( SizedBox.square(
dimension: 20.r, dimension: 20.r,
child: Assets.icons.typePublic.image(color: AppColors.primary), child: Assets.icons.typePublic.image(color: AppColors.primary),
@@ -287,135 +331,134 @@ class _CrudCollectionScreenState extends State<CrudCollectionScreen> {
); );
} }
/// Построение блока с описанием void _togglePublic() => _setPublic(!_isPublic);
List<Widget> _buildDescription(BuildContext context) {
return <Widget>[ void _setPublic(bool isPublic) {
_updateCollection(isPublic: isPublic);
safeSetState(() => _isPublic = isPublic);
}
List<Widget> _buildDescription() {
return [
AppTypography('Описание', type: SemiBold14px()), AppTypography('Описание', type: SemiBold14px()),
const HSpace(4), const HSpace(4),
CrudCollectionField( CrudCollectionField(
height: 110, height: 110,
width: 348, width: 348,
hint: 'Добавить описание', hint: 'Добавить описание',
content: _collection?.desc, content: _collection.desc,
onTap: () { onTap:
context.pushRoute( () => _navigateToFullscreenField(
CrudCollectionFullscreenField(
title: 'Описание', title: 'Описание',
height: 333, height: 333,
content: _collection?.desc, content: _collection.desc,
onEditingComplete: (res) { onResult: (result) => _updateCollection(desc: result ?? ''),
safeSetState(
() => _collection = _collection?.copyWith(desc: res ?? ''),
);
},
), ),
);
},
), ),
]; ];
} }
/// Построение блока фото и заголовка Widget _buildTitle() {
Widget _buildPhotoAndTitle(BuildContext context) {
return Row(
children: <Widget>[_buildPhoto(), const WSpace(8), _buildTitle(context)],
);
}
/// Построение поля для ввода заголовка
Widget _buildTitle(BuildContext context) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, children: [
children: <Widget>[
AppTypography('Название', type: SemiBold14px()), AppTypography('Название', type: SemiBold14px()),
const HSpace(4), const HSpace(4),
CrudCollectionField( CrudCollectionField(
height: 91, height: 91,
width: 225, width: 225,
hint: 'Добавить название', hint: 'Добавить название',
content: _collection?.title, content: _collection.title,
onTap: () { onTap:
context.pushRoute( () => _navigateToFullscreenField(
CrudCollectionFullscreenField(
title: 'Название', title: 'Название',
hint: 'Максимальное количество символов - 250', hint: 'Максимальное количество символов - 250',
content: _collection?.title, content: _collection.title,
onEditingComplete: (res) { onResult: (result) => _updateCollection(title: result ?? ''),
safeSetState(
() => _collection = _collection?.copyWith(title: res ?? ''),
);
},
), ),
);
},
), ),
], ],
); );
} }
/// Построение обложки void _navigateToFullscreenField({
required String title,
String? hint,
String? content,
required Function(String?) onResult,
double height = 91,
}) {
context.pushRoute(
CrudCollectionFullscreenField(
title: title,
hint: hint,
height: height,
content: content,
onEditingComplete: onResult,
),
);
}
Widget _buildPhotoAndTitle() {
return Row(
children: [
_buildPhoto(),
const WSpace(8),
Expanded(child: _buildTitle()),
],
);
}
Widget _buildPhoto() { Widget _buildPhoto() {
return GestureDetector( return GestureDetector(
onTap: () => _pickImage(), onTap: _pickImage,
child: SizedBox.square( child: SizedBox.square(
dimension: 115.r, dimension: 115.r,
child: DecoratedBox( child: DecoratedBox(
decoration: const BoxDecoration( decoration: const BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
gradient: LinearGradient( gradient: LinearGradient(
colors: <Color>[Color(0xFFB6AAFE), Color(0xFFDBD7F4)], colors: [Color(0xFFB6AAFE), Color(0xFFDBD7F4)],
begin: Alignment.bottomLeft, begin: Alignment.bottomLeft,
end: Alignment.topRight, end: Alignment.topRight,
), ),
), ),
child: Wif( child: Wif(
condition: _collection!.avatar != null, condition: _collection.avatar != null,
builder: builder:
(context) => ClipOval( (_) => ClipOval(
child: Image.memory(_collection!.avatar!, fit: BoxFit.cover), child: Image.file(
), File(_collection.avatar!),
fallback: fit: BoxFit.cover,
(context) => SizedBox.square( errorBuilder: (_, __, ___) => _buildPhotoPlaceholder(),
dimension: 32.r,
child: Center(
child: Assets.icons.typePhoto.image(
height: 32.h,
width: 32.w,
color: AppColors.primary,
),
), ),
), ),
fallback: (_) => _buildPhotoPlaceholder(),
), ),
), ),
), ),
); );
} }
/// Построение шапки Widget _buildPhotoPlaceholder() {
AppBar _buildAppBar(BuildContext context) { return SizedBox.square(
dimension: 32.r,
child: Center(
child: Assets.icons.typePhoto.image(
height: 32.h,
width: 32.w,
color: AppColors.primary,
),
),
);
}
AppBar _buildAppBar() {
return AppBar( return AppBar(
toolbarHeight: 56.h, toolbarHeight: 56.h,
backgroundColor: AppColors.white, backgroundColor: AppColors.white,
shadowColor: Colors.transparent, shadowColor: Colors.transparent,
leading: IconButton( leading: IconButton(
onPressed: () async { onPressed: _handleBackPress,
if (widget.editedCollection != null) {
final bool? res = await showCuperModalBottomSheet(
context: context,
height: 262.h,
builder:
(BuildContext context) => const AlertInfoDialog(
title: 'Вы хотите сбросить все внесенные изменения?',
acceptTitle: 'Да, сбросить',
declineTitle: 'Нет, оставить',
),
);
if (res != null && res) context.back();
} else {
context.back();
}
},
icon: const Icon(CupertinoIcons.left_chevron, color: Colors.black), icon: const Icon(CupertinoIcons.left_chevron, color: Colors.black),
), ),
centerTitle: true, centerTitle: true,
@@ -429,35 +472,66 @@ class _CrudCollectionScreenState extends State<CrudCollectionScreen> {
color: AppColors.body_text, color: AppColors.body_text,
), ),
), ),
actions: <Widget>[ actions: [
Wif( if (widget.editedCollection != null && _hasChanges())
condition: widget.editedCollection != null, Padding(
builder: (BuildContext context) { padding: const EdgeInsets.only(right: 16).r,
return Padding( child: GestureDetector(
padding: const EdgeInsets.only(right: 16).r, onTap: _showResetDialog,
child: GestureDetector( child: Assets.icons.typeTrash.image(
onTap: () { height: 24.h,
showCuperModalBottomSheet( width: 24.w,
context: context, color: AppColors.danger,
height: 262.h,
builder:
(BuildContext context) => const AlertInfoDialog(
title: 'Вы хотите сбросить все внесенные изменения?',
acceptTitle: 'Да, сбросить',
declineTitle: 'Нет, оставить',
),
);
},
child: Assets.icons.typeTrash.image(
height: 24.h,
width: 24.w,
color: AppColors.primary,
),
), ),
); ),
}, ),
),
], ],
); );
} }
Future<void> _handleBackPress() async {
if (widget.editedCollection != null) {
final shouldExit = await _showExitDialog();
if (shouldExit == true) context.back();
} else {
context.back();
}
}
Future<bool?> _showExitDialog() async {
// Показываем диалог только если есть редактируемая коллекция и есть изменения
if (widget.editedCollection != null && _hasChanges()) {
return showCuperModalBottomSheet<bool>(
context: context,
height: 262.h,
builder:
(_) => const AlertInfoDialog(
title: 'Вы хотите сбросить все внесенные изменения?',
acceptTitle: 'Да, сбросить',
declineTitle: 'Нет, оставить',
),
);
}
return true;
}
bool _hasChanges() {
// Если нет редактируемой коллекции, значит это создание новой
if (widget.editedCollection == null) return false;
// Сравниваем все поля
return _collection.title != widget.editedCollection!.title ||
_collection.desc != widget.editedCollection!.desc ||
_collection.isPublic != widget.editedCollection!.isPublic ||
_collection.avatar != widget.editedCollection!.image;
}
void _showResetDialog() {
_showExitDialog().then((result) {
if (result == true) {
_initializeCollection();
safeSetState(() {});
}
});
}
} }

View File

@@ -39,10 +39,10 @@ class _CrudCollectionFullscreenFieldState
@override @override
void initState() { void initState() {
super.initState();
if (widget.content != null) { if (widget.content != null) {
_controller.text = widget.content!; _controller.text = widget.content!;
} }
super.initState();
} }
@override @override
@@ -58,15 +58,14 @@ class _CrudCollectionFullscreenFieldState
top: false, top: false,
child: Scaffold( child: Scaffold(
backgroundColor: AppColors.gray_bg, backgroundColor: AppColors.gray_bg,
appBar: _buildAppBar(context), appBar: _buildAppBar(),
body: _buildMainBody(context), body: _buildMainBody(),
), ),
), ),
); );
} }
/// Построение основного тела экрана Widget _buildMainBody() {
Widget _buildMainBody(BuildContext context) {
return Stack( return Stack(
children: [ children: [
Padding( Padding(
@@ -77,7 +76,7 @@ class _CrudCollectionFullscreenFieldState
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const HSpace(16), const HSpace(16),
_buildField(context), _buildField(),
if (widget.hint != null) ...[ if (widget.hint != null) ...[
const HSpace(16), const HSpace(16),
AppTypography( AppTypography(
@@ -96,12 +95,10 @@ class _CrudCollectionFullscreenFieldState
); );
} }
/// Построение интерактивной плашки меню
Widget _buildMenu() { Widget _buildMenu() {
return Consumer<ScreenHeight>( return Consumer<ScreenHeight>(
builder: (context, screenHeight, _) { builder: (_, screenHeight, __) {
return AnimatedOpacity( return AnimatedOpacity(
// opacity: screenHeight.isOpen ? 1 : 0,
opacity: 1, opacity: 1,
duration: const Duration(milliseconds: 500), duration: const Duration(milliseconds: 500),
child: Container( child: Container(
@@ -126,7 +123,6 @@ class _CrudCollectionFullscreenFieldState
); );
} }
/// Кнопка "Вставить из буфера обмена"
Widget _buildPasteButton() { Widget _buildPasteButton() {
return GestureDetector( return GestureDetector(
onTap: _onPasteTap, onTap: _onPasteTap,
@@ -134,22 +130,20 @@ class _CrudCollectionFullscreenFieldState
); );
} }
/// Обработка нажатия на кнопку "Вставить" Future<void> _onPasteTap() async {
void _onPasteTap() async {
try { try {
final ClipboardData? data = await Clipboard.getData('text/plain'); final data = await Clipboard.getData(Clipboard.kTextPlain);
if (data?.text == null || data!.text!.isEmpty) { if (data?.text?.isEmpty ?? true) {
showErrorToast('Не удалось получить текст из буфера обмена'); showErrorToast('Не удалось получить текст из буфера обмена');
return; return;
} }
_controller.text += ' ${data.text}'; _controller.text += ' ${data!.text}';
showSuccessToast('Текст вставлен из буфера обмена'); showSuccessToast('Текст вставлен из буфера обмена');
} catch (e) { } catch (e) {
showErrorToast('Ошибка при вставке текста: $e'); showErrorToast('Ошибка при вставке текста: $e');
} }
} }
/// Кнопка "Скопировать в буфер обмена"
Widget _buildCopyButton() { Widget _buildCopyButton() {
return GestureDetector( return GestureDetector(
onTap: _onCopyTap, onTap: _onCopyTap,
@@ -157,8 +151,7 @@ class _CrudCollectionFullscreenFieldState
); );
} }
/// Обработка нажатия на кнопку "Копировать" Future<void> _onCopyTap() async {
void _onCopyTap() async {
if (_controller.text.isEmpty) { if (_controller.text.isEmpty) {
showErrorToast('Нет содержимого для отправки в буфер обмена'); showErrorToast('Нет содержимого для отправки в буфер обмена');
return; return;
@@ -171,45 +164,31 @@ class _CrudCollectionFullscreenFieldState
} }
} }
/// Кнопка "Подтвердить"
Widget _buildSubmitButton() { Widget _buildSubmitButton() {
return GestureDetector( return GestureDetector(
onTap: _onSubmitTap, onTap: _onSubmitTap,
child: SizedBox.square( child: SizedBox.square(
dimension: 32.r, dimension: 32.r,
child: DecoratedBox( child: const DecoratedBox(
decoration: const BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: AppColors.primary, color: AppColors.primary,
), ),
child: Center( child: Center(
child: Assets.icons.typeCheck.image( child: Icon(Icons.check, color: AppColors.white, size: 24),
height: 24.h,
width: 24.w,
color: AppColors.white,
),
), ),
), ),
), ),
); );
} }
/// Обработка нажатия на кнопку "Подтвердить"
void _onSubmitTap() { void _onSubmitTap() {
if (_controller.text.isEmpty) {
showErrorToast(
'Для создания публичной коллекции добавьте описание и тэги',
);
return;
}
widget.onEditingComplete(_controller.text); widget.onEditingComplete(_controller.text);
context.back(); context.back();
} }
/// Построение поля ввода Widget _buildField() {
Widget _buildField(BuildContext context) {
return SizedBox( return SizedBox(
height: widget.height.h, height: widget.height.h,
child: DecoratedBox( child: DecoratedBox(
@@ -236,30 +215,26 @@ class _CrudCollectionFullscreenFieldState
); );
} }
/// Построение шапки AppBar _buildAppBar() {
AppBar _buildAppBar(BuildContext context) {
return AppBar( return AppBar(
toolbarHeight: 56.h, toolbarHeight: 56.h,
backgroundColor: AppColors.white, backgroundColor: AppColors.white,
shadowColor: Colors.transparent, shadowColor: Colors.transparent,
leading: IconButton( leading: IconButton(
onPressed: () => _handleBackPress(context), onPressed: () => _handleBackPress(),
icon: const Icon(CupertinoIcons.left_chevron, color: Colors.black), icon: const Icon(CupertinoIcons.left_chevron, color: Colors.black),
), ),
centerTitle: true, centerTitle: true,
title: GestureDetector( title: AppTypography(
// onLongPress: () => _showExitDialog(context), widget.title,
child: AppTypography( type: SemiBold20px(),
widget.title, color: AppColors.body_text,
type: SemiBold20px(),
color: AppColors.body_text,
),
), ),
actions: [ actions: [
Padding( Padding(
padding: const EdgeInsets.only(right: 16).r, padding: const EdgeInsets.only(right: 16).r,
child: GestureDetector( child: GestureDetector(
onTap: () => _showResetDialog(context), onTap: _showResetDialog,
child: Assets.icons.typeTrash.image( child: Assets.icons.typeTrash.image(
height: 24.h, height: 24.h,
width: 24.w, width: 24.w,
@@ -271,39 +246,46 @@ class _CrudCollectionFullscreenFieldState
); );
} }
/// Обработка нажатия на кнопку "Назад" Future<void> _handleBackPress() async {
void _handleBackPress(BuildContext context) async { final shouldExit = await _showExitDialog();
// final bool? shouldExit = await _showExitDialog(context); if (shouldExit ?? false) {
// if (shouldExit ?? false) { context.back();
context.back(); }
// }
} }
/// Показать диалог выхода Future<bool?> _showExitDialog() async {
Future<bool?> _showExitDialog(BuildContext context) async { final res = await showCuperModalBottomSheet<bool>(
return showCuperModalBottomSheet(
context: context, context: context,
height: 262.h, height: 262.h,
builder: builder:
(context) => const AlertInfoDialog( (_) => const AlertInfoDialog(
title: 'Вы хотите выйти из режима создания описания коллекции?', title: 'У вас есть несохраненные изменения',
acceptTitle: 'Выйти, не сохранять', acceptTitle: 'Выйти',
declineTitle: 'Сохранить и выйти', declineTitle: 'Сохранить и выйти',
), ),
); );
if (res == null) return false;
if (res) return true;
widget.onEditingComplete(_controller.text);
return true;
} }
/// Показать диалог сброса Future<void> _showResetDialog() async {
void _showResetDialog(BuildContext context) { final res = await showCuperModalBottomSheet<bool>(
// showCuperModalBottomSheet( context: context,
// context: context, height: 262.h,
// height: 262.h, builder:
// builder: (_) => AlertInfoDialog(
// (context) => const AlertInfoDialog( title: 'Удалить вcе содержимое поля "${widget.title}"?',
// title: 'Вы хотите сбросить все внесенные изменения?', acceptTitle: 'Удалить',
// acceptTitle: 'Да, сбросить', declineTitle: 'Отменить',
// declineTitle: 'Нет, оставить', ),
// ), );
// );
if (res == true) {
_controller.clear();
}
} }
} }

View File

@@ -1,4 +1,5 @@
import 'dart:convert'; import 'dart:convert';
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';
@@ -94,7 +95,7 @@ class InfoDialog extends StatelessWidget {
condition: collection.image != null, condition: collection.image != null,
builder: builder:
(context) => ClipOval( (context) => ClipOval(
child: Image.memory(collection.image!, fit: BoxFit.cover), child: Image.file(File(collection.image!), fit: BoxFit.cover),
), ),
fallback: fallback:
(context) => Center( (context) => Center(

View File

@@ -1,3 +1,5 @@
import 'dart:io';
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:remever/common/functions.dart'; import 'package:remever/common/functions.dart';
@@ -217,7 +219,7 @@ class _ReplaceDialogState extends State<ReplaceDialog> {
condition: collection.image != null, condition: collection.image != null,
builder: builder:
(context) => ClipOval( (context) => ClipOval(
child: Image.memory(collection.image!, fit: BoxFit.cover), child: Image.file(File(collection.image!), fit: BoxFit.cover),
), ),
fallback: fallback:
(context) => Center( (context) => Center(

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

@@ -5,6 +5,10 @@ import 'package:remever/models/crud_collection_dto.dart';
/// Интерфейс взаимодействия с коллекциями /// Интерфейс взаимодействия с коллекциями
/// ///
abstract interface class CollectionsInterface { abstract interface class CollectionsInterface {
/// работа с api
Future<void> getCollectionsFromApi();
Future<void> createCollectionApi();
/// Получение списка коллекций /// Получение списка коллекций
Stream<List<Collection>> watchCollectionsList({String? search}); Stream<List<Collection>> watchCollectionsList({String? search});

View File

@@ -1,4 +1,6 @@
import 'package:dio/dio.dart';
import 'package:injectable/injectable.dart'; import 'package:injectable/injectable.dart';
import 'package:remever/common/services/api_client.dart';
import 'package:remever/database/database.dart'; import 'package:remever/database/database.dart';
import 'package:remever/inject.dart'; import 'package:remever/inject.dart';
import 'package:remever/models/crud_collection_dto.dart'; import 'package:remever/models/crud_collection_dto.dart';
@@ -35,4 +37,37 @@ final class CollectionsService implements CollectionsInterface {
// TODO: implement makeCollectionPublic // TODO: implement makeCollectionPublic
throw UnimplementedError(); throw UnimplementedError();
} }
@override
Future<void> getCollectionsFromApi() async {
try {
final Response<dynamic> response = await apiClient.get(
'/collections',
queryParameters: <String, dynamic>{'perPage': 20, 'page': 1},
);
print('data');
} catch (e) {
print('Response error $e');
}
}
@override
Future<void> createCollectionApi() async {
try {
final Response<dynamic> response = await apiClient.post(
'/collections',
data: {
"title": "Основы программирования для утюгов",
"description":
"Коллекция карточек по основам программирования для начинающих",
"is_public": true,
},
);
print('data');
} catch (e) {
print('Response error $e');
}
}
} }

View File

@@ -5,6 +5,10 @@ import 'package:remever/models/create_ticket_dto.dart';
/// Интерфейс взаимодействия с билетами в коллекция /// Интерфейс взаимодействия с билетами в коллекция
/// ///
abstract interface class TicketsInterface { abstract interface class TicketsInterface {
/// работа с api
Future<void> getTicketsFromApi();
Future<void> createTicketApi();
/// Получение списка билетов /// Получение списка билетов
Stream<List<Ticket>> watchTicketsList(String collectionId); Stream<List<Ticket>> watchTicketsList(String collectionId);

View File

@@ -1,4 +1,6 @@
import 'package:dio/dio.dart';
import 'package:injectable/injectable.dart'; import 'package:injectable/injectable.dart';
import 'package:remever/common/services/api_client.dart';
import 'package:remever/database/database.dart'; import 'package:remever/database/database.dart';
import 'package:remever/inject.dart'; import 'package:remever/inject.dart';
import 'package:remever/models/create_ticket_dto.dart'; import 'package:remever/models/create_ticket_dto.dart';
@@ -32,4 +34,40 @@ final class TicketsService implements TicketsInterface {
newCollectionId, newCollectionId,
); );
} }
@override
Future<void> getTicketsFromApi() async {
try {
final Response<dynamic> response = await apiClient.get('/cards');
print('data');
} catch (e) {
print('Response error $e');
}
}
@override
Future<void> createTicketApi() async {
try {
final Response<dynamic> response = await apiClient.post(
'/cards',
data: {
"title": "Основы программирования",
"question":
"Коллекция карточек по основам программирования для начинающих",
"question_picture_id": 1,
"answer":
"Коллекция карточек по основам программирования для начинающих",
"answer_picture_id": 2,
"is_public": true,
"is_reverse": false,
"collection_id": 1,
},
);
print('data');
} catch (e) {
print('Response error $e');
}
}
} }

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

View File

@@ -693,6 +693,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.5.4" version: "4.5.4"
image_cropper:
dependency: "direct main"
description:
name: image_cropper
sha256: "4e9c96c029eb5a23798da1b6af39787f964da6ffc78fd8447c140542a9f7c6fc"
url: "https://pub.dev"
source: hosted
version: "9.1.0"
image_cropper_for_web:
dependency: transitive
description:
name: image_cropper_for_web
sha256: fd81ebe36f636576094377aab32673c4e5d1609b32dec16fad98d2b71f1250a9
url: "https://pub.dev"
source: hosted
version: "6.1.0"
image_cropper_platform_interface:
dependency: transitive
description:
name: image_cropper_platform_interface
sha256: "6ca6b81769abff9a4dcc3bbd3d75f5dfa9de6b870ae9613c8cd237333a4283af"
url: "https://pub.dev"
source: hosted
version: "7.1.0"
image_size_getter: image_size_getter:
dependency: transitive dependency: transitive
description: description:
@@ -910,7 +934,7 @@ packages:
source: hosted source: hosted
version: "1.1.0" version: "1.1.0"
path_provider: path_provider:
dependency: transitive dependency: "direct main"
description: description:
name: path_provider name: path_provider
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"

View File

@@ -2,7 +2,7 @@ name: remever
description: "" description: ""
publish_to: 'none' publish_to: 'none'
version: 1.0.0+7 version: 1.0.0+8
environment: environment:
sdk: ^3.7.0 sdk: ^3.7.0
@@ -38,6 +38,9 @@ dependencies:
pin_code_fields: ^8.0.1 pin_code_fields: ^8.0.1
modal_bottom_sheet: ^3.0.0 modal_bottom_sheet: ^3.0.0
file_picker: ^10.0.0 file_picker: ^10.0.0
path_provider: ^2.1.5
image_cropper: ^9.1.0
# logs # logs
talker: ^4.6.11 talker: ^4.6.11