Compare commits

...

18 Commits

Author SHA1 Message Date
e54f7908e3 Merge pull request 'Добавлено форматирование текста' (#9) from feature/training into develop
Reviewed-on: #9
2025-11-05 11:59:47 +00:00
84aefe0d35 feature(core):добавлен формат текста 2025-11-05 14:58:05 +03:00
6d04c0ada0 feature(core):save 2025-10-29 10:13:34 +03:00
e791f08eef feature(self): добавлен разлогин 2025-09-08 22:55:38 +03:00
bcfb9d42e8 Merge pull request 'Feature: Получение данных для тренировки, фоторедактор, изменение хранения изображений на локальный формат файлов а не байтов в бд' (#8) from feature/training into develop
Reviewed-on: #8
2025-09-08 22:46:19 +03:00
845a380fbf feature(training): отбражение данных для тренировки 2025-09-08 22:44:03 +03:00
90531e6e4e feature(image): Добавлен редактор фото 2025-09-08 21:47:51 +03:00
6cb9e82e61 feature(collection): Подсчет кол-ва карточек в коллекции 2025-09-08 21:13:16 +03:00
51c4ae4f02 bugfix(image): Перенос хранения картинок из бд в папку приложения 2025-09-08 20:55:17 +03:00
a376faf0ce bugfix(collections): Оптимизация редактирования и создания коллекции 2025-09-08 17:42:09 +03:00
cebc46bbb3 feature(collections): Роутинг на поиск 2025-09-08 16:43:43 +03:00
8b546214eb feature(collections): Заготовка под апи. Переключалка коллекций 2025-09-08 16:39:33 +03:00
0426d94d60 Merge pull request 'feature(training): Добавлен состояния загрузки, пустоты и наличие данных на экране тренировки' (#7) from feature/training into develop
Reviewed-on: https://git.dizoft.ru/Dimkov966/Remever/pulls/7
2025-06-17 22:27:49 +03:00
285c4ca3f2 feature(training): Добавлен состояния загрузки, пустоты и наличие данных на экране тренировки 2025-06-17 22:27:22 +03:00
b1da4507f1 Merge pull request 'Feature: Добавлена верстка экрана статистики' (#6) from feature/statistick into develop
Reviewed-on: https://git.dizoft.ru/Dimkov966/Remever/pulls/6
2025-06-17 21:06:55 +03:00
0842c479c7 fix(home): Оптимизация работы навигационного бара 2025-06-17 21:05:44 +03:00
e3d3bcfb68 feature(statistick): Основная верстка экрана статистики 2 2025-06-17 20:59:23 +03:00
1a0ecee501 feature(statistick): Основная верстка экрана статистики 2025-06-17 20:56:48 +03:00
73 changed files with 3501 additions and 1992 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@@ -1,192 +0,0 @@
//
// Generated file. Do not edit.
// This file is generated from template in file `flutter_tools/lib/src/flutter_plugins.dart`.
//
// @dart = 3.7
import 'dart:io'; // flutter_ignore: dart_io_import.
import 'package:file_picker/file_picker.dart';
import 'package:path_provider_android/path_provider_android.dart';
import 'package:file_picker/file_picker.dart';
import 'package:path_provider_foundation/path_provider_foundation.dart';
import 'package:file_picker/file_picker.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:path_provider_linux/path_provider_linux.dart';
import 'package:share_plus/share_plus.dart';
import 'package:url_launcher_linux/url_launcher_linux.dart';
import 'package:file_picker/file_picker.dart';
import 'package:path_provider_foundation/path_provider_foundation.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter_secure_storage_windows/flutter_secure_storage_windows.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:path_provider_windows/path_provider_windows.dart';
import 'package:share_plus/share_plus.dart';
import 'package:url_launcher_windows/url_launcher_windows.dart';
@pragma('vm:entry-point')
class _PluginRegistrant {
@pragma('vm:entry-point')
static void register() {
if (Platform.isAndroid) {
try {
FilePickerIO.registerWith();
} catch (err) {
print(
'`file_picker` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
PathProviderAndroid.registerWith();
} catch (err) {
print(
'`path_provider_android` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
} else if (Platform.isIOS) {
try {
FilePickerIO.registerWith();
} catch (err) {
print(
'`file_picker` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
PathProviderFoundation.registerWith();
} catch (err) {
print(
'`path_provider_foundation` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
} else if (Platform.isLinux) {
try {
FilePickerLinux.registerWith();
} catch (err) {
print(
'`file_picker` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
PackageInfoPlusLinuxPlugin.registerWith();
} catch (err) {
print(
'`package_info_plus` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
PathProviderLinux.registerWith();
} catch (err) {
print(
'`path_provider_linux` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
SharePlusLinuxPlugin.registerWith();
} catch (err) {
print(
'`share_plus` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
UrlLauncherLinux.registerWith();
} catch (err) {
print(
'`url_launcher_linux` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
} else if (Platform.isMacOS) {
try {
FilePickerMacOS.registerWith();
} catch (err) {
print(
'`file_picker` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
PathProviderFoundation.registerWith();
} catch (err) {
print(
'`path_provider_foundation` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
} else if (Platform.isWindows) {
try {
FilePickerWindows.registerWith();
} catch (err) {
print(
'`file_picker` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
FlutterSecureStorageWindows.registerWith();
} catch (err) {
print(
'`flutter_secure_storage_windows` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
PackageInfoPlusWindowsPlugin.registerWith();
} catch (err) {
print(
'`package_info_plus` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
PathProviderWindows.registerWith();
} catch (err) {
print(
'`path_provider_windows` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
SharePlusWindowsPlugin.registerWith();
} catch (err) {
print(
'`share_plus` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
UrlLauncherWindows.registerWith();
} catch (err) {
print(
'`url_launcher_windows` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
}
}
}

File diff suppressed because one or more lines are too long

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/
@@ -502,10 +514,6 @@ pubspec_parse
3.6 3.6
file:///Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/pubspec_parse-1.5.0/ file:///Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/pubspec_parse-1.5.0/
file:///Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/pubspec_parse-1.5.0/lib/ file:///Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/pubspec_parse-1.5.0/lib/
readmore
3.0
file:///Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/readmore-3.0.0/
file:///Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/readmore-3.0.0/lib/
recase recase
2.12 2.12
file:///Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/recase-4.1.0/ file:///Users/vitalijnecitajlo/.pub-cache/hosted/pub.dev/recase-4.1.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>
</resources>
<style name="Ucrop.CropTheme" parent="Theme.AppCompat.Light.NoActionBar" /> <!--add
this line-->
</resources>

BIN
assets/.DS_Store vendored

Binary file not shown.

BIN
assets/icons/stats_best.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
assets/icons/stats_card.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
assets/icons/stats_down.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 764 B

BIN
assets/icons/stats_time.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
assets/icons/stats_up.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 429 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

138
ios/Podfile.lock Normal file
View File

@@ -0,0 +1,138 @@
PODS:
- DKImagePickerController/Core (4.3.9):
- DKImagePickerController/ImageDataManager
- DKImagePickerController/Resource
- DKImagePickerController/ImageDataManager (4.3.9)
- DKImagePickerController/PhotoGallery (4.3.9):
- DKImagePickerController/Core
- DKPhotoGallery
- DKImagePickerController/Resource (4.3.9)
- DKPhotoGallery (0.0.19):
- DKPhotoGallery/Core (= 0.0.19)
- DKPhotoGallery/Model (= 0.0.19)
- DKPhotoGallery/Preview (= 0.0.19)
- DKPhotoGallery/Resource (= 0.0.19)
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Core (0.0.19):
- DKPhotoGallery/Model
- DKPhotoGallery/Preview
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Model (0.0.19):
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Preview (0.0.19):
- DKPhotoGallery/Model
- DKPhotoGallery/Resource
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Resource (0.0.19):
- SDWebImage
- SwiftyGif
- file_picker (0.0.1):
- DKImagePickerController/PhotoGallery
- Flutter
- Flutter (1.0.0)
- flutter_secure_storage (6.0.0):
- Flutter
- fluttertoast (0.0.2):
- Flutter
- image_cropper (0.0.4):
- Flutter
- TOCropViewController (~> 2.7.4)
- package_info_plus (0.4.5):
- Flutter
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- SDWebImage (5.21.3):
- SDWebImage/Core (= 5.21.3)
- SDWebImage/Core (5.21.3)
- share_plus (0.0.1):
- Flutter
- sqlite3 (3.49.2):
- sqlite3/common (= 3.49.2)
- sqlite3/common (3.49.2)
- sqlite3/dbstatvtab (3.49.2):
- sqlite3/common
- sqlite3/fts5 (3.49.2):
- sqlite3/common
- sqlite3/math (3.49.2):
- sqlite3/common
- sqlite3/perf-threadsafe (3.49.2):
- sqlite3/common
- sqlite3/rtree (3.49.2):
- sqlite3/common
- sqlite3_flutter_libs (0.0.1):
- Flutter
- FlutterMacOS
- sqlite3 (~> 3.49.1)
- sqlite3/dbstatvtab
- sqlite3/fts5
- sqlite3/math
- sqlite3/perf-threadsafe
- sqlite3/rtree
- SwiftyGif (5.4.5)
- TOCropViewController (2.7.4)
DEPENDENCIES:
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
- image_cropper (from `.symlinks/plugins/image_cropper/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`)
SPEC REPOS:
trunk:
- DKImagePickerController
- DKPhotoGallery
- SDWebImage
- sqlite3
- SwiftyGif
- TOCropViewController
EXTERNAL SOURCES:
file_picker:
:path: ".symlinks/plugins/file_picker/ios"
Flutter:
:path: Flutter
flutter_secure_storage:
:path: ".symlinks/plugins/flutter_secure_storage/ios"
fluttertoast:
:path: ".symlinks/plugins/fluttertoast/ios"
image_cropper:
:path: ".symlinks/plugins/image_cropper/ios"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
share_plus:
:path: ".symlinks/plugins/share_plus/ios"
sqlite3_flutter_libs:
:path: ".symlinks/plugins/sqlite3_flutter_libs/darwin"
SPEC CHECKSUMS:
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1
image_cropper: c4326ea50132b1e1564499e5d32a84f01fb03537
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
SDWebImage: 16309af6d214ba3f77a7c6f6fdda888cb313a50a
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1
sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
TOCropViewController: 80b8985ad794298fb69d3341de183f33d1853654
PODFILE CHECKSUM: 4305caec6b40dde0ae97be1573c53de1882a07e5
COCOAPODS: 1.16.2

View File

@@ -10,7 +10,9 @@
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
56D0DA369935817C9FD61D64 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 20EA46D90FEDEE106AB74AE3 /* Pods_RunnerTests.framework */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
869B506EF2E45BAB39615F26 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B39F7BDABEAD0ED3D2BCBF8A /* Pods_Runner.framework */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
@@ -42,12 +44,18 @@
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
20EA46D90FEDEE106AB74AE3 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
274309643C09AEB895F5B282 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
3DD5DA132CCC13F9168EA46C /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
4D9F7D7DAC8938D01FCC4D51 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
8430F8544E2F6590CF5BD216 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
923EC9622249E9CDBDFE9D06 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -55,19 +63,39 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
B39F7BDABEAD0ED3D2BCBF8A /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
B5951109D53267981AE8A56D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
627E60B7283AB62A12CFF618 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
56D0DA369935817C9FD61D64 /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EB1CF9000F007C117D /* Frameworks */ = { 97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
869B506EF2E45BAB39615F26 /* Pods_Runner.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
/* End PBXFrameworksBuildPhase section */ /* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */ /* Begin PBXGroup section */
27B1E70DD21F0A27FF242714 /* Frameworks */ = {
isa = PBXGroup;
children = (
B39F7BDABEAD0ED3D2BCBF8A /* Pods_Runner.framework */,
20EA46D90FEDEE106AB74AE3 /* Pods_RunnerTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
331C8082294A63A400263BE5 /* RunnerTests */ = { 331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -94,6 +122,8 @@
97C146F01CF9000F007C117D /* Runner */, 97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */, 97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */, 331C8082294A63A400263BE5 /* RunnerTests */,
E9812416A7ACA68201AEE0BB /* Pods */,
27B1E70DD21F0A27FF242714 /* Frameworks */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
}; };
@@ -121,6 +151,20 @@
path = Runner; path = Runner;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
E9812416A7ACA68201AEE0BB /* Pods */ = {
isa = PBXGroup;
children = (
B5951109D53267981AE8A56D /* Pods-Runner.debug.xcconfig */,
4D9F7D7DAC8938D01FCC4D51 /* Pods-Runner.release.xcconfig */,
8430F8544E2F6590CF5BD216 /* Pods-Runner.profile.xcconfig */,
923EC9622249E9CDBDFE9D06 /* Pods-RunnerTests.debug.xcconfig */,
274309643C09AEB895F5B282 /* Pods-RunnerTests.release.xcconfig */,
3DD5DA132CCC13F9168EA46C /* Pods-RunnerTests.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
@@ -128,8 +172,10 @@
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = ( buildPhases = (
B6EC4F9E6BB4749CD063C598 /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */, 331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */, 331C807F294A63A400263BE5 /* Resources */,
627E60B7283AB62A12CFF618 /* Frameworks */,
); );
buildRules = ( buildRules = (
); );
@@ -145,12 +191,14 @@
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = ( buildPhases = (
7961A0D505DA31A674AC7FD6 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */, 9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */, 97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */, 97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */, 97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */, 9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
FD477B45F6C2D4CD5E5F5D4C /* [CP] Embed Pods Frameworks */,
); );
buildRules = ( buildRules = (
); );
@@ -238,6 +286,28 @@
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
}; };
7961A0D505DA31A674AC7FD6 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
9740EEB61CF901F6004384FC /* Run Script */ = { 9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1; alwaysOutOfDate = 1;
@@ -253,6 +323,45 @@
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
}; };
B6EC4F9E6BB4749CD063C598 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
FD477B45F6C2D4CD5E5F5D4C /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */ /* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */
@@ -379,6 +488,7 @@
}; };
331C8088294A63A400263BE5 /* Debug */ = { 331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 923EC9622249E9CDBDFE9D06 /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
@@ -396,6 +506,7 @@
}; };
331C8089294A63A400263BE5 /* Release */ = { 331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 274309643C09AEB895F5B282 /* Pods-RunnerTests.release.xcconfig */;
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
@@ -411,6 +522,7 @@
}; };
331C808A294A63A400263BE5 /* Profile */ = { 331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 3DD5DA132CCC13F9168EA46C /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;

View File

@@ -4,4 +4,7 @@
<FileRef <FileRef
location = "group:Runner.xcodeproj"> location = "group:Runner.xcodeproj">
</FileRef> </FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace> </Workspace>

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

@@ -103,6 +103,11 @@ class Bold14px extends TypographyTypeBold {
double get size => 14; double get size => 14;
} }
class Bold24px extends TypographyTypeBold {
@override
double get size => 24;
}
class Bold34px extends TypographyTypeBold { class Bold34px extends TypographyTypeBold {
@override @override
double get size => 34; double get size => 34;

View File

@@ -5,7 +5,7 @@ part of '../env.dart';
/// ///
class DevEnv extends Env { class DevEnv extends Env {
@override @override
Uri get url => Uri.parse('https://api.remever.dizoft-studio.ru'); Uri get url => Uri.parse('https://remever.neojik.tech');
@override @override
String get mode => 'dev'; String get mode => 'dev';

View File

@@ -1,6 +1,9 @@
// Package imports: // Package imports:
import 'dart:io';
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:remever/common/functions.dart'; import 'package:remever/common/functions.dart';
import 'package:remever/common/typedef.dart';
import 'package:remever/database/database.dart'; import 'package:remever/database/database.dart';
import 'package:remever/database/tables.dart'; import 'package:remever/database/tables.dart';
import 'package:remever/models/crud_collection_dto.dart'; import 'package:remever/models/crud_collection_dto.dart';
@@ -28,6 +31,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 +51,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) {
@@ -44,6 +59,33 @@ class CollectionsDao extends DatabaseAccessor<AppDatabase>
} }
} }
/// Синхронизация коллекций
Future<void> syncCollectionFromApi(List<Json> data) async {
for (final item in data) {
try {
await db.managers.collections.create(
(o) => o(
// id: Value<String>(item['id'].toString()),
title: item['title'],
desc: item['description'],
isPublic: Value<bool>(item['is_public']),
image: Value<String?>(null),
createdAt: Value<DateTime>(DateTime.parse(item['created_at'])),
updatedAt: Value<DateTime>(DateTime.parse(item['updated_at'])),
),
);
} catch (e, st) {
logger.logError(
'Не смог добавить колекцию в методе syncCollectionFromApi',
e,
st,
);
continue;
}
}
}
/// Обновление коллекции /// Обновление коллекции
Future<void> updateCollection(CrudCollectionDto dto, String id) async { Future<void> updateCollection(CrudCollectionDto dto, String id) async {
try { try {
@@ -54,7 +96,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

@@ -61,6 +61,29 @@ class $AssetsIconsGen {
AssetGenImage get settingsTrash => AssetGenImage get settingsTrash =>
const AssetGenImage('assets/icons/settings_trash.png'); const AssetGenImage('assets/icons/settings_trash.png');
/// File path: assets/icons/stats_best.png
AssetGenImage get statsBest =>
const AssetGenImage('assets/icons/stats_best.png');
/// File path: assets/icons/stats_card.png
AssetGenImage get statsCard =>
const AssetGenImage('assets/icons/stats_card.png');
/// File path: assets/icons/stats_down.png
AssetGenImage get statsDown =>
const AssetGenImage('assets/icons/stats_down.png');
/// File path: assets/icons/stats_repeat.png
AssetGenImage get statsRepeat =>
const AssetGenImage('assets/icons/stats_repeat.png');
/// File path: assets/icons/stats_time.png
AssetGenImage get statsTime =>
const AssetGenImage('assets/icons/stats_time.png');
/// File path: assets/icons/stats_up.png
AssetGenImage get statsUp => const AssetGenImage('assets/icons/stats_up.png');
/// File path: assets/icons/type=arrow_down.png /// File path: assets/icons/type=arrow_down.png
AssetGenImage get typeArrowDown => AssetGenImage get typeArrowDown =>
const AssetGenImage('assets/icons/type=arrow_down.png'); const AssetGenImage('assets/icons/type=arrow_down.png');
@@ -271,6 +294,12 @@ class $AssetsIconsGen {
settingsProfile, settingsProfile,
settingsShare, settingsShare,
settingsTrash, settingsTrash,
statsBest,
statsCard,
statsDown,
statsRepeat,
statsTime,
statsUp,
typeArrowDown, typeArrowDown,
typeBack, typeBack,
typeBold, typeBold,
@@ -381,6 +410,10 @@ class $AssetsImagesGen {
/// File path: assets/images/quote.png /// File path: assets/images/quote.png
AssetGenImage get quote => const AssetGenImage('assets/images/quote.png'); AssetGenImage get quote => const AssetGenImage('assets/images/quote.png');
/// File path: assets/images/training_empty.png
AssetGenImage get trainingEmpty =>
const AssetGenImage('assets/images/training_empty.png');
/// List of all assets /// List of all assets
List<dynamic> get values => [ List<dynamic> get values => [
aGitkeep, aGitkeep,
@@ -398,6 +431,7 @@ class $AssetsImagesGen {
logo, logo,
noData, noData,
quote, quote,
trainingEmpty,
]; ];
} }

View File

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

@@ -22,6 +22,8 @@ class AppRouter extends RootStackRouter {
], ],
), ),
AutoRoute(path: '/training', page: TrainingRoute.page),
AutoRoute(path: '/crud_collection', page: CrudCollectionRoute.page), AutoRoute(path: '/crud_collection', page: CrudCollectionRoute.page),
AutoRoute(path: '/collection_search', page: CollectionSearchRoute.page), AutoRoute(path: '/collection_search', page: CollectionSearchRoute.page),
AutoRoute(path: '/crudFullField', page: CrudCollectionFullscreenField.page), AutoRoute(path: '/crudFullField', page: CrudCollectionFullscreenField.page),

View File

@@ -9,9 +9,10 @@
// coverage:ignore-file // coverage:ignore-file
// ignore_for_file: no_leading_underscores_for_library_prefixes // ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:auto_route/auto_route.dart' as _i13; import 'package:auto_route/auto_route.dart' as _i14;
import 'package:flutter/cupertino.dart' as _i14; import 'package:flutter/cupertino.dart' as _i15;
import 'package:remever/database/database.dart' as _i15; import 'package:flutter/material.dart' as _i17;
import 'package:remever/database/database.dart' as _i16;
import 'package:remever/screens/auth/auth_screen.dart' as _i1; import 'package:remever/screens/auth/auth_screen.dart' as _i1;
import 'package:remever/screens/collections/collection_detail_screen.dart' import 'package:remever/screens/collections/collection_detail_screen.dart'
as _i2; as _i2;
@@ -27,16 +28,17 @@ import 'package:remever/screens/sandbox/sandbox_screen.dart' as _i9;
import 'package:remever/screens/settings/settings_screen.dart' as _i10; import 'package:remever/screens/settings/settings_screen.dart' as _i10;
import 'package:remever/screens/splash/splash_screen.dart' as _i11; import 'package:remever/screens/splash/splash_screen.dart' as _i11;
import 'package:remever/screens/statistick/statistick_screen.dart' as _i12; import 'package:remever/screens/statistick/statistick_screen.dart' as _i12;
import 'package:remever/screens/training/training_screen.dart' as _i13;
/// generated route for /// generated route for
/// [_i1.AuthScreen] /// [_i1.AuthScreen]
class AuthRoute extends _i13.PageRouteInfo<void> { class AuthRoute extends _i14.PageRouteInfo<void> {
const AuthRoute({List<_i13.PageRouteInfo>? children}) const AuthRoute({List<_i14.PageRouteInfo>? children})
: super(AuthRoute.name, initialChildren: children); : super(AuthRoute.name, initialChildren: children);
static const String name = 'AuthRoute'; static const String name = 'AuthRoute';
static _i13.PageInfo page = _i13.PageInfo( static _i14.PageInfo page = _i14.PageInfo(
name, name,
builder: (data) { builder: (data) {
return const _i1.AuthScreen(); return const _i1.AuthScreen();
@@ -47,11 +49,11 @@ class AuthRoute extends _i13.PageRouteInfo<void> {
/// generated route for /// generated route for
/// [_i2.CollectionDetailScreen] /// [_i2.CollectionDetailScreen]
class CollectionDetailRoute class CollectionDetailRoute
extends _i13.PageRouteInfo<CollectionDetailRouteArgs> { extends _i14.PageRouteInfo<CollectionDetailRouteArgs> {
CollectionDetailRoute({ CollectionDetailRoute({
_i14.Key? key, _i15.Key? key,
required _i15.Collection collection, required _i16.Collection collection,
List<_i13.PageRouteInfo>? children, List<_i14.PageRouteInfo>? children,
}) : super( }) : super(
CollectionDetailRoute.name, CollectionDetailRoute.name,
args: CollectionDetailRouteArgs(key: key, collection: collection), args: CollectionDetailRouteArgs(key: key, collection: collection),
@@ -60,7 +62,7 @@ class CollectionDetailRoute
static const String name = 'CollectionDetailRoute'; static const String name = 'CollectionDetailRoute';
static _i13.PageInfo page = _i13.PageInfo( static _i14.PageInfo page = _i14.PageInfo(
name, name,
builder: (data) { builder: (data) {
final args = data.argsAs<CollectionDetailRouteArgs>(); final args = data.argsAs<CollectionDetailRouteArgs>();
@@ -75,9 +77,9 @@ class CollectionDetailRoute
class CollectionDetailRouteArgs { class CollectionDetailRouteArgs {
const CollectionDetailRouteArgs({this.key, required this.collection}); const CollectionDetailRouteArgs({this.key, required this.collection});
final _i14.Key? key; final _i15.Key? key;
final _i15.Collection collection; final _i16.Collection collection;
@override @override
String toString() { String toString() {
@@ -87,13 +89,13 @@ class CollectionDetailRouteArgs {
/// generated route for /// generated route for
/// [_i3.CollectionScreen] /// [_i3.CollectionScreen]
class CollectionRoute extends _i13.PageRouteInfo<void> { class CollectionRoute extends _i14.PageRouteInfo<void> {
const CollectionRoute({List<_i13.PageRouteInfo>? children}) const CollectionRoute({List<_i14.PageRouteInfo>? children})
: super(CollectionRoute.name, initialChildren: children); : super(CollectionRoute.name, initialChildren: children);
static const String name = 'CollectionRoute'; static const String name = 'CollectionRoute';
static _i13.PageInfo page = _i13.PageInfo( static _i14.PageInfo page = _i14.PageInfo(
name, name,
builder: (data) { builder: (data) {
return const _i3.CollectionScreen(); return const _i3.CollectionScreen();
@@ -104,11 +106,11 @@ class CollectionRoute extends _i13.PageRouteInfo<void> {
/// generated route for /// generated route for
/// [_i4.CollectionSearchScreen] /// [_i4.CollectionSearchScreen]
class CollectionSearchRoute class CollectionSearchRoute
extends _i13.PageRouteInfo<CollectionSearchRouteArgs> { extends _i14.PageRouteInfo<CollectionSearchRouteArgs> {
CollectionSearchRoute({ CollectionSearchRoute({
_i14.Key? key, _i15.Key? key,
required void Function(_i15.Collection) onCollectionSelect, required void Function(_i16.Collection) onCollectionSelect,
List<_i13.PageRouteInfo>? children, List<_i14.PageRouteInfo>? children,
}) : super( }) : super(
CollectionSearchRoute.name, CollectionSearchRoute.name,
args: CollectionSearchRouteArgs( args: CollectionSearchRouteArgs(
@@ -120,7 +122,7 @@ class CollectionSearchRoute
static const String name = 'CollectionSearchRoute'; static const String name = 'CollectionSearchRoute';
static _i13.PageInfo page = _i13.PageInfo( static _i14.PageInfo page = _i14.PageInfo(
name, name,
builder: (data) { builder: (data) {
final args = data.argsAs<CollectionSearchRouteArgs>(); final args = data.argsAs<CollectionSearchRouteArgs>();
@@ -135,9 +137,9 @@ class CollectionSearchRoute
class CollectionSearchRouteArgs { class CollectionSearchRouteArgs {
const CollectionSearchRouteArgs({this.key, required this.onCollectionSelect}); const CollectionSearchRouteArgs({this.key, required this.onCollectionSelect});
final _i14.Key? key; final _i15.Key? key;
final void Function(_i15.Collection) onCollectionSelect; final void Function(_i16.Collection) onCollectionSelect;
@override @override
String toString() { String toString() {
@@ -147,11 +149,11 @@ class CollectionSearchRouteArgs {
/// generated route for /// generated route for
/// [_i5.CreateScreen] /// [_i5.CreateScreen]
class CreateRoute extends _i13.PageRouteInfo<CreateRouteArgs> { class CreateRoute extends _i14.PageRouteInfo<CreateRouteArgs> {
CreateRoute({ CreateRoute({
_i14.Key? key, _i15.Key? key,
_i15.Collection? collection, _i16.Collection? collection,
List<_i13.PageRouteInfo>? children, List<_i14.PageRouteInfo>? children,
}) : super( }) : super(
CreateRoute.name, CreateRoute.name,
args: CreateRouteArgs(key: key, collection: collection), args: CreateRouteArgs(key: key, collection: collection),
@@ -160,7 +162,7 @@ class CreateRoute extends _i13.PageRouteInfo<CreateRouteArgs> {
static const String name = 'CreateRoute'; static const String name = 'CreateRoute';
static _i13.PageInfo page = _i13.PageInfo( static _i14.PageInfo page = _i14.PageInfo(
name, name,
builder: (data) { builder: (data) {
final args = data.argsAs<CreateRouteArgs>( final args = data.argsAs<CreateRouteArgs>(
@@ -174,9 +176,9 @@ class CreateRoute extends _i13.PageRouteInfo<CreateRouteArgs> {
class CreateRouteArgs { class CreateRouteArgs {
const CreateRouteArgs({this.key, this.collection}); const CreateRouteArgs({this.key, this.collection});
final _i14.Key? key; final _i15.Key? key;
final _i15.Collection? collection; final _i16.Collection? collection;
@override @override
String toString() { String toString() {
@@ -187,15 +189,15 @@ class CreateRouteArgs {
/// generated route for /// generated route for
/// [_i6.CrudCollectionFullscreenField] /// [_i6.CrudCollectionFullscreenField]
class CrudCollectionFullscreenField class CrudCollectionFullscreenField
extends _i13.PageRouteInfo<CrudCollectionFullscreenFieldArgs> { extends _i14.PageRouteInfo<CrudCollectionFullscreenFieldArgs> {
CrudCollectionFullscreenField({ CrudCollectionFullscreenField({
_i14.Key? key, _i15.Key? key,
String title = '', String title = '',
String? hint, String? hint,
String? content, String? content,
double height = 92, double height = 92,
required void Function(String?) onEditingComplete, required void Function(String?) onEditingComplete,
List<_i13.PageRouteInfo>? children, List<_i14.PageRouteInfo>? children,
}) : super( }) : super(
CrudCollectionFullscreenField.name, CrudCollectionFullscreenField.name,
args: CrudCollectionFullscreenFieldArgs( args: CrudCollectionFullscreenFieldArgs(
@@ -211,7 +213,7 @@ class CrudCollectionFullscreenField
static const String name = 'CrudCollectionFullscreenField'; static const String name = 'CrudCollectionFullscreenField';
static _i13.PageInfo page = _i13.PageInfo( static _i14.PageInfo page = _i14.PageInfo(
name, name,
builder: (data) { builder: (data) {
final args = data.argsAs<CrudCollectionFullscreenFieldArgs>(); final args = data.argsAs<CrudCollectionFullscreenFieldArgs>();
@@ -237,7 +239,7 @@ class CrudCollectionFullscreenFieldArgs {
required this.onEditingComplete, required this.onEditingComplete,
}); });
final _i14.Key? key; final _i15.Key? key;
final String title; final String title;
@@ -257,11 +259,11 @@ class CrudCollectionFullscreenFieldArgs {
/// generated route for /// generated route for
/// [_i7.CrudCollectionScreen] /// [_i7.CrudCollectionScreen]
class CrudCollectionRoute extends _i13.PageRouteInfo<CrudCollectionRouteArgs> { class CrudCollectionRoute extends _i14.PageRouteInfo<CrudCollectionRouteArgs> {
CrudCollectionRoute({ CrudCollectionRoute({
_i14.Key? key, _i15.Key? key,
_i15.Collection? editedCollection, _i16.Collection? editedCollection,
List<_i13.PageRouteInfo>? children, List<_i14.PageRouteInfo>? children,
}) : super( }) : super(
CrudCollectionRoute.name, CrudCollectionRoute.name,
args: CrudCollectionRouteArgs( args: CrudCollectionRouteArgs(
@@ -273,7 +275,7 @@ class CrudCollectionRoute extends _i13.PageRouteInfo<CrudCollectionRouteArgs> {
static const String name = 'CrudCollectionRoute'; static const String name = 'CrudCollectionRoute';
static _i13.PageInfo page = _i13.PageInfo( static _i14.PageInfo page = _i14.PageInfo(
name, name,
builder: (data) { builder: (data) {
final args = data.argsAs<CrudCollectionRouteArgs>( final args = data.argsAs<CrudCollectionRouteArgs>(
@@ -290,9 +292,9 @@ class CrudCollectionRoute extends _i13.PageRouteInfo<CrudCollectionRouteArgs> {
class CrudCollectionRouteArgs { class CrudCollectionRouteArgs {
const CrudCollectionRouteArgs({this.key, this.editedCollection}); const CrudCollectionRouteArgs({this.key, this.editedCollection});
final _i14.Key? key; final _i15.Key? key;
final _i15.Collection? editedCollection; final _i16.Collection? editedCollection;
@override @override
String toString() { String toString() {
@@ -302,13 +304,13 @@ class CrudCollectionRouteArgs {
/// generated route for /// generated route for
/// [_i8.HomeScreen] /// [_i8.HomeScreen]
class HomeRoute extends _i13.PageRouteInfo<void> { class HomeRoute extends _i14.PageRouteInfo<void> {
const HomeRoute({List<_i13.PageRouteInfo>? children}) const HomeRoute({List<_i14.PageRouteInfo>? children})
: super(HomeRoute.name, initialChildren: children); : super(HomeRoute.name, initialChildren: children);
static const String name = 'HomeRoute'; static const String name = 'HomeRoute';
static _i13.PageInfo page = _i13.PageInfo( static _i14.PageInfo page = _i14.PageInfo(
name, name,
builder: (data) { builder: (data) {
return const _i8.HomeScreen(); return const _i8.HomeScreen();
@@ -318,13 +320,13 @@ class HomeRoute extends _i13.PageRouteInfo<void> {
/// generated route for /// generated route for
/// [_i9.SandboxScreen] /// [_i9.SandboxScreen]
class SandboxRoute extends _i13.PageRouteInfo<void> { class SandboxRoute extends _i14.PageRouteInfo<void> {
const SandboxRoute({List<_i13.PageRouteInfo>? children}) const SandboxRoute({List<_i14.PageRouteInfo>? children})
: super(SandboxRoute.name, initialChildren: children); : super(SandboxRoute.name, initialChildren: children);
static const String name = 'SandboxRoute'; static const String name = 'SandboxRoute';
static _i13.PageInfo page = _i13.PageInfo( static _i14.PageInfo page = _i14.PageInfo(
name, name,
builder: (data) { builder: (data) {
return const _i9.SandboxScreen(); return const _i9.SandboxScreen();
@@ -334,13 +336,13 @@ class SandboxRoute extends _i13.PageRouteInfo<void> {
/// generated route for /// generated route for
/// [_i10.SettingsScreen] /// [_i10.SettingsScreen]
class SettingsRoute extends _i13.PageRouteInfo<void> { class SettingsRoute extends _i14.PageRouteInfo<void> {
const SettingsRoute({List<_i13.PageRouteInfo>? children}) const SettingsRoute({List<_i14.PageRouteInfo>? children})
: super(SettingsRoute.name, initialChildren: children); : super(SettingsRoute.name, initialChildren: children);
static const String name = 'SettingsRoute'; static const String name = 'SettingsRoute';
static _i13.PageInfo page = _i13.PageInfo( static _i14.PageInfo page = _i14.PageInfo(
name, name,
builder: (data) { builder: (data) {
return const _i10.SettingsScreen(); return const _i10.SettingsScreen();
@@ -350,13 +352,13 @@ class SettingsRoute extends _i13.PageRouteInfo<void> {
/// generated route for /// generated route for
/// [_i11.SplashScreen] /// [_i11.SplashScreen]
class SplashRoute extends _i13.PageRouteInfo<void> { class SplashRoute extends _i14.PageRouteInfo<void> {
const SplashRoute({List<_i13.PageRouteInfo>? children}) const SplashRoute({List<_i14.PageRouteInfo>? children})
: super(SplashRoute.name, initialChildren: children); : super(SplashRoute.name, initialChildren: children);
static const String name = 'SplashRoute'; static const String name = 'SplashRoute';
static _i13.PageInfo page = _i13.PageInfo( static _i14.PageInfo page = _i14.PageInfo(
name, name,
builder: (data) { builder: (data) {
return const _i11.SplashScreen(); return const _i11.SplashScreen();
@@ -366,16 +368,50 @@ class SplashRoute extends _i13.PageRouteInfo<void> {
/// generated route for /// generated route for
/// [_i12.StatistickScreen] /// [_i12.StatistickScreen]
class StatistickRoute extends _i13.PageRouteInfo<void> { class StatistickRoute extends _i14.PageRouteInfo<StatistickRouteArgs> {
const StatistickRoute({List<_i13.PageRouteInfo>? children}) StatistickRoute({_i17.Key? key, List<_i14.PageRouteInfo>? children})
: super(StatistickRoute.name, initialChildren: children); : super(
StatistickRoute.name,
args: StatistickRouteArgs(key: key),
initialChildren: children,
);
static const String name = 'StatistickRoute'; static const String name = 'StatistickRoute';
static _i13.PageInfo page = _i13.PageInfo( static _i14.PageInfo page = _i14.PageInfo(
name, name,
builder: (data) { builder: (data) {
return const _i12.StatistickScreen(); final args = data.argsAs<StatistickRouteArgs>(
orElse: () => const StatistickRouteArgs(),
);
return _i12.StatistickScreen(key: args.key);
},
);
}
class StatistickRouteArgs {
const StatistickRouteArgs({this.key});
final _i17.Key? key;
@override
String toString() {
return 'StatistickRouteArgs{key: $key}';
}
}
/// generated route for
/// [_i13.TrainingScreen]
class TrainingRoute extends _i14.PageRouteInfo<void> {
const TrainingRoute({List<_i14.PageRouteInfo>? children})
: super(TrainingRoute.name, initialChildren: children);
static const String name = 'TrainingRoute';
static _i14.PageInfo page = _i14.PageInfo(
name,
builder: (data) {
return const _i13.TrainingScreen();
}, },
); );
} }

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,12 +153,14 @@ 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(
child: AppTypography( child: AppTypography(
collection.title.substring(0, 1), collection.title.isNotEmpty
? collection.title.substring(0, 1)
: '',
type: Bold34px(), type: Bold34px(),
), ),
), ),

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 {
@@ -66,6 +68,9 @@ class _CollectionScreenState extends State<CollectionScreen> {
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
child: FloatingActionButton( child: FloatingActionButton(
backgroundColor: AppColors.primary, backgroundColor: AppColors.primary,
// onPressed: () {
// getIt<CollectionsInterface>().getCollectionsFromApi();
// },
onPressed: () => context.pushRoute(CrudCollectionRoute()), onPressed: () => context.pushRoute(CrudCollectionRoute()),
child: const Icon(Icons.add), child: const Icon(Icons.add),
), ),

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(
@@ -169,15 +185,17 @@ class CollectionCard extends StatelessWidget {
decoration: BoxDecoration(shape: BoxShape.circle, color: AppColors.bg), decoration: BoxDecoration(shape: BoxShape.circle, color: AppColors.bg),
child: Wif( child: Wif(
condition: collection.image != null, condition: collection.image != null && collection.image != '',
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(
child: AppTypography( child: AppTypography(
collection.title.substring(0, 1), collection.title.isNotEmpty
? collection.title.substring(0, 1)
: '',
type: Bold34px(), type: Bold34px(),
), ),
), ),

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';
@@ -15,6 +17,7 @@ import 'package:remever/inject.dart';
import 'package:remever/screens/dialogs/alert_dialog.dart'; import 'package:remever/screens/dialogs/alert_dialog.dart';
import 'package:remever/screens/dialogs/replace_diaog.dart'; import 'package:remever/screens/dialogs/replace_diaog.dart';
import 'package:remever/services/tickets/tickets_interface.dart'; import 'package:remever/services/tickets/tickets_interface.dart';
import 'package:remever/widgets/markdown_readmore_text.dart';
class TicketCard extends StatefulWidget { class TicketCard extends StatefulWidget {
const TicketCard({ const TicketCard({
@@ -157,15 +160,15 @@ class _TicketCardState extends State<TicketCard> {
} }
Widget _buildText(BuildContext context) { Widget _buildText(BuildContext context) {
return ReadMoreText( final text = _isRolled ? widget.ticket.answer : widget.ticket.question;
_isRolled ? widget.ticket.answer : widget.ticket.question,
trimMode: TrimMode.Line, return MarkdownReadMoreText(
text,
trimLines: 3, trimLines: 3,
trimCollapsedText: '\nРазвернуть',
trimExpandedText: '\nСвернуть',
style: Regular16px().style, style: Regular16px().style,
moreStyle: Regular12px().style.copyWith(color: AppColors.primary_blue), linkStyle: Regular12px().style.copyWith(color: AppColors.primary_blue),
lessStyle: Regular12px().style.copyWith(color: AppColors.primary_blue), expandText: 'Развернуть',
collapseText: 'Свернуть',
); );
} }
@@ -181,7 +184,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

@@ -1,4 +1,5 @@
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:collection/collection.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@@ -39,18 +40,110 @@ 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();
_controller.addListener(_handleTextChange);
} }
@override @override
void dispose() { void dispose() {
_controller.removeListener(_handleTextChange);
_controller.dispose(); _controller.dispose();
super.dispose(); super.dispose();
} }
String? _lastText;
void _handleTextChange() {
final currentText = _controller.text;
// Если текст не изменился — выходим
if (_lastText == currentText) return;
// Находим позицию курсора
final selection = _controller.selection;
if (!selection.isValid) return;
// Проверяем, был ли добавлен \n
if (_lastText != null && currentText.length > _lastText!.length) {
final addedChars = currentText.substring(_lastText!.length);
if (addedChars == '\n') {
_handleNewLineInserted(selection.start - 1);
}
}
_lastText = currentText;
}
void _handleNewLineInserted(int newlinePosition) {
final text = _controller.text;
// Находим начало текущей строки (до \n)
final lineStart = text.lastIndexOf('\n', newlinePosition - 1) + 1;
final currentLine = text.substring(lineStart, newlinePosition);
String? prefix;
// Проверяем, начинается ли строка с префикса списка
if (currentLine.startsWith('')) {
prefix = '';
} else {
final match = RegExp(r'^(\d+)\.\s').firstMatch(currentLine);
if (match != null) {
final number = int.parse(match.group(1)!) + 1;
prefix = '$number. ';
}
}
// Если строка пустая (только префикс) — выходим из списка
if (currentLine.trim().isEmpty && prefix != null) {
_exitListAt(newlinePosition);
return;
}
// Если есть префикс — добавляем новую строку с префиксом
if (prefix != null) {
final insertPos = newlinePosition + 1;
final newText =
text.substring(0, insertPos) + prefix + text.substring(insertPos);
_controller.text = newText;
// Перемещаем курсор после префикса
_controller.selection = TextSelection.collapsed(
offset: insertPos + prefix.length,
);
return;
}
// Иначе — просто оставляем \n
_controller.selection = TextSelection.collapsed(
offset: newlinePosition + 1,
);
}
void _exitListAt(int newlinePosition) {
final text = _controller.text;
final insertPos = newlinePosition + 1;
// Удаляем префикс из новой строки
final lineStart = text.lastIndexOf('\n', newlinePosition - 1) + 1;
final currentLine = text.substring(lineStart, newlinePosition);
final trimmed = currentLine.trim();
String replacement = '\n';
if (trimmed.startsWith('') || RegExp(r'^\d+\.').hasMatch(trimmed)) {
replacement = '\n'; // просто перенос
}
final newText =
text.substring(0, insertPos) + replacement + text.substring(insertPos);
_controller.text = newText;
_controller.selection = TextSelection.collapsed(offset: insertPos);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return KeyboardSizeProvider( return KeyboardSizeProvider(
@@ -58,15 +151,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 +169,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 +188,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(
@@ -112,13 +202,22 @@ class _CrudCollectionFullscreenFieldState
top: BorderSide(color: AppColors.gray, width: 1.w), top: BorderSide(color: AppColors.gray, width: 1.w),
), ),
), ),
child: Row( child: SingleChildScrollView(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, scrollDirection: Axis.horizontal,
children: [ child: Row(
_buildPasteButton(), mainAxisAlignment: MainAxisAlignment.spaceEvenly,
_buildCopyButton(), children: [
_buildSubmitButton(), const WSpace(8),
], _buildPasteButton(),
_buildCopyButton(),
_buildBoldButton(),
_buildH1Button(),
_buildBulletListButton(),
_buildNumberedListButton(),
_buildSubmitButton(),
const WSpace(8),
],
),
), ),
), ),
); );
@@ -126,7 +225,119 @@ class _CrudCollectionFullscreenFieldState
); );
} }
/// Кнопка "Вставить из буфера обмена" Widget _buildBoldButton() {
return _MenuButton(
icon: Icon(Icons.format_bold),
onTap: () => _wrapSelection('**', '**'),
);
}
Widget _buildH1Button() {
return _MenuButton(
icon: Icon(Icons.format_italic_outlined),
onTap: () => _insertAtLineStart('# '),
);
}
Widget _buildBulletListButton() {
return _MenuButton(
icon: Icon(Icons.list),
onTap: () => _insertBulletList(),
);
}
Widget _buildNumberedListButton() {
return _MenuButton(
icon: Icon(Icons.list_alt_outlined),
onTap: () => _insertNumberedList(),
);
}
void _wrapSelection(String before, String after) {
final text = _controller.text;
final selection = _controller.selection;
if (!selection.isValid) {
_controller.text = '$before${selection.textInside(text)}$after';
_controller.selection = TextSelection.collapsed(
offset: _controller.text.length - after.length,
);
return;
}
final newText = text.replaceRange(
selection.start,
selection.end,
'$before${selection.textInside(text)}$after',
);
_controller.text = newText;
final cursorPos =
selection.start + before.length + selection.textInside(text).length;
_controller.selection = TextSelection.collapsed(offset: cursorPos);
}
void _insertAtLineStart(String prefix) {
final text = _controller.text;
final selection = _controller.selection;
int start = selection.start;
// Находим начало строки
int lineStart = text.lastIndexOf('\n', start - 1) + 1;
if (lineStart == 0 && start == 0) lineStart = 0;
final newText = text.replaceRange(lineStart, lineStart, prefix);
_controller.text = newText;
_controller.selection = TextSelection.collapsed(
offset: lineStart + prefix.length + (selection.end - selection.start),
);
}
void _insertBulletList() {
_insertListPrefix('');
}
void _insertNumberedList() {
_insertListPrefix('1. ');
}
void _insertListPrefix(String prefix) {
final text = _controller.text;
final selection = _controller.selection;
String insertText;
int newCursorPos;
if (selection.isCollapsed) {
// Вставляем в позицию курсора
insertText = prefix;
newCursorPos = selection.start + prefix.length;
} else {
// Оборачиваем выделение
final selected = selection.textInside(text);
final lines = selected.split('\n').where((l) => l.isNotEmpty).toList();
final prefixed = lines
.mapIndexed((index, line) {
if (prefix == '') return '$prefix$line';
final num = index + 1;
return '$num. $line';
})
.join('\n');
insertText = prefixed;
newCursorPos = selection.start + prefixed.length;
}
final newText = text.replaceRange(
selection.start,
selection.end,
insertText,
);
_controller.text = newText;
_controller.selection = TextSelection.collapsed(offset: newCursorPos);
}
Widget _buildPasteButton() { Widget _buildPasteButton() {
return GestureDetector( return GestureDetector(
onTap: _onPasteTap, onTap: _onPasteTap,
@@ -134,22 +345,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 +366,7 @@ class _CrudCollectionFullscreenFieldState
); );
} }
/// Обработка нажатия на кнопку "Копировать" Future<void> _onCopyTap() async {
void _onCopyTap() async {
if (_controller.text.isEmpty) { if (_controller.text.isEmpty) {
showErrorToast('Нет содержимого для отправки в буфер обмена'); showErrorToast('Нет содержимого для отправки в буфер обмена');
return; return;
@@ -171,45 +379,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(
@@ -226,6 +420,7 @@ class _CrudCollectionFullscreenFieldState
maxLines: 99, maxLines: 99,
maxLength: 250, maxLength: 250,
cursorColor: AppColors.danger, cursorColor: AppColors.danger,
textInputAction: TextInputAction.newline,
decoration: const InputDecoration.collapsed( decoration: const InputDecoration.collapsed(
hintText: 'Введите содержимое', hintText: 'Введите содержимое',
hintStyle: TextStyle(color: AppColors.gray), hintStyle: TextStyle(color: AppColors.gray),
@@ -236,30 +431,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 +462,61 @@ 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();
}
}
}
class _MenuButton extends StatelessWidget {
final Widget icon;
final VoidCallback onTap;
const _MenuButton({required this.icon, required this.onTap});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(padding: const EdgeInsets.all(8).r, child: icon),
);
} }
} }

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';
@@ -7,6 +8,7 @@ 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/database/database.dart';
import 'package:remever/widgets/markdown_text.dart';
import 'package:remever/widgets/primary_button.dart'; import 'package:remever/widgets/primary_button.dart';
class InfoDialog extends StatelessWidget { class InfoDialog extends StatelessWidget {
@@ -59,15 +61,24 @@ class InfoDialog extends StatelessWidget {
return Flexible( return Flexible(
child: SingleChildScrollView( child: SingleChildScrollView(
controller: ScrollController(), controller: ScrollController(),
child: AppTypography( child: MarkdownText(collection.desc),
collection.desc,
type: Regular14px(),
maxLines: 9999,
),
), ),
); );
} }
// Widget _buildText() {
// return Flexible(
// child: SingleChildScrollView(
// controller: ScrollController(),
// child: AppTypography(
// collection.desc,
// type: Regular14px(),
// maxLines: 9999,
// ),
// ),
// );
// }
/// ///
/// Название коллекции /// Название коллекции
/// ///
@@ -94,12 +105,14 @@ 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(
child: AppTypography( child: AppTypography(
collection.title.substring(0, 1), collection.title.isNotEmpty
? collection.title.substring(0, 1)
: '',
type: Bold34px(), type: Bold34px(),
), ),
), ),

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

@@ -21,31 +21,24 @@ class HomeScreen extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
logger.logBuild('build home screen'); logger.logBuild('build home screen');
return SafeArea( return Stack(
top: false, children: [
child: AutoTabsScaffold( SafeArea(
routes: <PageRouteInfo>[ top: false,
SettingsRoute(), child: AutoTabsScaffold(
StatistickRoute(), routes: <PageRouteInfo>[
CreateRoute(), SettingsRoute(),
CollectionRoute(), StatistickRoute(),
], CreateRoute(),
bottomNavigationBuilder: (_, TabsRouter tabsRouter) { CollectionRoute(),
return ColoredBox( ],
color: AppColors.bg, bottomNavigationBuilder: (_, TabsRouter tabsRouter) {
child: SizedBox( return _buildBackgroundBar(tabsRouter);
height: 73.h, },
child: Stack( ),
alignment: Alignment.bottomCenter, ),
children: <Widget>[ _buildCentralButton(context),
_buildBackgroundBar(tabsRouter), ],
_buildCentralButton(),
],
),
),
);
},
),
); );
} }
@@ -87,12 +80,12 @@ class HomeScreen extends StatelessWidget {
} }
/// Построение центральной кнопки /// Построение центральной кнопки
Widget _buildCentralButton() { Widget _buildCentralButton(BuildContext context) {
return Align( return Align(
alignment: Alignment.topCenter, alignment: Alignment(0, 0.91),
child: GestureDetector( child: GestureDetector(
onTap: () { onTap: () {
// Логика нажатия на центральную кнопку context.pushRoute(TrainingRoute());
}, },
child: SizedBox.square( child: SizedBox.square(
dimension: 60.r, dimension: 60.r,

View File

@@ -9,6 +9,7 @@ import 'package:remever/components/extensions/context.dart';
import 'package:remever/gen/assets.gen.dart'; import 'package:remever/gen/assets.gen.dart';
import 'package:remever/screens/settings/cubit/settings_cubit.dart'; import 'package:remever/screens/settings/cubit/settings_cubit.dart';
import 'package:remever/widgets/primary_button.dart'; import 'package:remever/widgets/primary_button.dart';
import 'package:share_plus/share_plus.dart';
class AboutSettingsState extends StatelessWidget { class AboutSettingsState extends StatelessWidget {
const AboutSettingsState({super.key}); const AboutSettingsState({super.key});
@@ -32,8 +33,8 @@ class AboutSettingsState extends StatelessWidget {
), ),
actions: <Widget>[ actions: <Widget>[
IconButton( IconButton(
onPressed: () { onPressed: () async {
showInfoToast('тут будет sharing'); await Share.share('Присоединяйся к нам в REMEVER');
}, },
icon: Assets.icons.settingsShare.image(height: 24.h, width: 24.w), icon: Assets.icons.settingsShare.image(height: 24.h, width: 24.w),
color: Colors.black, color: Colors.black,

View File

@@ -1,11 +1,15 @@
import 'package:auto_route/auto_route.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/functions.dart'; import 'package:remever/common/functions.dart';
import 'package:remever/common/resources.dart'; import 'package:remever/common/resources.dart';
import 'package:remever/common/storage.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/common/widgets/wspace.dart';
import 'package:remever/components/extensions/context.dart'; import 'package:remever/components/extensions/context.dart';
import 'package:remever/gen/assets.gen.dart'; import 'package:remever/gen/assets.gen.dart';
import 'package:remever/router.gr.dart';
import 'package:remever/screens/dialogs/alert_dialog.dart';
import 'package:remever/screens/settings/cubit/settings_cubit.dart'; import 'package:remever/screens/settings/cubit/settings_cubit.dart';
import 'package:remever/widgets/primary_button.dart'; import 'package:remever/widgets/primary_button.dart';
@@ -67,7 +71,25 @@ class InitialSettingsState extends StatelessWidget {
title: AppTypography('Настройки', type: SemiBold20px()), title: AppTypography('Настройки', type: SemiBold20px()),
actions: [ actions: [
IconButton( IconButton(
onPressed: () => showInfoToast('Тут будет выход'), onPressed: () async {
final res = await showCuperModalBottomSheet<bool>(
context: context,
height: 262.h,
builder:
(_) => const AlertInfoDialog(
title: 'Вы уверены что хотите выйти из своего профиля?',
acceptTitle: 'Выйти',
declineTitle: 'Отменить',
),
);
if (res != null && res) {
authSecStorage.delete(key: StorageKeys.accessToken);
authSecStorage.delete(key: StorageKeys.refreshToken);
context.replaceRoute(SplashRoute());
}
},
icon: Assets.icons.settingsExit.image(height: 24.h, width: 24.w), icon: Assets.icons.settingsExit.image(height: 24.h, width: 24.w),
color: Colors.black, color: Colors.black,
), ),

View File

@@ -1,12 +1,247 @@
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/resources.dart';
import 'package:remever/common/widgets/typography.dart';
import 'package:remever/common/widgets/wspace.dart';
import 'package:remever/components/extensions/context.dart';
import 'package:remever/gen/assets.gen.dart';
import 'package:share_plus/share_plus.dart';
import 'package:intl/intl.dart';
@RoutePage() @RoutePage()
class StatistickScreen extends StatelessWidget { class StatistickScreen extends StatelessWidget {
const StatistickScreen({super.key}); StatistickScreen({super.key});
final DateTime today = DateTime.now();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return const Placeholder(color: Colors.orange); return Scaffold(
backgroundColor: AppColors.bg,
appBar: _buildAppBar(context),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16).r,
child: SingleChildScrollView(
physics: BouncingScrollPhysics(),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16).r,
child: Container(
decoration: BoxDecoration(
color: AppColors.white,
borderRadius: BorderRadius.all(Radius.circular(12)).r,
),
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 16).r,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(
child: AppTypography(
"Лучший результат за день",
type: Medium16px(),
),
),
Center(
child: AppTypography(
DateFormat('dd.MM.yyyy').format(today),
type: Regular14px(),
color: AppColors.disabled,
),
),
HSpace(16),
_buildTodayStats(),
HSpace(32),
_buildSectionHeader(
"Повторили карточек",
Assets.icons.statsRepeat,
),
HSpace(16),
_buildStatisticsList([
{"label": "За сегодня", "value": "77"},
{"label": "За эту неделю", "value": "188"},
{"label": "За прошлую неделю", "value": "33"},
{"label": "За последний месяц", "value": "1669"},
{"label": "За все время", "value": "1345669"},
]),
HSpace(32),
_buildSectionHeader(
"Всего изучено карточек",
Assets.icons.statsBest,
),
HSpace(16),
_buildStatisticsList([
{"label": "За сегодня", "value": "77"},
{"label": "За эту неделю", "value": "188"},
{"label": "За прошлую неделю", "value": "33"},
{"label": "За последний месяц", "value": "1669"},
{"label": "За все время", "value": "1345669"},
]),
HSpace(32),
_buildSectionHeader(
"Прошли тренировок",
Assets.icons.statsTime,
),
HSpace(16),
_buildStatisticsList([
{"label": "За сегодня", "value": "77"},
{"label": "За эту неделю", "value": "188"},
{"label": "За прошлую неделю", "value": "33"},
{"label": "За последний месяц", "value": "1669"},
{"label": "За все время", "value": "1345669"},
]),
HSpace(32),
_buildSectionHeader(
"Вырос уровень у карточек",
Assets.icons.statsUp,
),
HSpace(16),
_buildStatisticsList([
{"label": "За сегодня", "value": "77"},
{"label": "За эту неделю", "value": "188"},
{"label": "За прошлую неделю", "value": "33"},
{"label": "За последний месяц", "value": "1669"},
{"label": "За все время", "value": "1345669"},
]),
HSpace(32),
_buildSectionHeader(
"Снизился уровень у карточек",
Assets.icons.statsDown,
),
HSpace(16),
_buildStatisticsList([
{"label": "За сегодня", "value": "77"},
{"label": "За эту неделю", "value": "188"},
{"label": "За прошлую неделю", "value": "33"},
{"label": "За последний месяц", "value": "1669"},
{"label": "За все время", "value": "1345669"},
]),
],
),
),
),
),
),
),
);
}
Widget _buildTodayStats() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
spacing: 8.r,
children: [
_buildStatBlock(
"230",
"повторили\nкарточек",
Color(0xFFD7E6F4),
Color(0xFF0058AB),
),
_buildStatBlock(
"19",
"прошли\nтренировок",
Color(0xFFFFE4E6),
Color(0xFFFF5C69),
),
_buildStatBlock(
"196",
"вырос\nуровень",
Color(0xFFD7F4EA),
Color(0xFF008456),
),
],
);
}
/// AppBar экрана настроек
AppBar _buildAppBar(BuildContext context) {
return AppBar(
toolbarHeight: 66.h,
backgroundColor: AppColors.white,
shadowColor: Colors.transparent,
centerTitle: true,
leadingWidth: 0,
title: AppTypography('Статистика', type: SemiBold20px()),
actions: [
IconButton(
onPressed: () => Share.share('Зацени мою статистику в REMEVER'),
icon: Assets.icons.settingsShare.image(height: 24.h, width: 24.w),
color: Colors.black,
),
],
);
}
// Вспомогательные методы
Widget _buildStatBlock(
String value,
String label,
Color color,
Color textColor,
) {
return Expanded(
child: Container(
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(12).r,
),
padding: EdgeInsets.all(12).r,
child: Column(
children: [
AppTypography(value, type: Bold24px(), color: textColor),
AppTypography(
label,
type: Regular12px(),
color: textColor,
maxLines: 2,
textAlign: TextAlign.center,
),
],
),
),
);
}
Widget _buildSectionHeader(String title, AssetGenImage icon) {
return Row(
children: [
icon.image(height: 20.h, width: 20.w),
WSpace(4),
AppTypography(title, type: Medium16px()),
],
);
}
Widget _buildStatisticsList(List<Map<String, String>> data) {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(12)).r,
color: AppColors.bg,
),
child: Padding(
padding: const EdgeInsets.all(12).r,
child: Column(
spacing: 8.r,
children:
data
.map(
(e) => Row(
children: [
Expanded(
child: AppTypography(
e['label']!,
type: Regular16px(),
),
),
Assets.icons.statsCard.image(height: 18.h, width: 18.w),
AppTypography(e["value"]!, type: Regular16px()),
],
),
)
.toList(),
),
),
);
} }
} }

View File

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

View File

@@ -0,0 +1,601 @@
// 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_cubit.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models',
);
/// @nodoc
mixin _$TrainingState {
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() loading,
required TResult Function() empty,
required TResult Function(List<TrainingDto> data) data,
required TResult Function() result,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? loading,
TResult? Function()? empty,
TResult? Function(List<TrainingDto> data)? data,
TResult? Function()? result,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? loading,
TResult Function()? empty,
TResult Function(List<TrainingDto> data)? data,
TResult Function()? result,
required TResult orElse(),
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Loading value) loading,
required TResult Function(_Empty value) empty,
required TResult Function(_Data value) data,
required TResult Function(_Result value) result,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Loading value)? loading,
TResult? Function(_Empty value)? empty,
TResult? Function(_Data value)? data,
TResult? Function(_Result value)? result,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Loading value)? loading,
TResult Function(_Empty value)? empty,
TResult Function(_Data value)? data,
TResult Function(_Result value)? result,
required TResult orElse(),
}) => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $TrainingStateCopyWith<$Res> {
factory $TrainingStateCopyWith(
TrainingState value,
$Res Function(TrainingState) then,
) = _$TrainingStateCopyWithImpl<$Res, TrainingState>;
}
/// @nodoc
class _$TrainingStateCopyWithImpl<$Res, $Val extends TrainingState>
implements $TrainingStateCopyWith<$Res> {
_$TrainingStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of TrainingState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
abstract class _$$LoadingImplCopyWith<$Res> {
factory _$$LoadingImplCopyWith(
_$LoadingImpl value,
$Res Function(_$LoadingImpl) then,
) = __$$LoadingImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$LoadingImplCopyWithImpl<$Res>
extends _$TrainingStateCopyWithImpl<$Res, _$LoadingImpl>
implements _$$LoadingImplCopyWith<$Res> {
__$$LoadingImplCopyWithImpl(
_$LoadingImpl _value,
$Res Function(_$LoadingImpl) _then,
) : super(_value, _then);
/// Create a copy of TrainingState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$LoadingImpl implements _Loading {
const _$LoadingImpl();
@override
String toString() {
return 'TrainingState.loading()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$LoadingImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() loading,
required TResult Function() empty,
required TResult Function(List<TrainingDto> data) data,
required TResult Function() result,
}) {
return loading();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? loading,
TResult? Function()? empty,
TResult? Function(List<TrainingDto> data)? data,
TResult? Function()? result,
}) {
return loading?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? loading,
TResult Function()? empty,
TResult Function(List<TrainingDto> data)? data,
TResult Function()? result,
required TResult orElse(),
}) {
if (loading != null) {
return loading();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Loading value) loading,
required TResult Function(_Empty value) empty,
required TResult Function(_Data value) data,
required TResult Function(_Result value) result,
}) {
return loading(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Loading value)? loading,
TResult? Function(_Empty value)? empty,
TResult? Function(_Data value)? data,
TResult? Function(_Result value)? result,
}) {
return loading?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Loading value)? loading,
TResult Function(_Empty value)? empty,
TResult Function(_Data value)? data,
TResult Function(_Result value)? result,
required TResult orElse(),
}) {
if (loading != null) {
return loading(this);
}
return orElse();
}
}
abstract class _Loading implements TrainingState {
const factory _Loading() = _$LoadingImpl;
}
/// @nodoc
abstract class _$$EmptyImplCopyWith<$Res> {
factory _$$EmptyImplCopyWith(
_$EmptyImpl value,
$Res Function(_$EmptyImpl) then,
) = __$$EmptyImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$EmptyImplCopyWithImpl<$Res>
extends _$TrainingStateCopyWithImpl<$Res, _$EmptyImpl>
implements _$$EmptyImplCopyWith<$Res> {
__$$EmptyImplCopyWithImpl(
_$EmptyImpl _value,
$Res Function(_$EmptyImpl) _then,
) : super(_value, _then);
/// Create a copy of TrainingState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$EmptyImpl implements _Empty {
const _$EmptyImpl();
@override
String toString() {
return 'TrainingState.empty()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$EmptyImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() loading,
required TResult Function() empty,
required TResult Function(List<TrainingDto> data) data,
required TResult Function() result,
}) {
return empty();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? loading,
TResult? Function()? empty,
TResult? Function(List<TrainingDto> data)? data,
TResult? Function()? result,
}) {
return empty?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? loading,
TResult Function()? empty,
TResult Function(List<TrainingDto> data)? data,
TResult Function()? result,
required TResult orElse(),
}) {
if (empty != null) {
return empty();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Loading value) loading,
required TResult Function(_Empty value) empty,
required TResult Function(_Data value) data,
required TResult Function(_Result value) result,
}) {
return empty(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Loading value)? loading,
TResult? Function(_Empty value)? empty,
TResult? Function(_Data value)? data,
TResult? Function(_Result value)? result,
}) {
return empty?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Loading value)? loading,
TResult Function(_Empty value)? empty,
TResult Function(_Data value)? data,
TResult Function(_Result value)? result,
required TResult orElse(),
}) {
if (empty != null) {
return empty(this);
}
return orElse();
}
}
abstract class _Empty implements TrainingState {
const factory _Empty() = _$EmptyImpl;
}
/// @nodoc
abstract class _$$DataImplCopyWith<$Res> {
factory _$$DataImplCopyWith(
_$DataImpl value,
$Res Function(_$DataImpl) then,
) = __$$DataImplCopyWithImpl<$Res>;
@useResult
$Res call({List<TrainingDto> data});
}
/// @nodoc
class __$$DataImplCopyWithImpl<$Res>
extends _$TrainingStateCopyWithImpl<$Res, _$DataImpl>
implements _$$DataImplCopyWith<$Res> {
__$$DataImplCopyWithImpl(_$DataImpl _value, $Res Function(_$DataImpl) _then)
: super(_value, _then);
/// Create a copy of TrainingState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({Object? data = null}) {
return _then(
_$DataImpl(
null == data
? _value._data
: data // ignore: cast_nullable_to_non_nullable
as List<TrainingDto>,
),
);
}
}
/// @nodoc
class _$DataImpl implements _Data {
const _$DataImpl(final List<TrainingDto> data) : _data = data;
final List<TrainingDto> _data;
@override
List<TrainingDto> get data {
if (_data is EqualUnmodifiableListView) return _data;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_data);
}
@override
String toString() {
return 'TrainingState.data(data: $data)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$DataImpl &&
const DeepCollectionEquality().equals(other._data, _data));
}
@override
int get hashCode =>
Object.hash(runtimeType, const DeepCollectionEquality().hash(_data));
/// Create a copy of TrainingState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$DataImplCopyWith<_$DataImpl> get copyWith =>
__$$DataImplCopyWithImpl<_$DataImpl>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() loading,
required TResult Function() empty,
required TResult Function(List<TrainingDto> data) data,
required TResult Function() result,
}) {
return data(this.data);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? loading,
TResult? Function()? empty,
TResult? Function(List<TrainingDto> data)? data,
TResult? Function()? result,
}) {
return data?.call(this.data);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? loading,
TResult Function()? empty,
TResult Function(List<TrainingDto> data)? data,
TResult Function()? result,
required TResult orElse(),
}) {
if (data != null) {
return data(this.data);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Loading value) loading,
required TResult Function(_Empty value) empty,
required TResult Function(_Data value) data,
required TResult Function(_Result value) result,
}) {
return data(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Loading value)? loading,
TResult? Function(_Empty value)? empty,
TResult? Function(_Data value)? data,
TResult? Function(_Result value)? result,
}) {
return data?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Loading value)? loading,
TResult Function(_Empty value)? empty,
TResult Function(_Data value)? data,
TResult Function(_Result value)? result,
required TResult orElse(),
}) {
if (data != null) {
return data(this);
}
return orElse();
}
}
abstract class _Data implements TrainingState {
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
abstract class _$$ResultImplCopyWith<$Res> {
factory _$$ResultImplCopyWith(
_$ResultImpl value,
$Res Function(_$ResultImpl) then,
) = __$$ResultImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$ResultImplCopyWithImpl<$Res>
extends _$TrainingStateCopyWithImpl<$Res, _$ResultImpl>
implements _$$ResultImplCopyWith<$Res> {
__$$ResultImplCopyWithImpl(
_$ResultImpl _value,
$Res Function(_$ResultImpl) _then,
) : super(_value, _then);
/// Create a copy of TrainingState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$ResultImpl implements _Result {
const _$ResultImpl();
@override
String toString() {
return 'TrainingState.result()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$ResultImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() loading,
required TResult Function() empty,
required TResult Function(List<TrainingDto> data) data,
required TResult Function() result,
}) {
return result();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? loading,
TResult? Function()? empty,
TResult? Function(List<TrainingDto> data)? data,
TResult? Function()? result,
}) {
return result?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? loading,
TResult Function()? empty,
TResult Function(List<TrainingDto> data)? data,
TResult Function()? result,
required TResult orElse(),
}) {
if (result != null) {
return result();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Loading value) loading,
required TResult Function(_Empty value) empty,
required TResult Function(_Data value) data,
required TResult Function(_Result value) result,
}) {
return result(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Loading value)? loading,
TResult? Function(_Empty value)? empty,
TResult? Function(_Data value)? data,
TResult? Function(_Result value)? result,
}) {
return result?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Loading value)? loading,
TResult Function(_Empty value)? empty,
TResult Function(_Data value)? data,
TResult Function(_Result value)? result,
required TResult orElse(),
}) {
if (result != null) {
return result(this);
}
return orElse();
}
}
abstract class _Result implements TrainingState {
const factory _Result() = _$ResultImpl;
}

View File

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

View File

@@ -0,0 +1,71 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:remever/common/resources.dart';
import 'package:remever/common/widgets/typography.dart';
import 'package:remever/common/widgets/wspace.dart';
import 'package:remever/components/extensions/context.dart';
import 'package:remever/gen/assets.gen.dart';
import 'package:remever/router.gr.dart';
import 'package:remever/widgets/primary_button.dart';
class TrainingEmpty extends StatelessWidget {
const TrainingEmpty({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.bg,
appBar: _buildAppBar(context),
body: _buildMain(context),
);
}
/// Построение шапки
AppBar _buildAppBar(BuildContext context) {
return AppBar(
backgroundColor: AppColors.white,
shadowColor: Colors.transparent,
leading: GestureDetector(
onTap: () => context.back(),
child: const Icon(Icons.close, color: Colors.black),
),
centerTitle: true,
title: AppTypography(
'Тренировка',
type: SemiBold20px(),
color: AppColors.body_text,
),
);
}
Widget _buildMain(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16).r,
child: Column(
children: [
HSpace(90),
Assets.images.trainingEmpty.image(height: 127.h, width: 160.w),
HSpace(20),
AppTypography(
'К сожалению, у вас нет карточек для изучения',
type: SemiBold20px(),
maxLines: 3,
textAlign: TextAlign.center,
),
Spacer(),
PrimaryButton(
onTap: () {
context.router.replaceAll([CreateRoute()]);
},
child: AppTypography(
'Создать карточку',
type: Medium14px(),
color: Colors.white,
),
),
HSpace(90),
],
),
);
}
}

View File

@@ -0,0 +1,35 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:remever/common/resources.dart';
import 'package:remever/common/widgets/typography.dart';
class TrainingLoading extends StatelessWidget {
const TrainingLoading({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.bg,
appBar: _buildAppBar(context),
body: Center(child: CircularProgressIndicator(color: AppColors.primary)),
);
}
/// Построение шапки
AppBar _buildAppBar(BuildContext context) {
return AppBar(
backgroundColor: AppColors.white,
shadowColor: Colors.transparent,
leading: GestureDetector(
onTap: () => context.back(),
child: const Icon(Icons.close, color: Colors.black),
),
centerTitle: true,
title: AppTypography(
'Тренировка',
type: SemiBold20px(),
color: AppColors.body_text,
),
);
}
}

View File

@@ -0,0 +1,206 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:remever/common/resources.dart';
import 'package:remever/common/widgets/typography.dart';
import 'package:remever/common/widgets/w_if.dart';
import 'package:remever/common/widgets/wspace.dart';
import 'package:remever/components/extensions/context.dart';
import 'package:remever/components/extensions/state.dart';
import 'package:remever/models/training_dto.dart';
import 'package:remever/screens/training/cubit/training_cubit.dart';
import 'package:remever/screens/training/states/empty.dart';
import 'package:remever/screens/training/states/loading.dart';
import 'package:remever/screens/training/widgets/training_ticket.dart';
import 'package:remever/widgets/primary_button.dart';
@RoutePage()
class TrainingScreen extends StatelessWidget {
const TrainingScreen({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider<TrainingCubit>(
create: (BuildContext context) => TrainingCubit(),
child: _buildMain(),
);
}
///
/// Построение основного блока
///
Widget _buildMain() {
return PopScope(
canPop: false,
child: BlocBuilder<TrainingCubit, TrainingState>(
builder: (BuildContext context, state) {
return state.when(
loading: () => TrainingLoading(),
empty: () => TrainingEmpty(),
data: (data) => TrainingData(data: data),
result: () => Placeholder(),
);
},
),
);
}
}
class TrainingData extends StatefulWidget {
const TrainingData({super.key, required this.data});
final List<TrainingDto> data;
@override
State<TrainingData> createState() => _TrainingDataState();
}
class _TrainingDataState extends State<TrainingData> {
bool _showAnswer = false;
int _currentTicketIndex = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.bg,
appBar: _buildAppBar(context),
body: _buildMain(),
);
}
/// Построение шапки
AppBar _buildAppBar(BuildContext context) {
return AppBar(
backgroundColor: AppColors.white,
shadowColor: Colors.transparent,
leading: GestureDetector(
onTap: () => context.back(),
child: const Icon(Icons.close, color: Colors.black),
),
centerTitle: true,
title: AppTypography(
'Тренировка',
type: SemiBold20px(),
color: AppColors.body_text,
),
actions: [_buildCounter(), WSpace(16)],
);
}
Widget _buildCounter() {
return Center(
child: Container(
height: 24.h,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(100)).r,
color: AppColors.bg,
),
child: Padding(
padding: const EdgeInsets.all(3).r,
child: Center(
child: AppTypography(
'${_currentTicketIndex + 1} из ${widget.data.length}',
type: Regular14px(),
),
),
),
),
);
}
Widget _buildMain() {
return Column(
children: [_buildTickets(), _buildShowAnswerButton(), _buildActions()],
);
}
Widget _buildActions() {
return Padding(
padding: const EdgeInsets.all(16).r,
child: Row(
spacing: 8.r,
children: [
Flexible(
child: PrimaryButton(
onTap: () {
if (widget.data.length == _currentTicketIndex + 1) {
context.read<TrainingCubit>().toResultState();
return;
}
safeSetState(() => _currentTicketIndex++);
},
color: AppColors.danger,
child: AppTypography(
'Не помню',
type: Medium14px(),
color: Colors.white,
),
),
),
Flexible(
child: PrimaryButton(
child: AppTypography(
'Помню',
type: Medium14px(),
color: Colors.white,
),
onTap: () {
if (widget.data.length == _currentTicketIndex + 1) {
context.read<TrainingCubit>().toResultState();
return;
}
safeSetState(() => _currentTicketIndex++);
},
),
),
],
),
);
}
Widget _buildShowAnswerButton() {
return Wif(
condition: !_showAnswer,
builder:
(context) => PrimaryButton(
onTap: () {
safeSetState(() {
_showAnswer = true;
});
},
color: AppColors.secondary,
width: 170,
child: AppTypography(
'Показать ответ',
type: Medium14px(),
color: AppColors.primary,
),
),
);
}
Widget _buildTickets() {
return Expanded(
child: SingleChildScrollView(
physics: BouncingScrollPhysics(),
child: Padding(
padding: const EdgeInsets.all(16).r,
child: Column(
spacing: 8.r,
children: [
TrainingTicket(trainingDto: widget.data[_currentTicketIndex]),
Wif(
condition: _showAnswer,
builder:
(context) => TrainingTicket(
trainingDto: widget.data[_currentTicketIndex],
isAnswer: true,
),
),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,145 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:remever/common/resources.dart';
import 'package:remever/common/widgets/typography.dart';
import 'package:remever/common/widgets/w_if.dart';
import 'package:remever/common/widgets/wspace.dart';
import 'package:remever/components/extensions/context.dart';
import 'package:remever/database/database.dart';
import 'package:remever/models/training_dto.dart';
class TrainingTicket extends StatelessWidget {
const TrainingTicket({
super.key,
this.isAnswer = false,
required this.trainingDto,
});
final TrainingDto trainingDto;
final bool isAnswer;
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12).r,
color: Colors.white,
),
constraints: BoxConstraints(minHeight: 50.h),
child: Stack(
children: [
SizedBox(
height: 90.h,
width: double.infinity,
child: DecoratedBox(decoration: getDecoration()),
),
Padding(
padding: const EdgeInsets.all(12).r,
child: Column(
children: [
_buildCollectionInfo(),
_buildImage(),
HSpace(4),
_buildText(context),
],
),
),
],
),
);
}
Widget _buildCollectionInfo() {
final collection = trainingDto.collection;
return Wif(
condition: !isAnswer && collection != null,
builder: (context) {
return Padding(
padding: const EdgeInsets.only(bottom: 8).r,
child: Row(
children: [
SizedBox(
height: 24.h,
width: 24.w,
child: Wif(
condition: collection!.image != null,
builder:
(context) => ClipOval(
child: Image.file(
File(collection.image!),
fit: BoxFit.cover,
),
),
fallback:
(context) => Center(
child: AppTypography(
collection.title.substring(0, 1),
type: Bold34px(),
),
),
),
),
WSpace(4),
AppTypography(
collection.title,
type: Regular14px(),
color: AppColors.disabled,
),
],
),
);
},
fallback: (context) => Row(children: []),
);
}
/// Декорирование контейнера
BoxDecoration getDecoration() {
return BoxDecoration(
borderRadius: BorderRadius.circular(12).r,
gradient: LinearGradient(
colors: [
isAnswer ? AppColors.answer : AppColors.question,
Colors.white,
],
begin: Alignment.topLeft,
end: const Alignment(-0.5, 1),
stops: const [0.25, 0.25],
),
);
}
Widget _buildText(BuildContext context) {
final ticket = trainingDto.ticket;
return AppTypography(
isAnswer ? ticket.answer : ticket.question,
maxLines: 99,
type: Regular14px(),
);
}
Widget _buildImage() {
final ticket = trainingDto.ticket;
final String? imagePath =
isAnswer ? ticket.answerImage : ticket.questionImage;
return Wif(
condition: imagePath != null,
builder:
(context) => Padding(
padding: const EdgeInsets.only(right: 8).r,
child: SizedBox.square(
dimension: 100.r,
child: ClipRRect(
borderRadius: BorderRadius.circular(8).r,
child: Image.file(File(imagePath!), fit: BoxFit.cover),
),
),
),
);
}
}

View File

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

View File

@@ -1,4 +1,8 @@
import 'package:dio/dio.dart';
import 'package:injectable/injectable.dart'; import 'package:injectable/injectable.dart';
import 'package:remever/common/functions.dart';
import 'package:remever/common/services/api_client.dart';
import 'package:remever/common/typedef.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';
@@ -17,6 +21,10 @@ final class CollectionsService implements CollectionsInterface {
@override @override
Future<void> createCollection(CrudCollectionDto dto) async { Future<void> createCollection(CrudCollectionDto dto) async {
try {
createCollectionApi(dto);
} catch (_) {}
return await getIt<AppDatabase>().collectionsDao.createCollection(dto); return await getIt<AppDatabase>().collectionsDao.createCollection(dto);
} }
@@ -35,4 +43,74 @@ final class CollectionsService implements CollectionsInterface {
// TODO: implement makeCollectionPublic // TODO: implement makeCollectionPublic
throw UnimplementedError(); throw UnimplementedError();
} }
@override
Future<void> getCollectionsFromApi() async {
try {
final List<Json> items = [];
final perPage = 20;
final Response<dynamic> response = await apiClient.get(
'/collections',
queryParameters: <String, dynamic>{'perPage': perPage, 'page': 9999},
);
if (response.data['success'] != true) {
logger.logInfo('В методе getCollectionsFromApi success false');
throw Exception('Не удалось получить коллекции');
}
int totalCount = response.data['result']['totalCount'];
if (totalCount == 0) {
return;
}
int pagesCount = (totalCount / perPage).ceil();
int currentPage = 0;
while (pagesCount != currentPage) {
final Response<dynamic> response = await apiClient.get(
'/collections',
queryParameters: <String, dynamic>{
'perPage': perPage,
'page': currentPage + 1,
},
);
if (response.data['success'] != true) {
throw Exception('Не удалось получить $currentPage страницу журнала');
}
items.addAll(
(response.data['result']['items'] as List)
.cast<Map<String, dynamic>>(),
);
currentPage = currentPage + 1;
}
await getIt<AppDatabase>().collectionsDao.syncCollectionFromApi(items);
} catch (e, st) {
print('Response error $e');
logger.logError('Ошибка в методе getCollectionsFromApi', e, st);
}
}
Future<void> createCollectionApi(CrudCollectionDto dto) async {
try {
final Response<dynamic> response = await apiClient.post(
'/collections',
data: {
"title": dto.title,
"description": dto.desc,
"is_public": dto.isPublic,
},
);
} catch (e, st) {
print('Response error $e');
logger.logError('Ошибка в методе createCollectionApi', e, st);
}
}
} }

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

@@ -0,0 +1,168 @@
import 'package:flutter/material.dart';
class MarkdownReadMoreText extends StatefulWidget {
final String text;
final int trimLines;
final TextStyle style;
final TextStyle linkStyle;
final String expandText;
final String collapseText;
const MarkdownReadMoreText(
this.text, {
super.key,
this.trimLines = 3,
required this.style,
required this.linkStyle,
this.expandText = 'Развернуть',
this.collapseText = 'Свернуть',
});
@override
State<MarkdownReadMoreText> createState() => _MarkdownReadMoreTextState();
}
class _MarkdownReadMoreTextState extends State<MarkdownReadMoreText> {
bool _isExpanded = false;
@override
Widget build(BuildContext context) {
final spans = _parseMarkdown(widget.text);
final fullText = TextSpan(children: spans, style: widget.style);
return LayoutBuilder(
builder: (context, constraints) {
final textPainter = TextPainter(
text: fullText,
maxLines: widget.trimLines,
textDirection: TextDirection.ltr,
)..layout(maxWidth: constraints.maxWidth);
final isOverflowed = textPainter.didExceedMaxLines;
if (!isOverflowed) {
return SelectableText.rich(fullText);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SelectableText.rich(
_isExpanded ? fullText : _getTrimmedSpan(textPainter, spans),
),
GestureDetector(
onTap: () => setState(() => _isExpanded = !_isExpanded),
child: Text(
_isExpanded ? widget.collapseText : widget.expandText,
style: widget.linkStyle,
),
),
],
);
},
);
}
TextSpan _getTrimmedSpan(TextPainter painter, List<TextSpan> spans) {
final trimmedText = StringBuffer();
final remainingSpans = <TextSpan>[];
int charCount = 0;
final maxChars =
painter
.getPositionForOffset(Offset(painter.width, painter.height))
.offset;
for (final span in spans) {
final spanText = span.text ?? '';
if (charCount + spanText.length <= maxChars) {
remainingSpans.add(span);
charCount += spanText.length;
} else {
final remaining = maxChars - charCount;
if (remaining > 0) {
remainingSpans.add(
TextSpan(text: spanText.substring(0, remaining), style: span.style),
);
}
break;
}
}
return TextSpan(children: remainingSpans, style: widget.style);
}
List<TextSpan> _parseMarkdown(String text) {
final lines = text.split('\n');
final spans = <TextSpan>[];
for (var line in lines) {
final trimmed = line.trim();
// H1
if (trimmed.startsWith('# ') && trimmed.length > 2) {
spans.add(
TextSpan(
text: '${trimmed.substring(2)}\n',
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
);
continue;
}
// Нумерованный список
if (RegExp(r'^\d+\.\s').hasMatch(trimmed)) {
final match = RegExp(r'^(\d+)\.\s(.+)').firstMatch(trimmed);
if (match != null) {
spans.add(
TextSpan(
text: '${match.group(0)!}\n',
style: const TextStyle(fontSize: 16),
),
);
continue;
}
}
// Маркированный список
if (trimmed.startsWith('') && trimmed.length > 2) {
spans.add(
TextSpan(
text: '${trimmed.substring(2)}\n',
style: const TextStyle(fontSize: 16),
),
);
continue;
}
// Жирный текст
final boldRegex = RegExp(r'\*\*([^*]+)\*\*');
var remaining = line;
var boldProcessed = <TextSpan>[];
int lastEnd = 0;
for (final match in boldRegex.allMatches(line)) {
if (match.start > lastEnd) {
boldProcessed.add(
TextSpan(text: line.substring(lastEnd, match.start)),
);
}
boldProcessed.add(
TextSpan(
text: match.group(1),
style: const TextStyle(fontWeight: FontWeight.bold),
),
);
lastEnd = match.end;
}
if (lastEnd < line.length) {
boldProcessed.add(TextSpan(text: line.substring(lastEnd)));
}
boldProcessed.add(const TextSpan(text: '\n'));
spans.addAll(boldProcessed);
}
return spans;
}
}

View File

@@ -0,0 +1,95 @@
import 'package:flutter/material.dart';
import 'package:remever/common/resources.dart';
class MarkdownText extends StatelessWidget {
final String text;
final TextStyle? baseStyle;
const MarkdownText(this.text, {super.key, this.baseStyle});
@override
Widget build(BuildContext context) {
return SelectableText.rich(
TextSpan(
children: _parseMarkdown(text),
style:
baseStyle ??
const TextStyle(fontSize: 14, color: AppColors.body_text),
),
);
}
List<TextSpan> _parseMarkdown(String text) {
final lines = text.split('\n');
final spans = <TextSpan>[];
for (var line in lines) {
final trimmed = line.trim();
// H1
if (trimmed.startsWith('# ') && trimmed.length > 2) {
spans.add(
TextSpan(
text: '${trimmed.substring(2)}\n',
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
);
continue;
}
// Нумерованный список
if (RegExp(r'^\d+\.\s').hasMatch(trimmed)) {
final match = RegExp(r'^(\d+)\.\s(.+)').firstMatch(trimmed);
if (match != null) {
spans.add(
TextSpan(
text: '${match.group(0)!}\n',
style: const TextStyle(fontSize: 14),
),
);
continue;
}
}
// Маркированный список
if (trimmed.startsWith('') && trimmed.length > 2) {
spans.add(
TextSpan(
text: '${trimmed.substring(2)}\n',
style: const TextStyle(fontSize: 14),
),
);
continue;
}
// Жирный текст
final boldRegex = RegExp(r'\*\*([^*]+)\*\*');
var remaining = line;
var boldProcessed = <TextSpan>[];
int lastEnd = 0;
for (final match in boldRegex.allMatches(line)) {
if (match.start > lastEnd) {
boldProcessed.add(
TextSpan(text: line.substring(lastEnd, match.start)),
);
}
boldProcessed.add(
TextSpan(
text: match.group(1),
style: const TextStyle(fontWeight: FontWeight.bold),
),
);
lastEnd = match.end;
}
if (lastEnd < line.length) {
boldProcessed.add(TextSpan(text: line.substring(lastEnd)));
}
boldProcessed.add(const TextSpan(text: '\n'));
spans.addAll(boldProcessed);
}
return spans;
}
}

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"
@@ -1029,14 +1053,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.5.0" version: "1.5.0"
readmore:
dependency: "direct main"
description:
name: readmore
sha256: e8fca2bd397b86342483b409e2ec26f06560a5963aceaa39b27f30722b506187
url: "https://pub.dev"
source: hosted
version: "3.0.0"
recase: recase:
dependency: transitive dependency: transitive
description: description:

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
@@ -33,11 +33,13 @@ dependencies:
fps_widget: ^1.0.1+2 fps_widget: ^1.0.1+2
provider: ^6.1.2 provider: ^6.1.2
freezed: ^2.5.8 freezed: ^2.5.8
readmore: ^3.0.0
fluttertoast: ^8.2.12 fluttertoast: ^8.2.12
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