diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index e8f9d11..4a2d90f 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -3,7 +3,7 @@ name: Android Release on: [push, pull_request] env: - flutter_version: "3.19.0" + flutter_version: "3.22.0" java_version: "12.x" flutter_channel: "stable" diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index b5f7524..27c7c43 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -3,7 +3,7 @@ name: Eatvio App Android Test on: [pull_request] env: - flutter_version: "3.19.0" + flutter_version: "3.22.0" java_version: "12.x" flutter_channel: "stable" diff --git a/analysis_options.yaml b/analysis_options.yaml index 9722fb4..22f0401 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,4 +1,6 @@ analyzer: + errors: + invalid_annotation_target: ignore plugins: - dart_code_metrics diff --git a/android/app/build.gradle b/android/app/build.gradle index 4c483ee..7652369 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,67 +1,59 @@ -plugins{ - id 'com.android.application' - id 'kotlin-android' +plugins { + id "com.android.application" + id "kotlin-android" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "dev.flutter.flutter-gradle-plugin" } + def localProperties = new Properties() -def localPropertiesFile = rootProject.file('local.properties') +def localPropertiesFile = rootProject.file("local.properties") if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> + localPropertiesFile.withReader("UTF-8") { reader -> localProperties.load(reader) } } -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} - -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +def flutterVersionCode = localProperties.getProperty("flutter.versionCode") if (flutterVersionCode == null) { - flutterVersionCode = '1' + flutterVersionCode = "1" } -def flutterVersionName = localProperties.getProperty('flutter.versionName') +def flutterVersionName = localProperties.getProperty("flutter.versionName") if (flutterVersionName == null) { - flutterVersionName = '1.0' + flutterVersionName = "1.0" } -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - android { - compileSdkVersion flutter.compileSdkVersion + namespace = "com.eatvioapp.app" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - } - - lintOptions { - disable 'InvalidPackage' + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.eatvioapp.app" - minSdkVersion 23 - targetSdkVersion flutter.targetSdkVersion - multiDexEnabled true - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName + applicationId = "com.eatvioapp.app" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdk = 23 + targetSdk = flutter.targetSdkVersion + versionCode = flutterVersionCode.toInteger() + versionName = flutterVersionName } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig signingConfigs.debug + signingConfig = signingConfigs.debug } } } flutter { - source '../..' + source = "../.." } -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation "androidx.multidex:multidex:2.0.1" -} diff --git a/android/build.gradle b/android/build.gradle index d7f8aaf..5869be3 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,16 +1,3 @@ -buildscript { - ext.kotlin_version = '1.7.20' - repositories { - google() - jcenter() - } - - dependencies { - classpath 'com.android.tools.build:gradle:7.2.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - allprojects { repositories { google() diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 6b66533..aeaff6f 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip diff --git a/android/settings.gradle b/android/settings.gradle index 44e62bc..536165d 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,11 +1,25 @@ -include ':app' +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} -def flutterSdkPath = properties.getProperty("flutter.sdk") -assert flutterSdkPath != null, "flutter.sdk not set in local.properties" -apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "7.3.0" apply false + id "org.jetbrains.kotlin.android" version "1.7.10" apply false +} + +include ":app" diff --git a/assets/fonts/MulishBold.ttf b/assets/fonts/MulishBold.ttf index 0c1eed7..e69de29 100644 Binary files a/assets/fonts/MulishBold.ttf and b/assets/fonts/MulishBold.ttf differ diff --git a/assets/fonts/MulishRegular.ttf b/assets/fonts/MulishRegular.ttf index 0971518..e69de29 100644 Binary files a/assets/fonts/MulishRegular.ttf and b/assets/fonts/MulishRegular.ttf differ diff --git a/assets/fonts/MulishSemiBold.ttf b/assets/fonts/MulishSemiBold.ttf index 9ac1fc8..e69de29 100644 Binary files a/assets/fonts/MulishSemiBold.ttf and b/assets/fonts/MulishSemiBold.ttf differ diff --git a/assets/fonts/SegoeUISemibold.ttf b/assets/fonts/SegoeUISemibold.ttf index 46b3b99..e69de29 100644 Binary files a/assets/fonts/SegoeUISemibold.ttf and b/assets/fonts/SegoeUISemibold.ttf differ diff --git a/devtools_options.yaml b/devtools_options.yaml index 7e7e7f6..5c27c3e 100644 --- a/devtools_options.yaml +++ b/devtools_options.yaml @@ -1 +1,2 @@ extensions: + - provider: true \ No newline at end of file diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 29940b0..68ec53f 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -156,7 +156,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a6b826d..5e31d3d 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ navigatorKey = GlobalKey(); + + static GoRouter appRouter = GoRouter( + initialLocation: '/auth-screen', + navigatorKey: navigatorKey, + routes: [ + GoRoute( + path: '/auth-screen', + name: 'authScreen', + redirect: (context, state) async { + if (await TokenStorage.haveTokenInStorage()) return '/my-profile-screen'; + return null; + }, + builder: (context, state) => AuthScreen.builder(context), + routes: [ + GoRoute( + path: 'language-select-screen', + name: 'languageSelectScreen', + builder: (context, state) => LanguageSelectScreen.builder(context), + routes: [ + GoRoute( + path: 'credentials-screen', + name: 'credentialsScreen', + builder: (context, state) => CredentialsScreen.builder(context, state.extra), + routes: [ + GoRoute( + path: 'name-screen', + name: 'nameScreen', + builder: (context, state) => NameScreen.builder(context, state.extra), + routes: [ + GoRoute( + path: 'gender-screen', + name: 'genderScreen', + builder: (context, state) => GenderScreen.builder(context, state.extra), + routes: [ + GoRoute( + path: 'date-of-birth-screen', + name: 'dateOfBirthScreen', + builder: (context, state) => DateOfBirthScreen.builder(context, state.extra), + routes: [ + GoRoute( + path: 'city-of-living-screen', + name: 'cityOfLivingScreen', + builder: (context, state) => CityOfLivingScreen.builder(context, state.extra), + routes: [ + GoRoute( + path: 'body-circumferences-screen', + name: 'bodyCircumferencesScreen', + builder: (context, state) => BodyCircumferencesScreen.builder(context, state.extra), + routes: [ + GoRoute( + path: 'weight-screen', + name: 'weightScreen', + builder: (context, state) => WeightScreen.builder(context, state.extra), + routes: [ + GoRoute( + path: 'height-screen', + name: 'heightScreen', + builder: (context, state) => HeightScreen.builder(context, state.extra), + routes: [ + GoRoute( + path: 'blood-pressure-screen', + name: 'bloodPressureScreen', + builder: (context, state) => BloodPressureScreen.builder(context, state.extra), + routes: [ + GoRoute( + path: 'favorite-sports-screen', + name: 'favoriteSportsScreen', + builder: (context, state) => FavoriteSportsScreen.builder(context, state.extra), + routes: [ + GoRoute( + name: 'allergiesScreen', + path: 'allergiesScreen', + builder: (context, state) => AllergiesScreen.builder(context, state.extra), + routes: [ + GoRoute( + name: 'medicineIntakeScreen', + path: 'medicine-intake-screen', + builder: (context, state) => MedicineIntakeScreen.builder(context, state.extra), + ), + GoRoute( + name: 'favoriteFoodsScreen', + path: 'favorite-foods-screen', + builder: (context, state) => FavoriteFoodsScreen.builder(context, state.extra), + routes: [ + GoRoute( + path: 'additional-information-screen', + name: 'additionalInformationScreen', + builder: (context, state) => AdditionalInformationScreen.builder(context, state.extra), + ), + ], + ), + ], + ), + ], + ), + ], + ), + ], + ), + ], + ), + ], + ), + ], + ), + ], + ), + ], + ), + ], + ), + ], + ), + ], + ), + ], + ), + ShellRoute( + builder: (context, state, child) { + return BottomNavigationBarWidget(child: child); + }, + routes: [ + GoRoute( + path: '/my-profile-screen', + name: 'myProfileScreen', + builder: (context, state) => MyProfileScreen(), + ), + GoRoute( + path: '/feed-screen', + name: 'feedScreen', + builder: (context, state) => FeedScreen(), + routes: [ + GoRoute( + path: 'blog-post-create-screen', + name: 'blogPostCreateScreen', + builder: (context, state) => BlogPostCreateScreen.builder(context), + ), + GoRoute( + path: 'blog-post-screen', + name: 'blogPostScreen', + builder: (context, state) => BlogPostScreen.builder( + context, + state.extra.toString(), + ), + routes: [ + GoRoute( + path: 'blog-post-edit-screen', + name: 'blogPostEditScreen', + builder: (context, state) => BlogPostEditScreen.builder( + context, + state.extra, + ), + ), + ], + ), + ], + ), + GoRoute( + path: '/diary-screen', + name: 'diaryScreen', + builder: (context, state) => DiaryEditScreen(), + ), + GoRoute( + path: '/chat-select-screen', + name: 'chatSelectScreen', + builder: (context, state) => ChatSelectScreen(), + routes: [ + GoRoute( + path: 'chat-screen', + name: 'chatScreen', + builder: (context, state) => ChatScreen(chatId: state.extra as String), + ), + ], + ), + ], + ), + GoRoute( + path: '/my-settings-screen', + name: 'mySettingsScreen', + builder: (context, state) => MySettingsScreen.builder(context), + ), + GoRoute( + path: '/recipe-select-screen', + name: 'recipeSelectScreen', + builder: (context, state) => RecipeSelectScreen.builder(context), + ), + GoRoute( + path: '/diet-select-screen', + name: 'dietSelectScreen', + builder: (context, state) => DietSelectScreen.builder(context), + ), + GoRoute( + path: '/diet-create-screen', + name: 'dietCreateScreen', + builder: (context, state) => DietCreateScreen.builder(context), + ), + GoRoute( + path: '/plan-select-screen', + name: 'planSelectScreen', + builder: (context, state) => PlanSelectScreen.builder(context), + ), + GoRoute( + path: '/glossary-screen', + name: 'glossaryScreen', + builder: (context, state) => GlossaryScreen.builder(context), + ), + GoRoute( + path: '/photo-view-screen/:index', + name: 'photoViewScreen', + builder: (context, state) => PhotoViewScreen( + imageUrls: state.extra as List, + index: int.parse(state.pathParameters['index']!), + ), + ), + ], + ); +} diff --git a/lib/core/app_export.dart b/lib/core/app_export.dart index 6c17ee1..233b58c 100644 --- a/lib/core/app_export.dart +++ b/lib/core/app_export.dart @@ -4,12 +4,10 @@ export 'package:eatvio_app/core/errors/exceptions.dart'; export 'package:eatvio_app/core/network/network_info.dart'; export 'package:eatvio_app/core/utils/image_constant.dart'; export 'package:eatvio_app/core/utils/logger.dart'; -export 'package:eatvio_app/core/utils/navigator_service.dart'; export 'package:eatvio_app/core/utils/pref_utils.dart'; export 'package:eatvio_app/core/utils/size_utils.dart'; export 'package:eatvio_app/data/models/selectionPopupModel/selection_popup_model.dart'; export 'package:eatvio_app/localization/app_localization.dart'; -export 'package:eatvio_app/routes/app_routes.dart'; export 'package:eatvio_app/theme/app_decoration.dart'; export 'package:eatvio_app/theme/custom_text_style.dart'; export 'package:eatvio_app/theme/theme_helper.dart'; diff --git a/lib/core/token_storage.dart b/lib/core/token_storage.dart new file mode 100644 index 0000000..82d2935 --- /dev/null +++ b/lib/core/token_storage.dart @@ -0,0 +1,32 @@ +import 'package:dio/dio.dart'; +import 'package:eatvio_app/locator_service.dart'; +import 'package:hive_flutter/hive_flutter.dart'; + +abstract class TokenStorage { + static addTokenToDioHeader(String token) { + sl().options.headers['Authorization'] = 'Bearer $token'; + } + + static haveTokenInStorage() async { + final box = await Hive.openBox('token'); + final token = box.get('token'); + if (token == null) { + return false; + } else { + addTokenToDioHeader(token); + return true; + } + } + + static getTokenFromStorage() async => await Hive.openBox('token') + ..get('token'); + + static saveTokenInStorage(String token) async { + await Hive.openBox('token') + ..put('token', token); + addTokenToDioHeader(token); + } + + static removeTokenInStorage(String token) async => await Hive.openBox('token') + ..delete('token'); +} diff --git a/lib/core/utils/navigator_service.dart b/lib/core/utils/navigator_service.dart deleted file mode 100644 index e5ea835..0000000 --- a/lib/core/utils/navigator_service.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:flutter/material.dart'; - -class NavigatorService { - static GlobalKey navigatorKey = GlobalKey(); - - static Future pushNamed(String routeName, - {dynamic arguments}) async { - return navigatorKey.currentState - ?.pushNamed(routeName, arguments: arguments); - } - - static void goBack() { - return navigatorKey.currentState?.pop(); - } - - static Future pushNamedAndRemoveUntil(String routeName, - {bool routePredicate = false, dynamic arguments}) async { - return navigatorKey.currentState?.pushNamedAndRemoveUntil( - routeName, (route) => routePredicate, - arguments: arguments); - } - - static Future popAndPushNamed(String routeName, - {dynamic arguments}) async { - return navigatorKey.currentState - ?.popAndPushNamed(routeName, arguments: arguments); - } -} diff --git a/lib/core/utils/progress_dialog_utils.dart b/lib/core/utils/progress_dialog_utils.dart index b111e96..ce4da75 100644 --- a/lib/core/utils/progress_dialog_utils.dart +++ b/lib/core/utils/progress_dialog_utils.dart @@ -1,17 +1,15 @@ +import 'package:eatvio_app/app_router.dart'; import 'package:flutter/material.dart'; -import 'package:eatvio_app/core/app_export.dart'; class ProgressDialogUtils { static bool isProgressVisible = false; ///common method for showing progress dialog - static void showProgressDialog( - {BuildContext? context, isCancellable = false}) async { - if (!isProgressVisible && - NavigatorService.navigatorKey.currentState?.overlay?.context != null) { + static void showProgressDialog({BuildContext? context, isCancellable = false}) async { + if (!isProgressVisible && AppRouter.navigatorKey.currentState?.overlay?.context != null) { showDialog( barrierDismissible: isCancellable, - context: NavigatorService.navigatorKey.currentState!.overlay!.context, + context: AppRouter.navigatorKey.currentState!.overlay!.context, builder: (BuildContext context) { return Center( child: CircularProgressIndicator.adaptive( @@ -28,9 +26,7 @@ class ProgressDialogUtils { ///common method for hiding progress dialog static void hideProgressDialog() { - if (isProgressVisible) - Navigator.pop( - NavigatorService.navigatorKey.currentState!.overlay!.context); + if (isProgressVisible) Navigator.pop(AppRouter.navigatorKey.currentState!.overlay!.context); isProgressVisible = false; } } diff --git a/lib/data/models/tokenModel/token_model.dart b/lib/data/models/tokenModel/token_model.dart index 17ab28a..3aa1d3f 100644 --- a/lib/data/models/tokenModel/token_model.dart +++ b/lib/data/models/tokenModel/token_model.dart @@ -1,5 +1,5 @@ import 'package:hive/hive.dart'; - +/// https://pub.dev/packages/flutter_secure_storage TODO replace hive to secure_storage for token storage @HiveType(typeId: 0) class TokenModel { @HiveField(0) diff --git a/lib/features/chat/models/chat_message_model.dart b/lib/features/chat/models/chat_message_model.dart new file mode 100644 index 0000000..220df3b --- /dev/null +++ b/lib/features/chat/models/chat_message_model.dart @@ -0,0 +1,18 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'chat_message_model.freezed.dart'; + +part 'chat_message_model.g.dart'; + +@freezed +class ChatMessageModel with _$ChatMessageModel { + const factory ChatMessageModel({ + required int id, + required String data, + @JsonKey(name: 'media_data') required List mediaData, + @JsonKey(name: 'author_id') required int authorId, + @JsonKey(name: 'updated_at') required String updatedAt, + }) = _ChatMessageModel; + + factory ChatMessageModel.fromJson(Map json) => _$ChatMessageModelFromJson(json); +} diff --git a/lib/features/chat/models/chat_message_model.freezed.dart b/lib/features/chat/models/chat_message_model.freezed.dart new file mode 100644 index 0000000..6185608 --- /dev/null +++ b/lib/features/chat/models/chat_message_model.freezed.dart @@ -0,0 +1,257 @@ +// 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 'chat_message_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(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'); + +ChatMessageModel _$ChatMessageModelFromJson(Map json) { + return _ChatMessageModel.fromJson(json); +} + +/// @nodoc +mixin _$ChatMessageModel { + int get id => throw _privateConstructorUsedError; + String get data => throw _privateConstructorUsedError; + @JsonKey(name: 'media_data') + List get mediaData => throw _privateConstructorUsedError; + @JsonKey(name: 'author_id') + int get authorId => throw _privateConstructorUsedError; + @JsonKey(name: 'updated_at') + String get updatedAt => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $ChatMessageModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ChatMessageModelCopyWith<$Res> { + factory $ChatMessageModelCopyWith( + ChatMessageModel value, $Res Function(ChatMessageModel) then) = + _$ChatMessageModelCopyWithImpl<$Res, ChatMessageModel>; + @useResult + $Res call( + {int id, + String data, + @JsonKey(name: 'media_data') List mediaData, + @JsonKey(name: 'author_id') int authorId, + @JsonKey(name: 'updated_at') String updatedAt}); +} + +/// @nodoc +class _$ChatMessageModelCopyWithImpl<$Res, $Val extends ChatMessageModel> + implements $ChatMessageModelCopyWith<$Res> { + _$ChatMessageModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? data = null, + Object? mediaData = null, + Object? authorId = null, + Object? updatedAt = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + data: null == data + ? _value.data + : data // ignore: cast_nullable_to_non_nullable + as String, + mediaData: null == mediaData + ? _value.mediaData + : mediaData // ignore: cast_nullable_to_non_nullable + as List, + authorId: null == authorId + ? _value.authorId + : authorId // ignore: cast_nullable_to_non_nullable + as int, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ChatMessageModelImplCopyWith<$Res> + implements $ChatMessageModelCopyWith<$Res> { + factory _$$ChatMessageModelImplCopyWith(_$ChatMessageModelImpl value, + $Res Function(_$ChatMessageModelImpl) then) = + __$$ChatMessageModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {int id, + String data, + @JsonKey(name: 'media_data') List mediaData, + @JsonKey(name: 'author_id') int authorId, + @JsonKey(name: 'updated_at') String updatedAt}); +} + +/// @nodoc +class __$$ChatMessageModelImplCopyWithImpl<$Res> + extends _$ChatMessageModelCopyWithImpl<$Res, _$ChatMessageModelImpl> + implements _$$ChatMessageModelImplCopyWith<$Res> { + __$$ChatMessageModelImplCopyWithImpl(_$ChatMessageModelImpl _value, + $Res Function(_$ChatMessageModelImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? data = null, + Object? mediaData = null, + Object? authorId = null, + Object? updatedAt = null, + }) { + return _then(_$ChatMessageModelImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + data: null == data + ? _value.data + : data // ignore: cast_nullable_to_non_nullable + as String, + mediaData: null == mediaData + ? _value._mediaData + : mediaData // ignore: cast_nullable_to_non_nullable + as List, + authorId: null == authorId + ? _value.authorId + : authorId // ignore: cast_nullable_to_non_nullable + as int, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ChatMessageModelImpl implements _ChatMessageModel { + const _$ChatMessageModelImpl( + {required this.id, + required this.data, + @JsonKey(name: 'media_data') required final List mediaData, + @JsonKey(name: 'author_id') required this.authorId, + @JsonKey(name: 'updated_at') required this.updatedAt}) + : _mediaData = mediaData; + + factory _$ChatMessageModelImpl.fromJson(Map json) => + _$$ChatMessageModelImplFromJson(json); + + @override + final int id; + @override + final String data; + final List _mediaData; + @override + @JsonKey(name: 'media_data') + List get mediaData { + if (_mediaData is EqualUnmodifiableListView) return _mediaData; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_mediaData); + } + + @override + @JsonKey(name: 'author_id') + final int authorId; + @override + @JsonKey(name: 'updated_at') + final String updatedAt; + + @override + String toString() { + return 'ChatMessageModel(id: $id, data: $data, mediaData: $mediaData, authorId: $authorId, updatedAt: $updatedAt)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ChatMessageModelImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.data, data) || other.data == data) && + const DeepCollectionEquality() + .equals(other._mediaData, _mediaData) && + (identical(other.authorId, authorId) || + other.authorId == authorId) && + (identical(other.updatedAt, updatedAt) || + other.updatedAt == updatedAt)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, id, data, + const DeepCollectionEquality().hash(_mediaData), authorId, updatedAt); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ChatMessageModelImplCopyWith<_$ChatMessageModelImpl> get copyWith => + __$$ChatMessageModelImplCopyWithImpl<_$ChatMessageModelImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$ChatMessageModelImplToJson( + this, + ); + } +} + +abstract class _ChatMessageModel implements ChatMessageModel { + const factory _ChatMessageModel( + {required final int id, + required final String data, + @JsonKey(name: 'media_data') required final List mediaData, + @JsonKey(name: 'author_id') required final int authorId, + @JsonKey(name: 'updated_at') required final String updatedAt}) = + _$ChatMessageModelImpl; + + factory _ChatMessageModel.fromJson(Map json) = + _$ChatMessageModelImpl.fromJson; + + @override + int get id; + @override + String get data; + @override + @JsonKey(name: 'media_data') + List get mediaData; + @override + @JsonKey(name: 'author_id') + int get authorId; + @override + @JsonKey(name: 'updated_at') + String get updatedAt; + @override + @JsonKey(ignore: true) + _$$ChatMessageModelImplCopyWith<_$ChatMessageModelImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/features/chat/models/chat_message_model.g.dart b/lib/features/chat/models/chat_message_model.g.dart new file mode 100644 index 0000000..8b28b74 --- /dev/null +++ b/lib/features/chat/models/chat_message_model.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'chat_message_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ChatMessageModelImpl _$$ChatMessageModelImplFromJson( + Map json) => + _$ChatMessageModelImpl( + id: (json['id'] as num).toInt(), + data: json['data'] as String, + mediaData: json['media_data'] as List, + authorId: (json['author_id'] as num).toInt(), + updatedAt: json['updated_at'] as String, + ); + +Map _$$ChatMessageModelImplToJson( + _$ChatMessageModelImpl instance) => + { + 'id': instance.id, + 'data': instance.data, + 'media_data': instance.mediaData, + 'author_id': instance.authorId, + 'updated_at': instance.updatedAt, + }; diff --git a/lib/features/chat/models/chat_room_model.dart b/lib/features/chat/models/chat_room_model.dart new file mode 100644 index 0000000..1fa5afe --- /dev/null +++ b/lib/features/chat/models/chat_room_model.dart @@ -0,0 +1,20 @@ +import 'package:eatvio_app/features/chat/models/chat_message_model.dart'; +import 'package:eatvio_app/features/chat/models/chat_room_settings_model.dart'; +import 'package:eatvio_app/features/chat/models/chat_user_model.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'chat_room_model.freezed.dart'; + +part 'chat_room_model.g.dart'; + +@freezed +class ChatRoomModel with _$ChatRoomModel { + const factory ChatRoomModel({ + required String id, + @JsonKey(name: 'chat_room_setting') required ChatRoomSettingsModel chatRoomSettings, + required List messages, + required Map users, + }) = _ChatRoomModel; + + factory ChatRoomModel.fromJson(Map json) => _$ChatRoomModelFromJson(json); +} diff --git a/lib/features/chat/models/chat_room_model.freezed.dart b/lib/features/chat/models/chat_room_model.freezed.dart new file mode 100644 index 0000000..b8b4ce4 --- /dev/null +++ b/lib/features/chat/models/chat_room_model.freezed.dart @@ -0,0 +1,255 @@ +// 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 'chat_room_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(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'); + +ChatRoomModel _$ChatRoomModelFromJson(Map json) { + return _ChatRoomModel.fromJson(json); +} + +/// @nodoc +mixin _$ChatRoomModel { + String get id => throw _privateConstructorUsedError; + @JsonKey(name: 'chat_room_setting') + ChatRoomSettingsModel get chatRoomSettings => + throw _privateConstructorUsedError; + List get messages => throw _privateConstructorUsedError; + Map get users => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $ChatRoomModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ChatRoomModelCopyWith<$Res> { + factory $ChatRoomModelCopyWith( + ChatRoomModel value, $Res Function(ChatRoomModel) then) = + _$ChatRoomModelCopyWithImpl<$Res, ChatRoomModel>; + @useResult + $Res call( + {String id, + @JsonKey(name: 'chat_room_setting') + ChatRoomSettingsModel chatRoomSettings, + List messages, + Map users}); + + $ChatRoomSettingsModelCopyWith<$Res> get chatRoomSettings; +} + +/// @nodoc +class _$ChatRoomModelCopyWithImpl<$Res, $Val extends ChatRoomModel> + implements $ChatRoomModelCopyWith<$Res> { + _$ChatRoomModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? chatRoomSettings = null, + Object? messages = null, + Object? users = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + chatRoomSettings: null == chatRoomSettings + ? _value.chatRoomSettings + : chatRoomSettings // ignore: cast_nullable_to_non_nullable + as ChatRoomSettingsModel, + messages: null == messages + ? _value.messages + : messages // ignore: cast_nullable_to_non_nullable + as List, + users: null == users + ? _value.users + : users // ignore: cast_nullable_to_non_nullable + as Map, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $ChatRoomSettingsModelCopyWith<$Res> get chatRoomSettings { + return $ChatRoomSettingsModelCopyWith<$Res>(_value.chatRoomSettings, + (value) { + return _then(_value.copyWith(chatRoomSettings: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$ChatRoomModelImplCopyWith<$Res> + implements $ChatRoomModelCopyWith<$Res> { + factory _$$ChatRoomModelImplCopyWith( + _$ChatRoomModelImpl value, $Res Function(_$ChatRoomModelImpl) then) = + __$$ChatRoomModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String id, + @JsonKey(name: 'chat_room_setting') + ChatRoomSettingsModel chatRoomSettings, + List messages, + Map users}); + + @override + $ChatRoomSettingsModelCopyWith<$Res> get chatRoomSettings; +} + +/// @nodoc +class __$$ChatRoomModelImplCopyWithImpl<$Res> + extends _$ChatRoomModelCopyWithImpl<$Res, _$ChatRoomModelImpl> + implements _$$ChatRoomModelImplCopyWith<$Res> { + __$$ChatRoomModelImplCopyWithImpl( + _$ChatRoomModelImpl _value, $Res Function(_$ChatRoomModelImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? chatRoomSettings = null, + Object? messages = null, + Object? users = null, + }) { + return _then(_$ChatRoomModelImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + chatRoomSettings: null == chatRoomSettings + ? _value.chatRoomSettings + : chatRoomSettings // ignore: cast_nullable_to_non_nullable + as ChatRoomSettingsModel, + messages: null == messages + ? _value._messages + : messages // ignore: cast_nullable_to_non_nullable + as List, + users: null == users + ? _value._users + : users // ignore: cast_nullable_to_non_nullable + as Map, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ChatRoomModelImpl implements _ChatRoomModel { + const _$ChatRoomModelImpl( + {required this.id, + @JsonKey(name: 'chat_room_setting') required this.chatRoomSettings, + required final List messages, + required final Map users}) + : _messages = messages, + _users = users; + + factory _$ChatRoomModelImpl.fromJson(Map json) => + _$$ChatRoomModelImplFromJson(json); + + @override + final String id; + @override + @JsonKey(name: 'chat_room_setting') + final ChatRoomSettingsModel chatRoomSettings; + final List _messages; + @override + List get messages { + if (_messages is EqualUnmodifiableListView) return _messages; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_messages); + } + + final Map _users; + @override + Map get users { + if (_users is EqualUnmodifiableMapView) return _users; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_users); + } + + @override + String toString() { + return 'ChatRoomModel(id: $id, chatRoomSettings: $chatRoomSettings, messages: $messages, users: $users)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ChatRoomModelImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.chatRoomSettings, chatRoomSettings) || + other.chatRoomSettings == chatRoomSettings) && + const DeepCollectionEquality().equals(other._messages, _messages) && + const DeepCollectionEquality().equals(other._users, _users)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + id, + chatRoomSettings, + const DeepCollectionEquality().hash(_messages), + const DeepCollectionEquality().hash(_users)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ChatRoomModelImplCopyWith<_$ChatRoomModelImpl> get copyWith => + __$$ChatRoomModelImplCopyWithImpl<_$ChatRoomModelImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ChatRoomModelImplToJson( + this, + ); + } +} + +abstract class _ChatRoomModel implements ChatRoomModel { + const factory _ChatRoomModel( + {required final String id, + @JsonKey(name: 'chat_room_setting') + required final ChatRoomSettingsModel chatRoomSettings, + required final List messages, + required final Map users}) = _$ChatRoomModelImpl; + + factory _ChatRoomModel.fromJson(Map json) = + _$ChatRoomModelImpl.fromJson; + + @override + String get id; + @override + @JsonKey(name: 'chat_room_setting') + ChatRoomSettingsModel get chatRoomSettings; + @override + List get messages; + @override + Map get users; + @override + @JsonKey(ignore: true) + _$$ChatRoomModelImplCopyWith<_$ChatRoomModelImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/features/chat/models/chat_room_model.g.dart b/lib/features/chat/models/chat_room_model.g.dart new file mode 100644 index 0000000..4ab3867 --- /dev/null +++ b/lib/features/chat/models/chat_room_model.g.dart @@ -0,0 +1,29 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'chat_room_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ChatRoomModelImpl _$$ChatRoomModelImplFromJson(Map json) => + _$ChatRoomModelImpl( + id: json['id'] as String, + chatRoomSettings: ChatRoomSettingsModel.fromJson( + json['chat_room_setting'] as Map), + messages: (json['messages'] as List) + .map((e) => ChatMessageModel.fromJson(e as Map)) + .toList(), + users: (json['users'] as Map).map( + (k, e) => + MapEntry(k, ChatUserModel.fromJson(e as Map)), + ), + ); + +Map _$$ChatRoomModelImplToJson(_$ChatRoomModelImpl instance) => + { + 'id': instance.id, + 'chat_room_setting': instance.chatRoomSettings, + 'messages': instance.messages, + 'users': instance.users, + }; diff --git a/lib/features/chat/models/chat_room_settings_model.dart b/lib/features/chat/models/chat_room_settings_model.dart new file mode 100644 index 0000000..7262c9a --- /dev/null +++ b/lib/features/chat/models/chat_room_settings_model.dart @@ -0,0 +1,18 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'chat_room_settings_model.freezed.dart'; + +part 'chat_room_settings_model.g.dart'; + +@freezed +class ChatRoomSettingsModel with _$ChatRoomSettingsModel { + const factory ChatRoomSettingsModel({ + required int id, + @JsonKey(name: 'room_name') required String roomName, + required String state, + @JsonKey(name: 'access_level') required String accessLevel, + @JsonKey(name: 'updated_at') required String updatedAt, + }) = _ChatRoomSettingsModel; + + factory ChatRoomSettingsModel.fromJson(Map json) => _$ChatRoomSettingsModelFromJson(json); +} diff --git a/lib/features/chat/models/chat_room_settings_model.freezed.dart b/lib/features/chat/models/chat_room_settings_model.freezed.dart new file mode 100644 index 0000000..4f21755 --- /dev/null +++ b/lib/features/chat/models/chat_room_settings_model.freezed.dart @@ -0,0 +1,254 @@ +// 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 'chat_room_settings_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(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'); + +ChatRoomSettingsModel _$ChatRoomSettingsModelFromJson( + Map json) { + return _ChatRoomSettingsModel.fromJson(json); +} + +/// @nodoc +mixin _$ChatRoomSettingsModel { + int get id => throw _privateConstructorUsedError; + @JsonKey(name: 'room_name') + String get roomName => throw _privateConstructorUsedError; + String get state => throw _privateConstructorUsedError; + @JsonKey(name: 'access_level') + String get accessLevel => throw _privateConstructorUsedError; + @JsonKey(name: 'updated_at') + String get updatedAt => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $ChatRoomSettingsModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ChatRoomSettingsModelCopyWith<$Res> { + factory $ChatRoomSettingsModelCopyWith(ChatRoomSettingsModel value, + $Res Function(ChatRoomSettingsModel) then) = + _$ChatRoomSettingsModelCopyWithImpl<$Res, ChatRoomSettingsModel>; + @useResult + $Res call( + {int id, + @JsonKey(name: 'room_name') String roomName, + String state, + @JsonKey(name: 'access_level') String accessLevel, + @JsonKey(name: 'updated_at') String updatedAt}); +} + +/// @nodoc +class _$ChatRoomSettingsModelCopyWithImpl<$Res, + $Val extends ChatRoomSettingsModel> + implements $ChatRoomSettingsModelCopyWith<$Res> { + _$ChatRoomSettingsModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? roomName = null, + Object? state = null, + Object? accessLevel = null, + Object? updatedAt = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + roomName: null == roomName + ? _value.roomName + : roomName // ignore: cast_nullable_to_non_nullable + as String, + state: null == state + ? _value.state + : state // ignore: cast_nullable_to_non_nullable + as String, + accessLevel: null == accessLevel + ? _value.accessLevel + : accessLevel // ignore: cast_nullable_to_non_nullable + as String, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ChatRoomSettingsModelImplCopyWith<$Res> + implements $ChatRoomSettingsModelCopyWith<$Res> { + factory _$$ChatRoomSettingsModelImplCopyWith( + _$ChatRoomSettingsModelImpl value, + $Res Function(_$ChatRoomSettingsModelImpl) then) = + __$$ChatRoomSettingsModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {int id, + @JsonKey(name: 'room_name') String roomName, + String state, + @JsonKey(name: 'access_level') String accessLevel, + @JsonKey(name: 'updated_at') String updatedAt}); +} + +/// @nodoc +class __$$ChatRoomSettingsModelImplCopyWithImpl<$Res> + extends _$ChatRoomSettingsModelCopyWithImpl<$Res, + _$ChatRoomSettingsModelImpl> + implements _$$ChatRoomSettingsModelImplCopyWith<$Res> { + __$$ChatRoomSettingsModelImplCopyWithImpl(_$ChatRoomSettingsModelImpl _value, + $Res Function(_$ChatRoomSettingsModelImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? roomName = null, + Object? state = null, + Object? accessLevel = null, + Object? updatedAt = null, + }) { + return _then(_$ChatRoomSettingsModelImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + roomName: null == roomName + ? _value.roomName + : roomName // ignore: cast_nullable_to_non_nullable + as String, + state: null == state + ? _value.state + : state // ignore: cast_nullable_to_non_nullable + as String, + accessLevel: null == accessLevel + ? _value.accessLevel + : accessLevel // ignore: cast_nullable_to_non_nullable + as String, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ChatRoomSettingsModelImpl implements _ChatRoomSettingsModel { + const _$ChatRoomSettingsModelImpl( + {required this.id, + @JsonKey(name: 'room_name') required this.roomName, + required this.state, + @JsonKey(name: 'access_level') required this.accessLevel, + @JsonKey(name: 'updated_at') required this.updatedAt}); + + factory _$ChatRoomSettingsModelImpl.fromJson(Map json) => + _$$ChatRoomSettingsModelImplFromJson(json); + + @override + final int id; + @override + @JsonKey(name: 'room_name') + final String roomName; + @override + final String state; + @override + @JsonKey(name: 'access_level') + final String accessLevel; + @override + @JsonKey(name: 'updated_at') + final String updatedAt; + + @override + String toString() { + return 'ChatRoomSettingsModel(id: $id, roomName: $roomName, state: $state, accessLevel: $accessLevel, updatedAt: $updatedAt)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ChatRoomSettingsModelImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.roomName, roomName) || + other.roomName == roomName) && + (identical(other.state, state) || other.state == state) && + (identical(other.accessLevel, accessLevel) || + other.accessLevel == accessLevel) && + (identical(other.updatedAt, updatedAt) || + other.updatedAt == updatedAt)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => + Object.hash(runtimeType, id, roomName, state, accessLevel, updatedAt); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ChatRoomSettingsModelImplCopyWith<_$ChatRoomSettingsModelImpl> + get copyWith => __$$ChatRoomSettingsModelImplCopyWithImpl< + _$ChatRoomSettingsModelImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ChatRoomSettingsModelImplToJson( + this, + ); + } +} + +abstract class _ChatRoomSettingsModel implements ChatRoomSettingsModel { + const factory _ChatRoomSettingsModel( + {required final int id, + @JsonKey(name: 'room_name') required final String roomName, + required final String state, + @JsonKey(name: 'access_level') required final String accessLevel, + @JsonKey(name: 'updated_at') required final String updatedAt}) = + _$ChatRoomSettingsModelImpl; + + factory _ChatRoomSettingsModel.fromJson(Map json) = + _$ChatRoomSettingsModelImpl.fromJson; + + @override + int get id; + @override + @JsonKey(name: 'room_name') + String get roomName; + @override + String get state; + @override + @JsonKey(name: 'access_level') + String get accessLevel; + @override + @JsonKey(name: 'updated_at') + String get updatedAt; + @override + @JsonKey(ignore: true) + _$$ChatRoomSettingsModelImplCopyWith<_$ChatRoomSettingsModelImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/features/chat/models/chat_room_settings_model.g.dart b/lib/features/chat/models/chat_room_settings_model.g.dart new file mode 100644 index 0000000..7b82433 --- /dev/null +++ b/lib/features/chat/models/chat_room_settings_model.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'chat_room_settings_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ChatRoomSettingsModelImpl _$$ChatRoomSettingsModelImplFromJson( + Map json) => + _$ChatRoomSettingsModelImpl( + id: (json['id'] as num).toInt(), + roomName: json['room_name'] as String, + state: json['state'] as String, + accessLevel: json['access_level'] as String, + updatedAt: json['updated_at'] as String, + ); + +Map _$$ChatRoomSettingsModelImplToJson( + _$ChatRoomSettingsModelImpl instance) => + { + 'id': instance.id, + 'room_name': instance.roomName, + 'state': instance.state, + 'access_level': instance.accessLevel, + 'updated_at': instance.updatedAt, + }; diff --git a/lib/features/chat/models/chat_user_model.dart b/lib/features/chat/models/chat_user_model.dart new file mode 100644 index 0000000..dd8bf02 --- /dev/null +++ b/lib/features/chat/models/chat_user_model.dart @@ -0,0 +1,19 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'chat_user_model.freezed.dart'; + +part 'chat_user_model.g.dart'; + +@freezed +class ChatUserModel with _$ChatUserModel { + const factory ChatUserModel({ + required int id, + required String name, + @JsonKey(name: 'profile_photo_path') + String? profilePhotoPath, + @JsonKey(name: 'profile_photo_url') + String? profilePhotoUrl, + }) = _ChatUserModel; + + factory ChatUserModel.fromJson(Map json) => _$ChatUserModelFromJson(json); +} diff --git a/lib/features/chat/models/chat_user_model.freezed.dart b/lib/features/chat/models/chat_user_model.freezed.dart new file mode 100644 index 0000000..095c09e --- /dev/null +++ b/lib/features/chat/models/chat_user_model.freezed.dart @@ -0,0 +1,225 @@ +// 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 'chat_user_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(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'); + +ChatUserModel _$ChatUserModelFromJson(Map json) { + return _ChatUserModel.fromJson(json); +} + +/// @nodoc +mixin _$ChatUserModel { + int get id => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + @JsonKey(name: 'profile_photo_path') + String? get profilePhotoPath => throw _privateConstructorUsedError; + @JsonKey(name: 'profile_photo_url') + String? get profilePhotoUrl => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $ChatUserModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ChatUserModelCopyWith<$Res> { + factory $ChatUserModelCopyWith( + ChatUserModel value, $Res Function(ChatUserModel) then) = + _$ChatUserModelCopyWithImpl<$Res, ChatUserModel>; + @useResult + $Res call( + {int id, + String name, + @JsonKey(name: 'profile_photo_path') String? profilePhotoPath, + @JsonKey(name: 'profile_photo_url') String? profilePhotoUrl}); +} + +/// @nodoc +class _$ChatUserModelCopyWithImpl<$Res, $Val extends ChatUserModel> + implements $ChatUserModelCopyWith<$Res> { + _$ChatUserModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? name = null, + Object? profilePhotoPath = freezed, + Object? profilePhotoUrl = freezed, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + profilePhotoPath: freezed == profilePhotoPath + ? _value.profilePhotoPath + : profilePhotoPath // ignore: cast_nullable_to_non_nullable + as String?, + profilePhotoUrl: freezed == profilePhotoUrl + ? _value.profilePhotoUrl + : profilePhotoUrl // ignore: cast_nullable_to_non_nullable + as String?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ChatUserModelImplCopyWith<$Res> + implements $ChatUserModelCopyWith<$Res> { + factory _$$ChatUserModelImplCopyWith( + _$ChatUserModelImpl value, $Res Function(_$ChatUserModelImpl) then) = + __$$ChatUserModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {int id, + String name, + @JsonKey(name: 'profile_photo_path') String? profilePhotoPath, + @JsonKey(name: 'profile_photo_url') String? profilePhotoUrl}); +} + +/// @nodoc +class __$$ChatUserModelImplCopyWithImpl<$Res> + extends _$ChatUserModelCopyWithImpl<$Res, _$ChatUserModelImpl> + implements _$$ChatUserModelImplCopyWith<$Res> { + __$$ChatUserModelImplCopyWithImpl( + _$ChatUserModelImpl _value, $Res Function(_$ChatUserModelImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? name = null, + Object? profilePhotoPath = freezed, + Object? profilePhotoUrl = freezed, + }) { + return _then(_$ChatUserModelImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + profilePhotoPath: freezed == profilePhotoPath + ? _value.profilePhotoPath + : profilePhotoPath // ignore: cast_nullable_to_non_nullable + as String?, + profilePhotoUrl: freezed == profilePhotoUrl + ? _value.profilePhotoUrl + : profilePhotoUrl // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ChatUserModelImpl implements _ChatUserModel { + const _$ChatUserModelImpl( + {required this.id, + required this.name, + @JsonKey(name: 'profile_photo_path') this.profilePhotoPath, + @JsonKey(name: 'profile_photo_url') this.profilePhotoUrl}); + + factory _$ChatUserModelImpl.fromJson(Map json) => + _$$ChatUserModelImplFromJson(json); + + @override + final int id; + @override + final String name; + @override + @JsonKey(name: 'profile_photo_path') + final String? profilePhotoPath; + @override + @JsonKey(name: 'profile_photo_url') + final String? profilePhotoUrl; + + @override + String toString() { + return 'ChatUserModel(id: $id, name: $name, profilePhotoPath: $profilePhotoPath, profilePhotoUrl: $profilePhotoUrl)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ChatUserModelImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.name, name) || other.name == name) && + (identical(other.profilePhotoPath, profilePhotoPath) || + other.profilePhotoPath == profilePhotoPath) && + (identical(other.profilePhotoUrl, profilePhotoUrl) || + other.profilePhotoUrl == profilePhotoUrl)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => + Object.hash(runtimeType, id, name, profilePhotoPath, profilePhotoUrl); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ChatUserModelImplCopyWith<_$ChatUserModelImpl> get copyWith => + __$$ChatUserModelImplCopyWithImpl<_$ChatUserModelImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ChatUserModelImplToJson( + this, + ); + } +} + +abstract class _ChatUserModel implements ChatUserModel { + const factory _ChatUserModel( + {required final int id, + required final String name, + @JsonKey(name: 'profile_photo_path') final String? profilePhotoPath, + @JsonKey(name: 'profile_photo_url') final String? profilePhotoUrl}) = + _$ChatUserModelImpl; + + factory _ChatUserModel.fromJson(Map json) = + _$ChatUserModelImpl.fromJson; + + @override + int get id; + @override + String get name; + @override + @JsonKey(name: 'profile_photo_path') + String? get profilePhotoPath; + @override + @JsonKey(name: 'profile_photo_url') + String? get profilePhotoUrl; + @override + @JsonKey(ignore: true) + _$$ChatUserModelImplCopyWith<_$ChatUserModelImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/features/chat/models/chat_user_model.g.dart b/lib/features/chat/models/chat_user_model.g.dart new file mode 100644 index 0000000..78e0f98 --- /dev/null +++ b/lib/features/chat/models/chat_user_model.g.dart @@ -0,0 +1,23 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'chat_user_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ChatUserModelImpl _$$ChatUserModelImplFromJson(Map json) => + _$ChatUserModelImpl( + id: (json['id'] as num).toInt(), + name: json['name'] as String, + profilePhotoPath: json['profile_photo_path'] as String?, + profilePhotoUrl: json['profile_photo_url'] as String?, + ); + +Map _$$ChatUserModelImplToJson(_$ChatUserModelImpl instance) => + { + 'id': instance.id, + 'name': instance.name, + 'profile_photo_path': instance.profilePhotoPath, + 'profile_photo_url': instance.profilePhotoUrl, + }; diff --git a/lib/features/chat/models/websocket_info_model.dart b/lib/features/chat/models/websocket_info_model.dart new file mode 100644 index 0000000..9fe8abc --- /dev/null +++ b/lib/features/chat/models/websocket_info_model.dart @@ -0,0 +1,12 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'websocket_info_model.freezed.dart'; + +part 'websocket_info_model.g.dart'; + +@freezed +class WebsocketInfoModel with _$WebsocketInfoModel { + const factory WebsocketInfoModel() = _WebsocketInfoModel; + + factory WebsocketInfoModel.fromJson(Map json) => _$WebsocketInfoModelFromJson(json); +} diff --git a/lib/features/chat/models/websocket_info_model.freezed.dart b/lib/features/chat/models/websocket_info_model.freezed.dart new file mode 100644 index 0000000..fd9ef4b --- /dev/null +++ b/lib/features/chat/models/websocket_info_model.freezed.dart @@ -0,0 +1,96 @@ +// 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 'websocket_info_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(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'); + +WebsocketInfoModel _$WebsocketInfoModelFromJson(Map json) { + return _WebsocketInfoModel.fromJson(json); +} + +/// @nodoc +mixin _$WebsocketInfoModel { + Map toJson() => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $WebsocketInfoModelCopyWith<$Res> { + factory $WebsocketInfoModelCopyWith( + WebsocketInfoModel value, $Res Function(WebsocketInfoModel) then) = + _$WebsocketInfoModelCopyWithImpl<$Res, WebsocketInfoModel>; +} + +/// @nodoc +class _$WebsocketInfoModelCopyWithImpl<$Res, $Val extends WebsocketInfoModel> + implements $WebsocketInfoModelCopyWith<$Res> { + _$WebsocketInfoModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; +} + +/// @nodoc +abstract class _$$WebsocketInfoModelImplCopyWith<$Res> { + factory _$$WebsocketInfoModelImplCopyWith(_$WebsocketInfoModelImpl value, + $Res Function(_$WebsocketInfoModelImpl) then) = + __$$WebsocketInfoModelImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$WebsocketInfoModelImplCopyWithImpl<$Res> + extends _$WebsocketInfoModelCopyWithImpl<$Res, _$WebsocketInfoModelImpl> + implements _$$WebsocketInfoModelImplCopyWith<$Res> { + __$$WebsocketInfoModelImplCopyWithImpl(_$WebsocketInfoModelImpl _value, + $Res Function(_$WebsocketInfoModelImpl) _then) + : super(_value, _then); +} + +/// @nodoc +@JsonSerializable() +class _$WebsocketInfoModelImpl implements _WebsocketInfoModel { + const _$WebsocketInfoModelImpl(); + + factory _$WebsocketInfoModelImpl.fromJson(Map json) => + _$$WebsocketInfoModelImplFromJson(json); + + @override + String toString() { + return 'WebsocketInfoModel()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$WebsocketInfoModelImpl); + } + + @JsonKey(ignore: true) + @override + int get hashCode => runtimeType.hashCode; + + @override + Map toJson() { + return _$$WebsocketInfoModelImplToJson( + this, + ); + } +} + +abstract class _WebsocketInfoModel implements WebsocketInfoModel { + const factory _WebsocketInfoModel() = _$WebsocketInfoModelImpl; + + factory _WebsocketInfoModel.fromJson(Map json) = + _$WebsocketInfoModelImpl.fromJson; +} diff --git a/lib/features/chat/models/websocket_info_model.g.dart b/lib/features/chat/models/websocket_info_model.g.dart new file mode 100644 index 0000000..6573c3c --- /dev/null +++ b/lib/features/chat/models/websocket_info_model.g.dart @@ -0,0 +1,15 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'websocket_info_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$WebsocketInfoModelImpl _$$WebsocketInfoModelImplFromJson( + Map json) => + _$WebsocketInfoModelImpl(); + +Map _$$WebsocketInfoModelImplToJson( + _$WebsocketInfoModelImpl instance) => + {}; diff --git a/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart b/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart new file mode 100644 index 0000000..f66069e --- /dev/null +++ b/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart @@ -0,0 +1,20 @@ +import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.dart'; +import 'package:eatvio_app/features/chat/repositories/chat_repository.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class ChatRoomCubit extends Cubit { + final ChatRepository chatRepository; + + ChatRoomCubit({ + required this.chatRepository, + }) : super(ChatRoomState.loading()); + + initChat(String chatId) async { + try { + final chatRoomModel = await chatRepository.getChatRoom(chatId); + emit(ChatRoomState.loaded(chatRoomModel: chatRoomModel)); + } catch (e) { + emit(ChatRoomState.error(errorMessage: 'Ошибка получеия данных')); + } + } +} diff --git a/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.dart b/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.dart new file mode 100644 index 0000000..89184a6 --- /dev/null +++ b/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.dart @@ -0,0 +1,19 @@ +import 'package:eatvio_app/features/chat/models/chat_room_model.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'chat_room_state.freezed.dart'; + +@freezed +abstract class ChatRoomState with _$ChatRoomState { + const factory ChatRoomState() = _ChatRoomState; + + const factory ChatRoomState.loading() = ChatRoomStateLoading; + + const factory ChatRoomState.loaded({ + required ChatRoomModel chatRoomModel, + }) = ChatRoomStateLoaded; + + const factory ChatRoomState.error({ + required String errorMessage, + }) = ChatRoomStateError; +} diff --git a/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.freezed.dart b/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.freezed.dart new file mode 100644 index 0000000..5bd9ae8 --- /dev/null +++ b/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.freezed.dart @@ -0,0 +1,622 @@ +// 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 'chat_room_state.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(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 _$ChatRoomState { + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() loading, + required TResult Function(ChatRoomModel chatRoomModel) loaded, + required TResult Function(String errorMessage) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? loading, + TResult? Function(ChatRoomModel chatRoomModel)? loaded, + TResult? Function(String errorMessage)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? loading, + TResult Function(ChatRoomModel chatRoomModel)? loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map( + TResult Function(_ChatRoomState value) $default, { + required TResult Function(ChatRoomStateLoading value) loading, + required TResult Function(ChatRoomStateLoaded value) loaded, + required TResult Function(ChatRoomStateError value) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_ChatRoomState value)? $default, { + TResult? Function(ChatRoomStateLoading value)? loading, + TResult? Function(ChatRoomStateLoaded value)? loaded, + TResult? Function(ChatRoomStateError value)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap( + TResult Function(_ChatRoomState value)? $default, { + TResult Function(ChatRoomStateLoading value)? loading, + TResult Function(ChatRoomStateLoaded value)? loaded, + TResult Function(ChatRoomStateError value)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ChatRoomStateCopyWith<$Res> { + factory $ChatRoomStateCopyWith( + ChatRoomState value, $Res Function(ChatRoomState) then) = + _$ChatRoomStateCopyWithImpl<$Res, ChatRoomState>; +} + +/// @nodoc +class _$ChatRoomStateCopyWithImpl<$Res, $Val extends ChatRoomState> + implements $ChatRoomStateCopyWith<$Res> { + _$ChatRoomStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; +} + +/// @nodoc +abstract class _$$ChatRoomStateImplCopyWith<$Res> { + factory _$$ChatRoomStateImplCopyWith( + _$ChatRoomStateImpl value, $Res Function(_$ChatRoomStateImpl) then) = + __$$ChatRoomStateImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$ChatRoomStateImplCopyWithImpl<$Res> + extends _$ChatRoomStateCopyWithImpl<$Res, _$ChatRoomStateImpl> + implements _$$ChatRoomStateImplCopyWith<$Res> { + __$$ChatRoomStateImplCopyWithImpl( + _$ChatRoomStateImpl _value, $Res Function(_$ChatRoomStateImpl) _then) + : super(_value, _then); +} + +/// @nodoc + +class _$ChatRoomStateImpl implements _ChatRoomState { + const _$ChatRoomStateImpl(); + + @override + String toString() { + return 'ChatRoomState()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$ChatRoomStateImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() loading, + required TResult Function(ChatRoomModel chatRoomModel) loaded, + required TResult Function(String errorMessage) error, + }) { + return $default(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? loading, + TResult? Function(ChatRoomModel chatRoomModel)? loaded, + TResult? Function(String errorMessage)? error, + }) { + return $default?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? loading, + TResult Function(ChatRoomModel chatRoomModel)? loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) { + if ($default != null) { + return $default(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map( + TResult Function(_ChatRoomState value) $default, { + required TResult Function(ChatRoomStateLoading value) loading, + required TResult Function(ChatRoomStateLoaded value) loaded, + required TResult Function(ChatRoomStateError value) error, + }) { + return $default(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_ChatRoomState value)? $default, { + TResult? Function(ChatRoomStateLoading value)? loading, + TResult? Function(ChatRoomStateLoaded value)? loaded, + TResult? Function(ChatRoomStateError value)? error, + }) { + return $default?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap( + TResult Function(_ChatRoomState value)? $default, { + TResult Function(ChatRoomStateLoading value)? loading, + TResult Function(ChatRoomStateLoaded value)? loaded, + TResult Function(ChatRoomStateError value)? error, + required TResult orElse(), + }) { + if ($default != null) { + return $default(this); + } + return orElse(); + } +} + +abstract class _ChatRoomState implements ChatRoomState { + const factory _ChatRoomState() = _$ChatRoomStateImpl; +} + +/// @nodoc +abstract class _$$ChatRoomStateLoadingImplCopyWith<$Res> { + factory _$$ChatRoomStateLoadingImplCopyWith(_$ChatRoomStateLoadingImpl value, + $Res Function(_$ChatRoomStateLoadingImpl) then) = + __$$ChatRoomStateLoadingImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$ChatRoomStateLoadingImplCopyWithImpl<$Res> + extends _$ChatRoomStateCopyWithImpl<$Res, _$ChatRoomStateLoadingImpl> + implements _$$ChatRoomStateLoadingImplCopyWith<$Res> { + __$$ChatRoomStateLoadingImplCopyWithImpl(_$ChatRoomStateLoadingImpl _value, + $Res Function(_$ChatRoomStateLoadingImpl) _then) + : super(_value, _then); +} + +/// @nodoc + +class _$ChatRoomStateLoadingImpl implements ChatRoomStateLoading { + const _$ChatRoomStateLoadingImpl(); + + @override + String toString() { + return 'ChatRoomState.loading()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ChatRoomStateLoadingImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() loading, + required TResult Function(ChatRoomModel chatRoomModel) loaded, + required TResult Function(String errorMessage) error, + }) { + return loading(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? loading, + TResult? Function(ChatRoomModel chatRoomModel)? loaded, + TResult? Function(String errorMessage)? error, + }) { + return loading?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? loading, + TResult Function(ChatRoomModel chatRoomModel)? loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map( + TResult Function(_ChatRoomState value) $default, { + required TResult Function(ChatRoomStateLoading value) loading, + required TResult Function(ChatRoomStateLoaded value) loaded, + required TResult Function(ChatRoomStateError value) error, + }) { + return loading(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_ChatRoomState value)? $default, { + TResult? Function(ChatRoomStateLoading value)? loading, + TResult? Function(ChatRoomStateLoaded value)? loaded, + TResult? Function(ChatRoomStateError value)? error, + }) { + return loading?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap( + TResult Function(_ChatRoomState value)? $default, { + TResult Function(ChatRoomStateLoading value)? loading, + TResult Function(ChatRoomStateLoaded value)? loaded, + TResult Function(ChatRoomStateError value)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(this); + } + return orElse(); + } +} + +abstract class ChatRoomStateLoading implements ChatRoomState { + const factory ChatRoomStateLoading() = _$ChatRoomStateLoadingImpl; +} + +/// @nodoc +abstract class _$$ChatRoomStateLoadedImplCopyWith<$Res> { + factory _$$ChatRoomStateLoadedImplCopyWith(_$ChatRoomStateLoadedImpl value, + $Res Function(_$ChatRoomStateLoadedImpl) then) = + __$$ChatRoomStateLoadedImplCopyWithImpl<$Res>; + @useResult + $Res call({ChatRoomModel chatRoomModel}); + + $ChatRoomModelCopyWith<$Res> get chatRoomModel; +} + +/// @nodoc +class __$$ChatRoomStateLoadedImplCopyWithImpl<$Res> + extends _$ChatRoomStateCopyWithImpl<$Res, _$ChatRoomStateLoadedImpl> + implements _$$ChatRoomStateLoadedImplCopyWith<$Res> { + __$$ChatRoomStateLoadedImplCopyWithImpl(_$ChatRoomStateLoadedImpl _value, + $Res Function(_$ChatRoomStateLoadedImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? chatRoomModel = null, + }) { + return _then(_$ChatRoomStateLoadedImpl( + chatRoomModel: null == chatRoomModel + ? _value.chatRoomModel + : chatRoomModel // ignore: cast_nullable_to_non_nullable + as ChatRoomModel, + )); + } + + @override + @pragma('vm:prefer-inline') + $ChatRoomModelCopyWith<$Res> get chatRoomModel { + return $ChatRoomModelCopyWith<$Res>(_value.chatRoomModel, (value) { + return _then(_value.copyWith(chatRoomModel: value)); + }); + } +} + +/// @nodoc + +class _$ChatRoomStateLoadedImpl implements ChatRoomStateLoaded { + const _$ChatRoomStateLoadedImpl({required this.chatRoomModel}); + + @override + final ChatRoomModel chatRoomModel; + + @override + String toString() { + return 'ChatRoomState.loaded(chatRoomModel: $chatRoomModel)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ChatRoomStateLoadedImpl && + (identical(other.chatRoomModel, chatRoomModel) || + other.chatRoomModel == chatRoomModel)); + } + + @override + int get hashCode => Object.hash(runtimeType, chatRoomModel); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ChatRoomStateLoadedImplCopyWith<_$ChatRoomStateLoadedImpl> get copyWith => + __$$ChatRoomStateLoadedImplCopyWithImpl<_$ChatRoomStateLoadedImpl>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() loading, + required TResult Function(ChatRoomModel chatRoomModel) loaded, + required TResult Function(String errorMessage) error, + }) { + return loaded(chatRoomModel); + } + + @override + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? loading, + TResult? Function(ChatRoomModel chatRoomModel)? loaded, + TResult? Function(String errorMessage)? error, + }) { + return loaded?.call(chatRoomModel); + } + + @override + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? loading, + TResult Function(ChatRoomModel chatRoomModel)? loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded(chatRoomModel); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map( + TResult Function(_ChatRoomState value) $default, { + required TResult Function(ChatRoomStateLoading value) loading, + required TResult Function(ChatRoomStateLoaded value) loaded, + required TResult Function(ChatRoomStateError value) error, + }) { + return loaded(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_ChatRoomState value)? $default, { + TResult? Function(ChatRoomStateLoading value)? loading, + TResult? Function(ChatRoomStateLoaded value)? loaded, + TResult? Function(ChatRoomStateError value)? error, + }) { + return loaded?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap( + TResult Function(_ChatRoomState value)? $default, { + TResult Function(ChatRoomStateLoading value)? loading, + TResult Function(ChatRoomStateLoaded value)? loaded, + TResult Function(ChatRoomStateError value)? error, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded(this); + } + return orElse(); + } +} + +abstract class ChatRoomStateLoaded implements ChatRoomState { + const factory ChatRoomStateLoaded( + {required final ChatRoomModel chatRoomModel}) = _$ChatRoomStateLoadedImpl; + + ChatRoomModel get chatRoomModel; + @JsonKey(ignore: true) + _$$ChatRoomStateLoadedImplCopyWith<_$ChatRoomStateLoadedImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$ChatRoomStateErrorImplCopyWith<$Res> { + factory _$$ChatRoomStateErrorImplCopyWith(_$ChatRoomStateErrorImpl value, + $Res Function(_$ChatRoomStateErrorImpl) then) = + __$$ChatRoomStateErrorImplCopyWithImpl<$Res>; + @useResult + $Res call({String errorMessage}); +} + +/// @nodoc +class __$$ChatRoomStateErrorImplCopyWithImpl<$Res> + extends _$ChatRoomStateCopyWithImpl<$Res, _$ChatRoomStateErrorImpl> + implements _$$ChatRoomStateErrorImplCopyWith<$Res> { + __$$ChatRoomStateErrorImplCopyWithImpl(_$ChatRoomStateErrorImpl _value, + $Res Function(_$ChatRoomStateErrorImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? errorMessage = null, + }) { + return _then(_$ChatRoomStateErrorImpl( + errorMessage: null == errorMessage + ? _value.errorMessage + : errorMessage // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$ChatRoomStateErrorImpl implements ChatRoomStateError { + const _$ChatRoomStateErrorImpl({required this.errorMessage}); + + @override + final String errorMessage; + + @override + String toString() { + return 'ChatRoomState.error(errorMessage: $errorMessage)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ChatRoomStateErrorImpl && + (identical(other.errorMessage, errorMessage) || + other.errorMessage == errorMessage)); + } + + @override + int get hashCode => Object.hash(runtimeType, errorMessage); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ChatRoomStateErrorImplCopyWith<_$ChatRoomStateErrorImpl> get copyWith => + __$$ChatRoomStateErrorImplCopyWithImpl<_$ChatRoomStateErrorImpl>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() loading, + required TResult Function(ChatRoomModel chatRoomModel) loaded, + required TResult Function(String errorMessage) error, + }) { + return error(errorMessage); + } + + @override + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? loading, + TResult? Function(ChatRoomModel chatRoomModel)? loaded, + TResult? Function(String errorMessage)? error, + }) { + return error?.call(errorMessage); + } + + @override + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? loading, + TResult Function(ChatRoomModel chatRoomModel)? loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(errorMessage); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map( + TResult Function(_ChatRoomState value) $default, { + required TResult Function(ChatRoomStateLoading value) loading, + required TResult Function(ChatRoomStateLoaded value) loaded, + required TResult Function(ChatRoomStateError value) error, + }) { + return error(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_ChatRoomState value)? $default, { + TResult? Function(ChatRoomStateLoading value)? loading, + TResult? Function(ChatRoomStateLoaded value)? loaded, + TResult? Function(ChatRoomStateError value)? error, + }) { + return error?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap( + TResult Function(_ChatRoomState value)? $default, { + TResult Function(ChatRoomStateLoading value)? loading, + TResult Function(ChatRoomStateLoaded value)? loaded, + TResult Function(ChatRoomStateError value)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(this); + } + return orElse(); + } +} + +abstract class ChatRoomStateError implements ChatRoomState { + const factory ChatRoomStateError({required final String errorMessage}) = + _$ChatRoomStateErrorImpl; + + String get errorMessage; + @JsonKey(ignore: true) + _$$ChatRoomStateErrorImplCopyWith<_$ChatRoomStateErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/features/chat/presentation/pages/chat_screen.dart b/lib/features/chat/presentation/pages/chat_screen.dart new file mode 100644 index 0000000..c6ad9be --- /dev/null +++ b/lib/features/chat/presentation/pages/chat_screen.dart @@ -0,0 +1,200 @@ +import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart'; +import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.dart'; +import 'package:eatvio_app/features/chat/presentation/widgets/chat_bubble_widget.dart'; +import 'package:eatvio_app/locator_service.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:eatvio_app/core/app_export.dart'; +import 'package:flutter/material.dart'; + +class ChatScreen extends StatelessWidget { + final String chatId; + + const ChatScreen({ + required this.chatId, + super.key, + }); + + Widget build(BuildContext context) { + return BlocProvider( + create: (BuildContext context) => sl()..initChat(chatId), + child: BlocBuilder( + builder: (context, state) { + if (state is ChatRoomStateLoading) { + return Scaffold( + body: Center( + child: CupertinoActivityIndicator(), + ), + ); + } else if (state is ChatRoomStateLoaded) { + return _buildChat(context, state); + } else if (state is ChatRoomStateError) { + return Center(child: Text(state.errorMessage)); + } else { + return Scaffold( + body: Center( + child: CupertinoActivityIndicator(), + ), + ); + } + }, + ), + ); + } + + _buildChat(BuildContext context, ChatRoomStateLoaded state) { + mediaQueryData = MediaQuery.of(context); + return Scaffold( + resizeToAvoidBottomInset: true, + backgroundColor: PrimaryColors().gray50, + appBar: AppBar( + titleSpacing: 0, + centerTitle: false, + backgroundColor: Colors.white, + forceMaterialTransparency: true, + elevation: 0, + title: Row( + children: [ + Container( + width: 40, + height: 40, + alignment: Alignment.center, + decoration: BoxDecoration( + color: Color(0xFFEBF4FF), + shape: BoxShape.circle, + ), + child: Text( + state.chatRoomModel.users['to_chat_user']!.name.split(' ').first[0] + state.chatRoomModel.users['to_chat_user']!.name.split(' ')[1][0], + style: TextStyle( + color: Color(0xFF7F9CF5), + fontWeight: FontWeight.w400, + fontSize: 20, + ), + ), + ), + SizedBox(width: 16), + Text( + state.chatRoomModel.users['to_chat_user']!.name, + style: CustomTextStyles.titleSmallBold.copyWith( + color: PrimaryColors().blueGray800, + ), + ), + ], + ), + ), + body: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: Container( + color: Color(0xFFF0F2F5), + width: double.maxFinite, + child: CustomScrollView( + reverse: true, + slivers: [ + SliverPadding( + padding: EdgeInsets.symmetric(vertical: 16), + sliver: SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) => ChatBubbleWidget( + text: state.chatRoomModel.messages[state.chatRoomModel.messages.length - index - 1].data, + isCurrentUser: state.chatRoomModel.users['to_chat_user']!.id != + state.chatRoomModel.messages[state.chatRoomModel.messages.length - index - 1].authorId, + mediaData: state.chatRoomModel.messages[state.chatRoomModel.messages.length - index - 1].mediaData, + ), + childCount: state.chatRoomModel.messages.length, + ), + ), + ), + ], + ), + ), + ), + Padding( + padding: EdgeInsets.symmetric(vertical: 8), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(width: 8), + InkWell( + onTap: () {}, + borderRadius: BorderRadiusStyle.circleBorder17, + child: Ink( + height: 32, + width: 32, + child: Icon( + Icons.attach_file_rounded, + size: 28, + color: Color(0xFF858E99), + ), + ), + ), + SizedBox(width: 8), + SizedBox( + width: mediaQueryData.size.width - 96, + child: TextFormField( + maxLines: 5, + minLines: 1, + keyboardType: TextInputType.multiline, + style: TextStyle( + fontSize: 16, + fontFamily: 'Mulish', + fontWeight: FontWeight.w400, + color: Colors.black, + ), + decoration: InputDecoration( + contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 4), + hintText: 'Сообщение', + hintStyle: TextStyle( + fontSize: 16, + fontFamily: 'Mulish', + fontWeight: FontWeight.w400, + color: Color(0xFFAEAEB2), + ), + filled: true, + border: OutlineInputBorder( + borderRadius: BorderRadiusStyle.circleBorder17, + borderSide: BorderSide( + color: Color(0xFFD1D1D6), + width: 1, + ), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadiusStyle.circleBorder17, + borderSide: BorderSide( + color: Color(0xFFD1D1D6), + width: 1, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadiusStyle.circleBorder17, + borderSide: BorderSide( + color: Color(0xFFD1D1D6), + width: 1, + ), + ), + ), + ), + ), + SizedBox(width: 8), + InkWell( + borderRadius: BorderRadiusStyle.circleBorder17, + onTap: () {}, + child: Ink( + height: 32, + width: 32, + child: Icon( + Icons.send_outlined, + size: 28, + color: Color(0xFF858E99), + ), + ), + ), + SizedBox(width: 8), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/chat/chat_select_screen/bloc/chat_select_bloc.dart b/lib/features/chat/presentation/pages/chat_select_screen/bloc/chat_select_bloc.dart similarity index 89% rename from lib/presentation/chat/chat_select_screen/bloc/chat_select_bloc.dart rename to lib/features/chat/presentation/pages/chat_select_screen/bloc/chat_select_bloc.dart index f543c5a..ba44ade 100644 --- a/lib/presentation/chat/chat_select_screen/bloc/chat_select_bloc.dart +++ b/lib/features/chat/presentation/pages/chat_select_screen/bloc/chat_select_bloc.dart @@ -1,3 +1,6 @@ +import 'package:eatvio_app/app_router.dart'; +import 'package:eatvio_app/features/chat/presentation/pages/chat_select_screen/models/chat_entry.dart'; +import 'package:eatvio_app/features/chat/presentation/pages/chat_select_screen/models/chat_select_model.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; @@ -7,8 +10,8 @@ import 'package:intl/intl.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'dart:convert'; import '/core/app_export.dart'; -import 'package:eatvio_app/presentation/chat/chat_select_screen/models/chat_select_model.dart'; -import 'package:eatvio_app/presentation/chat/chat_select_screen/models/chat_entry.dart'; // ignore: must_be_immutable +import 'package:go_router/go_router.dart'; + part 'chat_select_event.dart'; part 'chat_select_state.dart'; @@ -47,9 +50,7 @@ class ChatSelectBloc extends Bloc { final box = await Hive.openBox('token'); final token = box.get('token'); if (token == null) { - NavigatorService.popAndPushNamed( - AppRoutes.authScreen, - ); + AppRouter.navigatorKey.currentContext?.goNamed('authScreen'); } final response = await http.get( Uri.parse(dotenv.env['SERVER_ROOT']! + '/chat/index'), diff --git a/lib/presentation/chat/chat_select_screen/bloc/chat_select_event.dart b/lib/features/chat/presentation/pages/chat_select_screen/bloc/chat_select_event.dart similarity index 100% rename from lib/presentation/chat/chat_select_screen/bloc/chat_select_event.dart rename to lib/features/chat/presentation/pages/chat_select_screen/bloc/chat_select_event.dart diff --git a/lib/presentation/chat/chat_select_screen/bloc/chat_select_state.dart b/lib/features/chat/presentation/pages/chat_select_screen/bloc/chat_select_state.dart similarity index 100% rename from lib/presentation/chat/chat_select_screen/bloc/chat_select_state.dart rename to lib/features/chat/presentation/pages/chat_select_screen/bloc/chat_select_state.dart diff --git a/lib/presentation/chat/chat_select_screen/chat_search_view.dart b/lib/features/chat/presentation/pages/chat_select_screen/chat_search_view.dart similarity index 76% rename from lib/presentation/chat/chat_select_screen/chat_search_view.dart rename to lib/features/chat/presentation/pages/chat_select_screen/chat_search_view.dart index b944766..939bb42 100644 --- a/lib/presentation/chat/chat_select_screen/chat_search_view.dart +++ b/lib/features/chat/presentation/pages/chat_select_screen/chat_search_view.dart @@ -73,18 +73,13 @@ class CustomSearchView extends StatelessWidget { @override Widget build(BuildContext context) { - return alignment != null - ? Align( - alignment: alignment ?? Alignment.center, - child: searchViewWidget(context)) - : searchViewWidget(context); + return alignment != null ? Align(alignment: alignment ?? Alignment.center, child: searchViewWidget(context)) : searchViewWidget(context); } searchViewWidget(BuildContext context) => SizedBox( width: width ?? double.maxFinite, child: TextFormField( - scrollPadding: - EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), + scrollPadding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), controller: controller, focusNode: focusNode, onTapOutside: (event) => onTapOutside(), @@ -110,26 +105,19 @@ class CustomSearchView extends StatelessWidget { hintText: hintText ?? "", hintStyle: hintStyle ?? CustomTextStyles.bodySmallMulishBluegray800, prefixIcon: prefix ?? - SizedBox( - child: Padding( - padding: EdgeInsets.only(left: 10), - child: Icon(Icons.search)), - ), - prefixIconConstraints: prefixConstraints ?? - BoxConstraints( - maxHeight: 40.v, + Padding( + padding: EdgeInsets.only(left: 10), + child: Icon( + Icons.search, + color: Color(0xFF7B8794), + ), ), suffixIcon: suffix ?? - Padding( - padding: EdgeInsets.only( - right: 15.h, - ), - child: IconButton( - onPressed: () => controller!.clear(), - icon: Icon( - Icons.clear, - color: Colors.grey.shade600, - ), + IconButton( + onPressed: () => controller!.clear(), + icon: Icon( + Icons.clear, + color: Colors.grey.shade600, ), ), suffixIconConstraints: suffixConstraints ?? @@ -137,12 +125,7 @@ class CustomSearchView extends StatelessWidget { maxHeight: 40.v, ), isDense: true, - contentPadding: contentPadding ?? - EdgeInsets.only( - top: 11.v, - right: 11.h, - bottom: 11.v, - ), + contentPadding: EdgeInsets.all(16), fillColor: fillColor ?? appTheme.gray100, filled: filled, border: borderDecoration ?? diff --git a/lib/features/chat/presentation/pages/chat_select_screen/chat_select_screen.dart b/lib/features/chat/presentation/pages/chat_select_screen/chat_select_screen.dart new file mode 100644 index 0000000..a206822 --- /dev/null +++ b/lib/features/chat/presentation/pages/chat_select_screen/chat_select_screen.dart @@ -0,0 +1,97 @@ +import 'package:eatvio_app/features/chat/presentation/pages/chat_select_screen/models/chat_entry.dart'; +import 'package:eatvio_app/features/chat/presentation/pages/chat_select_screen/widgets/chat_entry_widget.dart'; +import 'package:flutter/cupertino.dart'; + +import 'bloc/chat_select_bloc.dart'; +import 'models/chat_select_model.dart'; +import 'package:eatvio_app/core/app_export.dart'; +import 'chat_search_view.dart'; +import 'package:flutter/material.dart'; + +class ChatSelectScreen extends StatelessWidget { + const ChatSelectScreen({Key? key}) : super(key: key); + + static Widget builder(BuildContext context) { + return BlocProvider( + create: (context) => ChatSelectBloc( + ChatSelectState( + chatSelectModelObj: ChatSelectModel(), + ), + )..add( + ChatSelectInitialEvent(), + ), + child: ChatSelectScreen(), + ); + } + + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state is ChatSelectInitialState) { + return Scaffold( + body: Center( + child: CupertinoActivityIndicator(), + ), + ); + } else if (state is ChatSelectLoadedState) { + return Scaffold( + appBar: AppBar( + backgroundColor: Colors.white, + forceMaterialTransparency: true, + toolbarHeight: 0, + ), + body: NestedScrollView( + headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { + return [ + SliverAppBar( + backgroundColor: Colors.white, + forceMaterialTransparency: true, + floating: true, + snap: true, + title: CustomSearchView( + controller: state.searchController, + fillColor: Color(0xFFF0F2F5), + hintText: "Поиск", + hintStyle: TextStyle( + color: Color(0xFF7B8794), + ), + ), + ), + ]; + }, + body: _buildChatList(context, state), + ), + ); + } else if (state is ChatSelectErrorState) { + return Center(child: Text(state.errorMessage)); + } else { + return Scaffold( + body: Center( + child: CupertinoActivityIndicator(), + ), + ); + } + }, + ); + } +} + +_buildChatList(BuildContext context, ChatSelectLoadedState state) { + return ListView.separated( + padding: EdgeInsets.all(16), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + separatorBuilder: (context, index) { + return SizedBox( + height: 10.v, + ); + }, + itemCount: state.chatRooms?.length ?? 0, + itemBuilder: (context, index) { + ChatItemModel model = state.chatRooms?[index]; + return ChatEntryWidget( + chatEntryObj: model, + ); + }, + ); +} diff --git a/lib/presentation/chat/chat_select_screen/models/chat_entry.dart b/lib/features/chat/presentation/pages/chat_select_screen/models/chat_entry.dart similarity index 100% rename from lib/presentation/chat/chat_select_screen/models/chat_entry.dart rename to lib/features/chat/presentation/pages/chat_select_screen/models/chat_entry.dart diff --git a/lib/presentation/chat/chat_select_screen/models/chat_select_model.dart b/lib/features/chat/presentation/pages/chat_select_screen/models/chat_select_model.dart similarity index 84% rename from lib/presentation/chat/chat_select_screen/models/chat_select_model.dart rename to lib/features/chat/presentation/pages/chat_select_screen/models/chat_select_model.dart index 2b90449..c95b52e 100644 --- a/lib/presentation/chat/chat_select_screen/models/chat_select_model.dart +++ b/lib/features/chat/presentation/pages/chat_select_screen/models/chat_select_model.dart @@ -1,7 +1,7 @@ // ignore_for_file: must_be_immutable +import 'package:eatvio_app/features/chat/presentation/pages/chat_select_screen/models/chat_entry.dart'; import 'package:equatable/equatable.dart'; -import 'package:eatvio_app/presentation/chat/chat_select_screen/models/chat_entry.dart'; /// This class defines the variables used in the [chat_select_screen], /// and is typically used to hold data that is passed between different parts of the application. diff --git a/lib/features/chat/presentation/pages/chat_select_screen/widgets/chat_entry_widget.dart b/lib/features/chat/presentation/pages/chat_select_screen/widgets/chat_entry_widget.dart new file mode 100644 index 0000000..fbd6b1b --- /dev/null +++ b/lib/features/chat/presentation/pages/chat_select_screen/widgets/chat_entry_widget.dart @@ -0,0 +1,93 @@ +// ignore_for_file: must_be_immutable + +import 'package:eatvio_app/features/chat/presentation/pages/chat_select_screen/models/chat_entry.dart'; +import 'package:flutter/material.dart'; +import 'package:eatvio_app/core/app_export.dart'; +import 'package:go_router/go_router.dart'; + +class ChatEntryWidget extends StatelessWidget { + ChatEntryWidget({ + required this.chatEntryObj, + super.key, + }); + + ChatItemModel chatEntryObj; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () => GoRouter.of(context).goNamed( + 'chatScreen', + extra: chatEntryObj.id.toString(), + ), + child: Container( + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + borderRadius: BorderRadiusStyle.circleBorder8, + color: Color(0xFFF6FAFF), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 48, + height: 48, + alignment: Alignment.center, + decoration: BoxDecoration( + color: Color(0xFFEBF4FF), + shape: BoxShape.circle, + ), + child: Text( + chatEntryObj.username!.split(' ').first[0] + chatEntryObj.username!.split(' ')[1][0], + style: TextStyle( + color: Color(0xFF7F9CF5), + fontWeight: FontWeight.w400, + fontSize: 20, + ), + ), + ), + SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + chatEntryObj.username!.length <= 15 ? chatEntryObj.username! : chatEntryObj.username!.substring(0, 15) + '...', + style: theme.textTheme.titleMedium, + ), + Spacer(), + CustomImageView( + imagePath: ImageConstant.imageNotFound, + height: 9.v, + width: 16.h, + margin: EdgeInsets.only( + top: 4.v, + bottom: 6.v, + ), + ), + Padding( + padding: EdgeInsets.only(left: 6.h), + child: Text( + chatEntryObj.dateText!, + style: theme.textTheme.bodyMedium, + ), + ) + ], + ), + SizedBox(height: 6.v), + Text( + chatEntryObj.greetingText!, + style: CustomTextStyles.bodyMediumBluegray800, + ), + ], + ), + ) + ], + ), + ), + ); + } +} diff --git a/lib/features/chat/presentation/pages/photo_view_screen.dart b/lib/features/chat/presentation/pages/photo_view_screen.dart new file mode 100644 index 0000000..229ced5 --- /dev/null +++ b/lib/features/chat/presentation/pages/photo_view_screen.dart @@ -0,0 +1,124 @@ +import 'package:eatvio_app/core/app_export.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:go_router/go_router.dart'; +import 'package:photo_view/photo_view.dart'; +import 'package:photo_view/photo_view_gallery.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class PhotoViewScreen extends StatefulWidget { + final List imageUrls; + final int index; + + const PhotoViewScreen({ + super.key, + required this.imageUrls, + required this.index, + }); + + @override + State createState() => _PhotoViewScreenState(); +} + +class _PhotoViewScreenState extends State { + late final pageController; + int imageIndex = 0; + + @override + void initState() { + imageIndex = widget.index; + pageController = PageController(initialPage: imageIndex); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.black, + appBar: AppBar( + centerTitle: true, + systemOverlayStyle: SystemUiOverlayStyle( + statusBarColor: Colors.black, + statusBarBrightness: Brightness.dark, + statusBarIconBrightness: Brightness.light, + ), + backgroundColor: Colors.black, + foregroundColor: Colors.white, + title: Text( + (imageIndex + 1).toString() + ' из ' + widget.imageUrls.length.toString(), + ), + ), + body: Dismissible( + direction: DismissDirection.vertical, + key: const Key('key'), + onDismissed: (_) => GoRouter.of(context).pop(), + resizeDuration: Duration(microseconds: 1), + child: Container( + child: PhotoViewGallery.builder( + scrollPhysics: const BouncingScrollPhysics(), + builder: (BuildContext context, int index) { + return PhotoViewGalleryPageOptions( + imageProvider: NetworkImage(widget.imageUrls[index]), + initialScale: PhotoViewComputedScale.contained, + minScale: PhotoViewComputedScale.contained, + heroAttributes: PhotoViewHeroAttributes(tag: widget.imageUrls[index]), + errorBuilder: (context, object, stackTrace) { + return Column( + children: [ + SizedBox(height: 32), + Text('Файл'), + SizedBox(height: 8), + ListTile( + shape: RoundedRectangleBorder( + borderRadius: BorderRadiusStyle.circleBorder17, + ), + contentPadding: EdgeInsets.all(16), + onTap: () async { + if (!await launchUrl(Uri.parse(widget.imageUrls[index]))) { + throw Exception('Could not launch ${widget.imageUrls[index]}'); + } + }, + leading: Icon( + Icons.file_copy_outlined, + color: Colors.white, + ), + trailing: Icon( + Icons.download_outlined, + color: Colors.white, + ), + title: Text( + widget.imageUrls[index].replaceAll(dotenv.env['STORAGE_URL']! + 'chat_files/', ''), + style: TextStyle( + color: Colors.white, + fontSize: 12, + ), + ), + ), + ], + ); + }, + ); + }, + itemCount: widget.imageUrls.length, + loadingBuilder: (context, event) => Center( + child: Container( + width: 20.0, + height: 20.0, + child: CircularProgressIndicator( + value: event == null ? 0 : event.cumulativeBytesLoaded / event.expectedTotalBytes!, + ), + ), + ), + pageController: pageController, + onPageChanged: (page) { + setState(() { + imageIndex = page; + }); + }, + ), + ), + ), + ); + } +} diff --git a/lib/features/chat/presentation/widgets/chat_bubble_widget.dart b/lib/features/chat/presentation/widgets/chat_bubble_widget.dart new file mode 100644 index 0000000..127795c --- /dev/null +++ b/lib/features/chat/presentation/widgets/chat_bubble_widget.dart @@ -0,0 +1,110 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:eatvio_app/theme/app_decoration.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:go_router/go_router.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class ChatBubbleWidget extends StatelessWidget { + const ChatBubbleWidget({ + super.key, + required this.text, + required this.isCurrentUser, + this.mediaData, + }); + + final String text; + final bool isCurrentUser; + final List? mediaData; + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.fromLTRB(isCurrentUser ? 64 : 16, 4, isCurrentUser ? 16 : 64, 4), + child: Align( + alignment: isCurrentUser ? Alignment.centerRight : Alignment.centerLeft, + child: DecoratedBox( + decoration: BoxDecoration( + color: isCurrentUser ? Color(0xFFF6FAFF) : Colors.white, + borderRadius: BorderRadiusStyle.circleBorder17, + ), + child: Padding( + padding: const EdgeInsets.all(12), + child: Column( + children: [ + if (mediaData != null) + CustomScrollView( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + slivers: [ + SliverList.separated( + itemBuilder: (BuildContext context, int index) { + if (!mediaData![index].toString().contains('.png') && + !mediaData![index].toString().contains('.jpg') && + !mediaData![index].toString().contains('.jpeg')) { + return ListTile( + contentPadding: EdgeInsets.zero, + horizontalTitleGap: 8, + onTap: () async { + if (!await launchUrl(Uri.parse(dotenv.env['STORAGE_URL']! + mediaData![index].toString()))) { + throw Exception('Could not launch ${mediaData![index].toString()}'); + } + }, + leading: Icon( + Icons.file_copy_outlined, + color: Color(0xFF2F80ED), + ), + trailing: Icon( + Icons.download_outlined, + color: Color(0xFF2F80ED), + ), + title: Text( + mediaData![index].toString().replaceAll('chat_files/', ''), + maxLines: 2, + style: TextStyle( + color: Color(0xFF323F4B), + fontSize: 12, + overflow: TextOverflow.ellipsis, + ), + ), + ); + } + return GestureDetector( + onTap: () { + GoRouter.of(context).pushNamed( + 'photoViewScreen', + extra: mediaData!.map((e) => dotenv.env['STORAGE_URL']! + e.toString()).toList(), + pathParameters: { + 'index': index.toString(), + }, + ); + }, + child: ClipRRect( + borderRadius: BorderRadiusStyle.roundedBorder8, + child: CachedNetworkImage( + imageUrl: dotenv.env['STORAGE_URL']! + '${mediaData![index]}', + ), + ), + ); + }, + separatorBuilder: (BuildContext context, int index) => const SizedBox(height: 8), + itemCount: mediaData!.length, + ), + ], + ), + if (mediaData!.length > 0 && text.isNotEmpty) SizedBox(height: 8), + if (text.isNotEmpty) + Text( + text, + style: TextStyle( + color: Color(0xFF323F4B), + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/features/chat/repositories/chat_repository.dart b/lib/features/chat/repositories/chat_repository.dart new file mode 100644 index 0000000..137aa87 --- /dev/null +++ b/lib/features/chat/repositories/chat_repository.dart @@ -0,0 +1,31 @@ +import 'package:eatvio_app/features/chat/models/chat_room_model.dart'; +import 'package:eatvio_app/features/chat/models/websocket_info_model.dart'; +import 'package:eatvio_app/features/chat/services/chat_service.dart'; + +class ChatRepository { + final ChatService chatService; + + ChatRepository({ + required this.chatService, + }); + + /// TODO use "dartz" package because need Either for error handling + Future getChatRoom(String chatId) async { + try { + return (await chatService.getChatRoomModel(chatId))['chatRoom']!; + } catch (e) { + print(e); + throw (e); + } + } + + /// TODO use "dartz" package because need Either for error handling + Future getWebsocket() async { + try { + return (await chatService.getWebsocket()); + } catch (e) { + print(e); + throw (e); + } + } +} diff --git a/lib/features/chat/services/chat_service.dart b/lib/features/chat/services/chat_service.dart new file mode 100644 index 0000000..5b27ccf --- /dev/null +++ b/lib/features/chat/services/chat_service.dart @@ -0,0 +1,17 @@ +import 'package:eatvio_app/features/chat/models/chat_room_model.dart'; +import 'package:dio/dio.dart'; +import 'package:eatvio_app/features/chat/models/websocket_info_model.dart'; +import 'package:retrofit/retrofit.dart'; + +part 'chat_service.g.dart'; + +@RestApi() +abstract class ChatService { + factory ChatService(Dio dio, {String baseUrl}) = _ChatService; + + @GET('/chat/show/{id}') + Future> getChatRoomModel(@Path() String id); + + @GET('/profile/ws/params') + Future getWebsocket(); +} diff --git a/lib/features/chat/services/chat_service.g.dart b/lib/features/chat/services/chat_service.g.dart new file mode 100644 index 0000000..db9fdea --- /dev/null +++ b/lib/features/chat/services/chat_service.g.dart @@ -0,0 +1,105 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'chat_service.dart'; + +// ************************************************************************** +// RetrofitGenerator +// ************************************************************************** + +// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers + +class _ChatService implements ChatService { + _ChatService( + this._dio, { + this.baseUrl, + }); + + final Dio _dio; + + String? baseUrl; + + @override + Future> getChatRoomModel(String id) async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + const Map? _data = null; + final _result = await _dio.fetch>( + _setStreamType>(Options( + method: 'GET', + headers: _headers, + extra: _extra, + ) + .compose( + _dio.options, + '/chat/show/${id}', + queryParameters: queryParameters, + data: _data, + ) + .copyWith( + baseUrl: _combineBaseUrls( + _dio.options.baseUrl, + baseUrl, + )))); + var value = _result.data!.map((k, dynamic v) => + MapEntry(k, ChatRoomModel.fromJson(v as Map))); + return value; + } + + @override + Future getWebsocket() async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + const Map? _data = null; + final _result = await _dio + .fetch>(_setStreamType(Options( + method: 'GET', + headers: _headers, + extra: _extra, + ) + .compose( + _dio.options, + '/profile/ws/params', + queryParameters: queryParameters, + data: _data, + ) + .copyWith( + baseUrl: _combineBaseUrls( + _dio.options.baseUrl, + baseUrl, + )))); + final value = WebsocketInfoModel.fromJson(_result.data!); + return value; + } + + RequestOptions _setStreamType(RequestOptions requestOptions) { + if (T != dynamic && + !(requestOptions.responseType == ResponseType.bytes || + requestOptions.responseType == ResponseType.stream)) { + if (T == String) { + requestOptions.responseType = ResponseType.plain; + } else { + requestOptions.responseType = ResponseType.json; + } + } + return requestOptions; + } + + String _combineBaseUrls( + String dioBaseUrl, + String? baseUrl, + ) { + if (baseUrl == null || baseUrl.trim().isEmpty) { + return dioBaseUrl; + } + + final url = Uri.parse(baseUrl); + + if (url.isAbsolute) { + return url.toString(); + } + + return Uri.parse(dioBaseUrl).resolveUri(url).toString(); + } +} diff --git a/lib/localization/app_localization.dart b/lib/localization/app_localization.dart index 4701a67..2fc106d 100644 --- a/lib/localization/app_localization.dart +++ b/lib/localization/app_localization.dart @@ -1,8 +1,9 @@ +import 'package:eatvio_app/app_router.dart'; + import 'ru/ru_translations.dart'; //import 'en_us/en_us_translations.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/cupertino.dart'; -import '../core/app_export.dart'; class AppLocalization { AppLocalization(this.locale); @@ -17,7 +18,7 @@ class AppLocalization { };*/ static AppLocalization of() { - final context = NavigatorService.navigatorKey.currentContext; + final context = AppRouter.navigatorKey.currentContext; if (context != null) { return Localizations.of(context, AppLocalization)!; } else { diff --git a/lib/locator_service.dart b/lib/locator_service.dart new file mode 100644 index 0000000..6823378 --- /dev/null +++ b/lib/locator_service.dart @@ -0,0 +1,28 @@ +import 'package:dio/dio.dart'; +import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart'; +import 'package:eatvio_app/features/chat/repositories/chat_repository.dart'; +import 'package:eatvio_app/features/chat/services/chat_service.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:get_it/get_it.dart'; + +final sl = GetIt.instance; + +Future init() async { + // BLoC + sl.registerFactory(() => ChatRoomCubit(chatRepository: sl())); + + // Repository + sl.registerLazySingleton(() => ChatRepository(chatService: sl())); + + // Services + sl.registerLazySingleton(() => ChatService(sl())); + + // External + sl.registerLazySingleton( + () => Dio( + BaseOptions( + baseUrl: dotenv.env['SERVER_ROOT']!, + ), + ), + ); +} diff --git a/lib/main.dart b/lib/main.dart index 15fb79c..8e0c1b9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,3 +1,12 @@ +import 'package:eatvio_app/app_router.dart'; +import 'package:eatvio_app/features/chat/presentation/pages/chat_select_screen/bloc/chat_select_bloc.dart'; +import 'package:eatvio_app/features/chat/presentation/pages/chat_select_screen/models/chat_select_model.dart'; +import 'package:eatvio_app/presentation/diary/diary_edit_screen/bloc/diary_edit_bloc.dart'; +import 'package:eatvio_app/presentation/diary/diary_edit_screen/models/diary_edit_model.dart'; +import 'package:eatvio_app/presentation/feed/feed_screen/bloc/feed_bloc.dart'; +import 'package:eatvio_app/presentation/feed/feed_screen/models/feed_model.dart'; +import 'package:eatvio_app/presentation/profile/my_profile_screen/bloc/my_profile_bloc.dart'; +import 'package:eatvio_app/presentation/profile/my_profile_screen/models/my_profile_model.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; @@ -6,10 +15,13 @@ import 'package:hive_flutter/hive_flutter.dart'; import 'package:path_provider/path_provider.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:keyboard_dismisser/keyboard_dismisser.dart'; +import 'package:eatvio_app/locator_service.dart' as di; var globalMessengerKey = GlobalKey(); + void main() async { WidgetsFlutterBinding.ensureInitialized(); + await di.init(); final appDocumentDir = await getApplicationDocumentsDirectory(); final path = appDocumentDir.path; await dotenv.load(fileName: ".env"); @@ -26,39 +38,65 @@ void main() async { class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocProvider( - create: (context) => ThemeBloc( - ThemeState( - themeType: PrefUtils().getThemeData(), + return MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => ThemeBloc( + ThemeState(themeType: PrefUtils().getThemeData()), + ), ), - ), + BlocProvider( + create: (context) => MyProfileBloc( + MyProfileState( + myProfileModelObj: MyProfileModel(), + ), + )..add(MyProfileInitialEvent()), + ), + BlocProvider( + create: (context) => FeedBloc(FeedState(myProfileModelObj: FeedModel())) + ..add( + FeedInitialEvent(), + ), + ), + BlocProvider( + create: (context) => DiaryEditBloc(DiaryEditState(diaryEditModelObj: DiaryEditModel())) + ..add( + DiaryEditInitialEvent(), + ), + ), + BlocProvider( + create: (context) => ChatSelectBloc(ChatSelectState(chatSelectModelObj: ChatSelectModel())) + ..add( + ChatSelectInitialEvent(), + ), + ), + ], child: BlocBuilder( builder: (context, state) { return KeyboardDismisser( - gestures: [ - GestureType.onTap, - GestureType.onPanUpdateDownDirection, + gestures: [ + GestureType.onTap, + GestureType.onPanUpdateDownDirection, + ], + child: MaterialApp.router( + theme: theme, + title: 'Eatvio', + routerConfig: AppRouter.appRouter, + debugShowCheckedModeBanner: false, + localizationsDelegates: [ + AppLocalizationDelegate(), + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, ], - child: MaterialApp( - theme: theme, - title: 'Eatvio', - navigatorKey: NavigatorService.navigatorKey, - debugShowCheckedModeBanner: false, - localizationsDelegates: [ - AppLocalizationDelegate(), - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - ], - supportedLocales: [ - Locale( - 'ru', - '', - ), - ], - initialRoute: AppRoutes.initialRoute, - routes: AppRoutes.routes, - )); + supportedLocales: [ + Locale( + 'ru', + '', + ), + ], + ), + ); }, ), ); diff --git a/lib/presentation/auth/auth_screen/auth_screen.dart b/lib/presentation/auth/auth_screen/auth_screen.dart index bdd2d78..9694a2c 100644 --- a/lib/presentation/auth/auth_screen/auth_screen.dart +++ b/lib/presentation/auth/auth_screen/auth_screen.dart @@ -1,3 +1,6 @@ +import 'package:eatvio_app/core/token_storage.dart'; +import 'package:go_router/go_router.dart'; + import 'bloc/auth_bloc.dart'; import 'models/auth_model.dart'; import 'package:eatvio_app/core/app_export.dart'; @@ -19,7 +22,9 @@ class AuthScreen extends StatelessWidget { AuthState( authModelObj: AuthModel(), ), - )..add(AuthInitialEvent()), + )..add( + AuthInitialEvent(), + ), child: AuthScreen(), ); } @@ -27,72 +32,83 @@ class AuthScreen extends StatelessWidget { @override Widget build(BuildContext context) { mediaQueryData = MediaQuery.of(context); - return BlocBuilder(builder: (context, state) { - return Scaffold( - resizeToAvoidBottomInset: false, - appBar: _buildAppBar(context), - body: Container( - width: double.maxFinite, - padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v), - child: Column(children: [ - SizedBox(height: 36.v), - Text("user_authorization".tr, - style: theme.textTheme.headlineSmall), - SizedBox(height: 60.v), - Text("email".tr, style: theme.textTheme.bodyLarge), - SizedBox(height: 3.v), - CustomTextFormField( - controller: state.emailController, - textInputAction: TextInputAction.done), - SizedBox(height: 22.v), - Text("password".tr, style: theme.textTheme.bodyLarge), - SizedBox(height: 5.v), - CustomTextFormField( - obscureText: true, - controller: state.passwordController, - ), - SizedBox(height: 50.v), - NextStepButton( - text: "no_account".tr.toUpperCase(), + return Scaffold( + resizeToAvoidBottomInset: false, + appBar: _buildAppBar(context), + body: GestureDetector( + onTap: () => FocusScope.of(context).unfocus(), + child: Container( + width: double.maxFinite, + padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v), + child: Column(children: [ + SizedBox(height: 36.v), + Text("user_authorization".tr, style: theme.textTheme.headlineSmall), + SizedBox(height: 60.v), + Text("email".tr, style: theme.textTheme.bodyLarge), + SizedBox(height: 3.v), + BlocSelector( + selector: (state) => state.emailController, + builder: (context, emailController) { + return CustomTextFormField(controller: emailController, textInputAction: TextInputAction.done); + }), + SizedBox(height: 22.v), + Text("password".tr, style: theme.textTheme.bodyLarge), + SizedBox(height: 5.v), + BlocSelector( + selector: (state) => state.passwordController, + builder: (context, passwordController) { + return CustomTextFormField( + obscureText: true, + controller: passwordController, + ); + }), + SizedBox(height: 50.v), + NextStepButton( + text: "no_account".tr.toUpperCase(), + margin: EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v), + buttonStyle: CustomButtonStyles.outlinePrimaryTL15, + onPressed: onTapNoAccount(context), + ), + /*NextStepButton( + text: "Тест дневника".toUpperCase(), margin: EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v), buttonStyle: CustomButtonStyles.outlinePrimaryTL15, - onPressed: onTapNoAccount(context)), + onPressed: onTapDiaryTest(context)), NextStepButton( text: "authentificate".tr.toUpperCase(), margin: EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v), buttonStyle: CustomButtonStyles.outlinePrimaryTL15, - onPressed: onTapAuth(state, context)), - ]), - )); - }); + onPressed: onTapProfileTest(context)),*/ + BlocBuilder(builder: (context, state) { + return NextStepButton( + text: "authentificate".tr.toUpperCase(), + margin: EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v), + buttonStyle: CustomButtonStyles.outlinePrimaryTL15, + onPressed: onTapAuth(state, context)); + }), + ])))); } onTapAuth(AuthState state, BuildContext context) { return () async { - List errors = validateFields( - state.emailController!.text, state.passwordController!.text); + List errors = validateFields(state.emailController!.text, state.passwordController!.text); if (errors.isEmpty) { final Map credentials = { 'email': state.emailController!.text, 'password': state.passwordController!.text, }; - final response = await http.post( - Uri.parse(dotenv.env['SERVER_ROOT']! + '/login'), - headers: {'Content-Type': 'application/json'}, - body: json.encode(credentials), - encoding: utf8); + final response = await http.post(Uri.parse(dotenv.env['SERVER_ROOT']! + '/login'), + headers: {'Content-Type': 'application/json'}, body: json.encode(credentials), encoding: utf8); print(response.statusCode); if (response.statusCode == 200) { var res = await jsonDecode(response.body); final box = await Hive.openBox('token'); final token = res['token']; box.put('token', token); - Navigator.pushNamed( - context, - AppRoutes.myProfile, - ); + TokenStorage.saveTokenInStorage(token); + GoRouter.of(context).go('/my-profile-screen'); } else { showDialog( context: context, @@ -134,34 +150,37 @@ class AuthScreen extends StatelessWidget { onTapOk(BuildContext context) { return () { - Navigator.pop(context); + GoRouter.of(context).pop(context); }; } onTapDiaryTest(BuildContext context) { return () { - Navigator.pushNamed(context, AppRoutes.diaryEdit); + GoRouter.of(context).goNamed('diaryScreen'); }; } onTapProfileTest(BuildContext context) { return () { - Navigator.pushNamed(context, AppRoutes.mySettings); + GoRouter.of(context).goNamed('mySettingsScreen'); }; } onTapNoAccount(BuildContext context) { return () { - Navigator.pushNamed(context, AppRoutes.languageSelectScreen); + GoRouter.of(context).goNamed('languageSelectScreen'); }; } /// Section Widget _buildAppBar(BuildContext context) { return CustomAppBar( - leadingWidth: 29.h, - centerTitle: true, - title: AppbarTitleImage(imagePath: ImageConstant.imgLogoEatvio)); + leadingWidth: 29.h, + centerTitle: true, + title: AppbarTitleImage( + imagePath: ImageConstant.imgLogoEatvio, + ), + ); } List validateFields(String email, String password) { diff --git a/lib/presentation/auth/auth_screen/bloc/auth_bloc.dart b/lib/presentation/auth/auth_screen/bloc/auth_bloc.dart index cd0203a..6622981 100644 --- a/lib/presentation/auth/auth_screen/bloc/auth_bloc.dart +++ b/lib/presentation/auth/auth_screen/bloc/auth_bloc.dart @@ -2,7 +2,9 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import '/core/app_export.dart'; import 'package:eatvio_app/presentation/auth/auth_screen/models/auth_model.dart'; + part 'auth_event.dart'; + part 'auth_state.dart'; /// A bloc that manages the state of a Auth according to the event that is dispatched to it. @@ -15,8 +17,11 @@ class AuthBloc extends Bloc { AuthInitialEvent event, Emitter emit, ) async { - emit(state.copyWith( + emit( + state.copyWith( emailController: TextEditingController(), - passwordController: TextEditingController())); + passwordController: TextEditingController(), + ), + ); } } diff --git a/lib/presentation/chat/chat_screen/bloc/chat_bloc.dart b/lib/presentation/chat/chat_screen/bloc/chat_bloc.dart deleted file mode 100644 index 52cf81d..0000000 --- a/lib/presentation/chat/chat_screen/bloc/chat_bloc.dart +++ /dev/null @@ -1,79 +0,0 @@ -import 'package:equatable/equatable.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; -import 'package:hive/hive.dart'; -import 'package:http/http.dart' as http; -import 'dart:convert'; -import '/core/app_export.dart'; -import 'package:eatvio_app/presentation/chat/chat_screen/models/chat_model.dart'; -import 'package:eatvio_app/presentation/chat/chat_screen/models/chat_message_model.dart'; -part 'chat_event.dart'; -part 'chat_state.dart'; - -/// A bloc that manages the state of a Chat according to the event that is dispatched to it. -class ChatBloc extends Bloc { - ChatBloc(ChatState initialState) : super(initialState) { - on(_onInitialize); - } - - _onInitialize( - ChatInitialEvent event, - Emitter emit, - ) async { - try { - final box = await Hive.openBox('token'); - final token = box.get('token'); - if (token == null) { - NavigatorService.popAndPushNamed( - AppRoutes.authScreen, - ); - } - final String? chatID = state.chatModelObj?.chatID; - final response = await http.get( - Uri.parse(dotenv.env['SERVER_ROOT']! + '/chat/show/$chatID'), - headers: { - 'Authorization': 'Bearer $token', - }, - ); - if (response.statusCode == 200) { - final data = jsonDecode(response.body); - if (data is Map && - data.containsKey('chatRoom') && - data['chatRoom'].containsKey('messages')) { - if (state is ChatLoadedState) { - var currentState = state as ChatLoadedState; - emit(currentState.copyWith( - chatModelObj: currentState.chatModelObj, - messages: currentState.messages)); - } else { - emit(ChatLoadedState( - messages: fillMessages(data['chatRoom']['messages']))); - } - } else { - // Один или несколько ключевых полей отсутствуют, обрабатываем ошибку - emit(ChatErrorState('Некоторые необходимые данные отсутствуют.')); - } - } else { - // Статус ответа не равен 200, обрабатываем ошибку - emit(ChatErrorState('Ошибка сервера: ${response.statusCode}')); - } - } catch (e) { - // Ошибка при декодировании JSON или другая ошибка выполнения - emit(ChatErrorState('Ошибка при получении данных о чатах: $e')); - } - } - - List fillMessages(List messagesJSON) { - List messages = []; - for (int i = 0; i < messagesJSON.length; i++) { - messages.add(ChatMessageModel( - id: messagesJSON[i]['id'], - data: messagesJSON[i]['data'], - mediaData: messagesJSON[i]['mediaData'] ?? [], - updatedAt: messagesJSON[i]['updated_at'], - authorId: messagesJSON[i]['author_id'])); - } - - return messages; - } -} diff --git a/lib/presentation/chat/chat_screen/bloc/chat_event.dart b/lib/presentation/chat/chat_screen/bloc/chat_event.dart deleted file mode 100644 index 1fe6df9..0000000 --- a/lib/presentation/chat/chat_screen/bloc/chat_event.dart +++ /dev/null @@ -1,16 +0,0 @@ -// ignore_for_file: must_be_immutable - -part of 'chat_bloc.dart'; - -/// Abstract class for all events that can be dispatched from the -///Chat widget. -/// -/// Events must be immutable and implement the [Equatable] interface. -@immutable -abstract class ChatEvent extends Equatable {} - -/// Event that is dispatched when the Chat widget is first created. -class ChatInitialEvent extends ChatEvent { - @override - List get props => []; -} diff --git a/lib/presentation/chat/chat_screen/bloc/chat_state.dart b/lib/presentation/chat/chat_screen/bloc/chat_state.dart deleted file mode 100644 index 6f5718d..0000000 --- a/lib/presentation/chat/chat_screen/bloc/chat_state.dart +++ /dev/null @@ -1,53 +0,0 @@ -// ignore_for_file: must_be_immutable - -part of 'chat_bloc.dart'; - -/// Represents the state of Chat in the application. -/// - -class ChatState extends Equatable { - ChatState({ - this.chatModelObj, - }); - - ChatModel? chatModelObj; - - @override - List get props => [chatModelObj]; - ChatState copyWith({ChatModel? chatModelObj}) { - return ChatState( - chatModelObj: chatModelObj ?? this.chatModelObj, - ); - } -} - -class ChatLoadedState extends ChatState { - ChatLoadedState({this.chatModelObj, this.messages}); - - ChatModel? chatModelObj; - List? messages; - - @override - List get props => [chatModelObj, messages]; - ChatLoadedState copyWith( - {ChatModel? chatModelObj, List? messages}) { - return ChatLoadedState( - chatModelObj: chatModelObj ?? this.chatModelObj, - messages: messages ?? this.messages, - ); - } -} - -class ChatErrorState extends ChatState { - final String errorMessage; - - ChatErrorState(this.errorMessage); - - @override - List get props => [errorMessage]; -} - -class ChatInitialState extends ChatState { - ChatInitialState({required ChatModel chatModelObj}) - : super(chatModelObj: chatModelObj); -} diff --git a/lib/presentation/chat/chat_screen/chat_screen.dart b/lib/presentation/chat/chat_screen/chat_screen.dart deleted file mode 100644 index 37c3df5..0000000 --- a/lib/presentation/chat/chat_screen/chat_screen.dart +++ /dev/null @@ -1,192 +0,0 @@ -import 'bloc/chat_bloc.dart'; -import 'models/chat_model.dart'; -import 'package:eatvio_app/core/app_export.dart'; -import 'package:flutter/material.dart'; -import 'package:eatvio_app/widgets/app_bar/custom_app_bar.dart'; -import 'package:eatvio_app/widgets/app_bar/appbar_subtitle.dart'; -import 'package:eatvio_app/widgets/app_bar/appbar_leading_image.dart'; -import 'package:eatvio_app/widgets/app_bar/appbar_title_image.dart'; -import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; -import 'package:eatvio_app/widgets/custom_text_form_field.dart'; - -class ChatScreen extends StatelessWidget { - const ChatScreen({Key? key}) : super(key: key); - static Widget builder(BuildContext context) { - final chatID = ModalRoute.of(context)!.settings.arguments; - return BlocProvider( - create: (context) => ChatBloc( - ChatState(chatModelObj: ChatModel(chatID: chatID.toString()))) - ..add(ChatInitialEvent()), - child: ChatScreen()); - } - - Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - if (state is ChatInitialState) { - // Это начальное состояние, данные ещё не загружены. - return Container( - color: Colors.white, - ); - } else if (state is ChatLoadedState) { - // Данные загружены, можно строить экран. - return _buildChat(context, state); - } else if (state is ChatErrorState) { - // Произошла ошибка при загрузке данных. - return Center(child: Text(state.errorMessage)); - } else { - // Другие состояния, если они есть. - return Container(); // Пустой контейнер для необработанных состояний. - } - }, - ); - } - - _buildChat(BuildContext context, ChatLoadedState state) { - mediaQueryData = MediaQuery.of(context); - return Scaffold( - resizeToAvoidBottomInset: false, - appBar: _buildAppbar(context), - body: SizedBox( - width: double.maxFinite, - child: Container( - width: double.maxFinite, - decoration: AppDecoration.fillGray, - child: ListView( - children: [ - SizedBox(height: 30.v), - ], - ), - ), - ), - bottomNavigationBar: _buildTextField(context), - ); - } - - /// Section Widget - _buildAppbar(BuildContext context) { - return CustomAppBar( - leadingWidth: 26.h, - leading: AppbarLeadingImage( - imagePath: ImageConstant.imgArrowLeft, - margin: EdgeInsets.only( - left: 16.h, - top: 18.v, - bottom: 25.v, - ), - onTap: () => onTapArrowleftone(context), - ), - title: Padding( - padding: EdgeInsets.only(left: 12.h), - child: Row( - children: [ - AppbarTitleImage( - imagePath: ImageConstant.imgIconMoreHorizontal, - margin: EdgeInsets.only(bottom: 3.v), - ), - Padding( - padding: EdgeInsets.only( - left: 12.h, - top: 2.v, - ), - child: Column( - children: [ - AppbarSubtitle( - text: "Валерий Леонтьев", - ), - SizedBox(height: 3.v), - Text("Был в сети вчера", style: theme.textTheme.bodySmall!) - ], - ), - ) - ], - ), - ), - ); - } - - /// Section Widget - _buildTextField(BuildContext context) { - return Container( - width: double.maxFinite, - padding: EdgeInsets.symmetric( - horizontal: 7.h, - vertical: 6.v, - ), - decoration: AppDecoration.outlineBlueGray, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - CustomImageView( - imagePath: ImageConstant.imgAttach, - height: 29.adaptSize, - width: 29.adaptSize, - margin: EdgeInsets.only( - top: 1.v, - bottom: 6.v, - ), - ), - Expanded( - child: Padding( - padding: EdgeInsets.only( - left: 4.h, - bottom: 4.v, - ), - child: BlocSelector( - selector: (state) => TextEditingController(), - builder: (context, messageController) { - return CustomTextFormField( - controller: messageController, - hintText: "Напишите сообщение...", - textInputAction: TextInputAction.done, - ); - }, - ), - ), - ), - CustomImageView( - imagePath: ImageConstant.imgMic, - height: 24.v, - width: 18.h, - margin: EdgeInsets.only( - left: 11.h, - top: 4.v, - bottom: 8.v, - ), - ) - ], - ), - ); - } - - /// Common widget - - ///Handling route based on bottom click actions - String getCurrentRoute(BottomBarEnum type) { - switch (type) { - case BottomBarEnum.tf: - return AppRoutes.chatScreen; - default: - return "/"; - } - } - - ///Handling page based on route - Widget getCurrentPage( - BuildContext context, - String currentRoute, - ) { - switch (currentRoute) { - case AppRoutes.chatScreen: - return ChatScreen.builder(context); - default: - return ChatScreen(); - } - } - - /// Navigates to the previous screen. - onTapArrowleftone(BuildContext context) { - NavigatorService.goBack(); - } -} diff --git a/lib/presentation/chat/chat_screen/models/chat_message_model.dart b/lib/presentation/chat/chat_screen/models/chat_message_model.dart deleted file mode 100644 index e91596f..0000000 --- a/lib/presentation/chat/chat_screen/models/chat_message_model.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:equatable/equatable.dart'; - -class ChatMessageModel extends Equatable { - final int id; - final String data; - final List mediaData; - final int authorId; - final String updatedAt; - - ChatMessageModel({ - required this.id, - required this.data, - required this.mediaData, - required this.authorId, - required this.updatedAt, - }); - - ChatMessageModel copyWith({ - int? id, - String? data, - List? mediaData, - int? authorId, - String? updatedAt, - }) { - return ChatMessageModel( - id: id ?? this.id, - data: data ?? this.data, - mediaData: mediaData ?? this.mediaData, - authorId: authorId ?? this.authorId, - updatedAt: updatedAt ?? this.updatedAt, - ); - } - - @override - List get props => [id, data, mediaData, authorId, updatedAt]; -} diff --git a/lib/presentation/chat/chat_screen/models/chat_model.dart b/lib/presentation/chat/chat_screen/models/chat_model.dart deleted file mode 100644 index 970b9ad..0000000 --- a/lib/presentation/chat/chat_screen/models/chat_model.dart +++ /dev/null @@ -1,19 +0,0 @@ -// ignore_for_file: must_be_immutable - -import 'package:equatable/equatable.dart'; - -/// This class defines the variables used in the [chat_screen], -/// and is typically used to hold data that is passed between different parts of the application. - -class ChatModel extends Equatable { - final String chatID; - - ChatModel({required String chatID}) : chatID = chatID; - - ChatModel copyWith(String chatID) { - return ChatModel(chatID: chatID); - } - - @override - List get props => [chatID]; -} diff --git a/lib/presentation/chat/chat_select_screen/chat_select_screen.dart b/lib/presentation/chat/chat_select_screen/chat_select_screen.dart deleted file mode 100644 index 6304ca2..0000000 --- a/lib/presentation/chat/chat_select_screen/chat_select_screen.dart +++ /dev/null @@ -1,110 +0,0 @@ -import 'bloc/chat_select_bloc.dart'; -import 'models/chat_select_model.dart'; -import 'package:eatvio_app/core/app_export.dart'; -import '../chat_select_screen/chat_search_view.dart'; -import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; -import 'package:flutter/material.dart'; -import 'package:eatvio_app/presentation/chat/chat_select_screen/models/chat_entry.dart'; // ignore: must_be_immutable -import 'package:eatvio_app/presentation/chat/chat_select_screen/widgets/chat_entry_widget.dart'; // ignore: must_be_immutable - -class ChatSelectScreen extends StatelessWidget { - const ChatSelectScreen({Key? key}) : super(key: key); - static Widget builder(BuildContext context) { - return BlocProvider( - create: (context) => ChatSelectBloc( - ChatSelectState(chatSelectModelObj: ChatSelectModel())) - ..add(ChatSelectInitialEvent()), - child: ChatSelectScreen()); - } - - Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - if (state is ChatSelectInitialState) { - // Это начальное состояние, данные ещё не загружены. - return Container( - color: Colors.white, - ); - } else if (state is ChatSelectLoadedState) { - // Данные загружены, можно строить экран. - return _buildScreen(context, state); - } else if (state is ChatSelectErrorState) { - // Произошла ошибка при загрузке данных. - return Center(child: Text(state.errorMessage)); - } else { - // Другие состояния, если они есть. - return Container(); // Пустой контейнер для необработанных состояний. - } - }, - ); - } - - _buildScreen(BuildContext context, ChatSelectLoadedState state) { - mediaQueryData = MediaQuery.of(context); - return Scaffold( - resizeToAvoidBottomInset: false, - body: SafeArea( - child: Container( - width: double.maxFinite, - decoration: AppDecoration.fillGray, - child: ListView( - children: [ - Container( - margin: EdgeInsets.only( - left: 16.h, - right: 20.h, - bottom: 10.h, - ), - padding: EdgeInsets.all(12.h), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: const BorderRadius.all(Radius.circular(12)), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 2, - blurRadius: 5, - offset: Offset(0, 3), - ), - ], - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - CustomSearchView( - controller: state.searchController, - hintText: "Поиск", - ), - SizedBox(height: 20.v), - _buildChatList(context, state), - ], - ), - ) - ], - ), - ), - ), bottomNavigationBar: _buildBottomBar(context)); - } -} - -_buildChatList(BuildContext context, ChatSelectLoadedState state) { - return ListView.separated( - shrinkWrap: true, - separatorBuilder: (context, index) { - return SizedBox( - height: 10.v, - ); - }, - itemCount: state.chatRooms?.length ?? 0, - itemBuilder: (context, index) { - ChatItemModel model = state.chatRooms?[index]; - return ChatEntryWidget( - model, - ); - }, - ); -} - -_buildBottomBar(BuildContext context) { - return CustomBottomBar(onChanged: (BottomBarEnum type) {}); -} diff --git a/lib/presentation/chat/chat_select_screen/widgets/chat_entry_widget.dart b/lib/presentation/chat/chat_select_screen/widgets/chat_entry_widget.dart deleted file mode 100644 index 52f9de3..0000000 --- a/lib/presentation/chat/chat_select_screen/widgets/chat_entry_widget.dart +++ /dev/null @@ -1,110 +0,0 @@ -// ignore_for_file: must_be_immutable - -import 'package:flutter/material.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; -import 'package:eatvio_app/core/app_export.dart'; -import 'package:eatvio_app/presentation/chat/chat_select_screen/models/chat_entry.dart'; -import 'package:cached_network_image/cached_network_image.dart'; - -class ChatEntryWidget extends StatelessWidget { - ChatEntryWidget(this.chatEntryObj, {Key? key}) - : super( - key: key, - ); - - ChatItemModel chatEntryObj; - - @override - Widget build(BuildContext context) { - return GestureDetector( - onTap: () => Navigator.pushNamed( - context, - AppRoutes.chatScreen, - arguments: chatEntryObj.id.toString(), - ), - child: Container( - padding: EdgeInsets.all(8.h), - decoration: AppDecoration.fillGray100.copyWith( - borderRadius: BorderRadiusStyle.circleBorder8, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - CachedNetworkImage( - imageUrl: dotenv.env['STORAGE_URL']! + chatEntryObj.userImage!, - imageBuilder: (context, imageProvider) => CircleAvatar( - backgroundImage: imageProvider, - radius: 25, - ), - placeholder: (context, url) => CircularProgressIndicator(), - errorWidget: (context, url, error) => CircleAvatar( - child: Icon(Icons.person), - backgroundColor: Colors.white, - radius: 25, - ), - ), - /*CustomImageView( - imagePath: - dotenv.env['STORAGE_URL']! + chatEntryObj.userImage!, - height: 60.adaptSize, - width: 60.adaptSize, - radius: BorderRadius.circular( - 30.h, - ), - ),*/ - Expanded( - child: Padding( - padding: EdgeInsets.only( - left: 12.h, - top: 2.v, - bottom: 11.v, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: 227.h, - child: Row( - children: [ - Text( - chatEntryObj.username!.length <= 15 - ? chatEntryObj.username! - : chatEntryObj.username!.substring(0, 15) + - '...', - style: theme.textTheme.titleMedium, - ), - Spacer(), - CustomImageView( - imagePath: ImageConstant.imageNotFound, - height: 9.v, - width: 16.h, - margin: EdgeInsets.only( - top: 4.v, - bottom: 6.v, - ), - ), - Padding( - padding: EdgeInsets.only(left: 6.h), - child: Text( - chatEntryObj.dateText!, - style: theme.textTheme.bodyMedium, - ), - ) - ], - ), - ), - SizedBox(height: 6.v), - Text( - chatEntryObj.greetingText!, - style: CustomTextStyles.bodyMediumBluegray800, - ) - ], - ), - ), - ) - ]), - ), - ); - } -} diff --git a/lib/presentation/diary/diary_edit_screen/diary_edit_screen.dart b/lib/presentation/diary/diary_edit_screen/diary_edit_screen.dart index 0bef582..e2be948 100644 --- a/lib/presentation/diary/diary_edit_screen/diary_edit_screen.dart +++ b/lib/presentation/diary/diary_edit_screen/diary_edit_screen.dart @@ -13,22 +13,15 @@ import 'package:eatvio_app/widgets/custom_edit_text_form_field.dart'; import 'package:eatvio_app/widgets/custom_edit_drop_down.dart'; import 'package:eatvio_app/widgets/custom_outlined_button.dart'; import 'package:eatvio_app/widgets/custom_scale_element.dart'; -import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; import 'package:carousel_slider/carousel_slider.dart'; // ignore: must_be_immutable class DiaryEditScreen extends StatelessWidget { - final void Function() onInitialize; - DiaryEditScreen({Key? key, required this.onInitialize}) : super(key: key); + DiaryEditScreen({Key? key}) : super(key: key); static Widget builder(BuildContext context) { return BlocProvider( - create: (context) => - DiaryEditBloc(DiaryEditState(diaryEditModelObj: DiaryEditModel())) - ..add(DiaryEditInitialEvent()), - child: DiaryEditScreen( - onInitialize: () {}, - )); + create: (context) => DiaryEditBloc(DiaryEditState(diaryEditModelObj: DiaryEditModel()))..add(DiaryEditInitialEvent()), child: DiaryEditScreen()); } Widget build(BuildContext context) { @@ -53,56 +46,52 @@ class DiaryEditScreen extends StatelessWidget { ); } - Widget _buildDiaryEditScreen( - BuildContext context, DiaryEditLoadedState state) { - mediaQueryData = MediaQuery.of(context); + Widget _buildDiaryEditScreen(BuildContext context, DiaryEditLoadedState state) { return RefreshIndicator( onRefresh: refreshPage(context), child: Stack( + alignment: Alignment.bottomCenter, children: [ FormBuilder( child: Scaffold( - backgroundColor: Color.fromARGB(255, 240, 242, 245), - resizeToAvoidBottomInset: true, - body: SafeArea( - child: SizedBox( - width: double.maxFinite, - child: Column( - children: [ - Expanded( - child: SingleChildScrollView( - child: Padding( - padding: EdgeInsets.only(bottom: 5.v), - child: Column( - children: [ - _buildHeaderRow(context, state), - SizedBox(height: 30.v), - _buildWakupRetire(context, state), - SizedBox(height: 30.v), - _buildMeal(context, state), - SizedBox(height: 20.v), - _buildMood(context, state), - SizedBox(height: 30.v), - _buildNutritionChart(context, state), - SizedBox(height: 30.v), - _buildComment(context, state), - ], - ), + appBar: AppBar( + toolbarHeight: 0, + automaticallyImplyLeading: false, + ), + backgroundColor: Color.fromARGB(255, 240, 242, 245), + resizeToAvoidBottomInset: true, + body: SizedBox( + width: double.maxFinite, + child: Column( + children: [ + Expanded( + child: SingleChildScrollView( + child: Padding( + padding: EdgeInsets.only(bottom: 5.v), + child: Column( + children: [ + _buildHeaderRow(context, state), + SizedBox(height: 30.v), + _buildWakupRetire(context, state), + SizedBox(height: 30.v), + _buildMeal(context, state), + SizedBox(height: 20.v), + _buildMood(context, state), + SizedBox(height: 30.v), + _buildNutritionChart(context, state), + SizedBox(height: 30.v), + _buildComment(context, state), + ], ), ), ), - ], - ), + ), + ], ), ), - bottomNavigationBar: _buildBottomBar(context) ), - ), - Positioned( - left: 16.h, - right: 16.h, - bottom: 50.v, // Adjusted position to lift the button higher - child: _buildEndDay(context), + ), ), + _buildEndDay(context), ], ), ); @@ -114,7 +103,7 @@ class DiaryEditScreen extends StatelessWidget { margin: EdgeInsets.only( left: 16.h, right: 16.h, - bottom: 41.v, + bottom: 16.v, ), buttonStyle: CustomButtonStyles.outlinePrimaryTL15, decoration: BoxDecoration( @@ -153,10 +142,8 @@ class DiaryEditScreen extends StatelessWidget { ], ), child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: MainAxisAlignment.center, children: [ - CustomImageView( - imagePath: ImageConstant.imgArrowLeft, onTap: onTapBack()), Padding( padding: EdgeInsets.only(top: 1.v), child: Column( @@ -181,16 +168,6 @@ class DiaryEditScreen extends StatelessWidget { ); } - onTapBack() { - return () { - NavigatorService.goBack(); - }; - } - - _buildBottomBar(BuildContext context) { - return CustomBottomBar(onChanged: (BottomBarEnum type) {}); - } - /// Section Widget _buildAddProduct(BuildContext context, DiaryEditLoadedState state) { return CustomOutlinedButton( @@ -259,9 +236,7 @@ class DiaryEditScreen extends StatelessWidget { borderRadius: BorderRadiusStyle.roundedBorder5, ), child: Center( - child: CircularProgressIndicator( - color: appTheme - .blueGray300), // Замените на свой прелоадер при необходимости + child: CircularProgressIndicator(color: appTheme.blueGray300), // Замените на свой прелоадер при необходимости ), ), ], @@ -285,8 +260,7 @@ class DiaryEditScreen extends StatelessWidget { _buildMeal(BuildContext context, DiaryEditLoadedState state) { final digitsOnly = FilteringTextInputFormatter.digitsOnly; - List _options = - state.autocompleteList!.map((dynamic item) => item as String).toList(); + List _options = state.autocompleteList!.map((dynamic item) => item as String).toList(); //print(_options); FocusNode focusNodeName = FocusNode(); return Container( @@ -331,9 +305,7 @@ class DiaryEditScreen extends StatelessWidget { onTap: () => _selectMealTime(context, state), child: AbsorbPointer( child: CustomTextFormField( - hintText: state.mealTimeController - ?.format(context) - .toString(), + hintText: state.mealTimeController?.format(context).toString(), autofocus: false, ), ), @@ -347,8 +319,7 @@ class DiaryEditScreen extends StatelessWidget { SizedBox(height: 20.v), Padding( padding: EdgeInsets.symmetric(horizontal: 10.h), - child: BlocSelector( + child: BlocSelector( selector: (state) => state.diaryEditModelObj, builder: (context, diaryEditModelObj) { return CustomDropDown( @@ -440,8 +411,7 @@ class DiaryEditScreen extends StatelessWidget { ), Padding( padding: EdgeInsets.only(left: 30.h), - child: BlocSelector( + child: BlocSelector( selector: (state) => state.diaryEditModelObj, builder: (context, diaryEditModelObj) { return CustomDropDown( @@ -459,9 +429,7 @@ class DiaryEditScreen extends StatelessWidget { SizedBox(height: 30.v), _buildAddProduct(context, state), SizedBox(height: 30.v), - ...state.portions! - .map((portion) => _buildProduct(context, portion)) - .toList(), + ...state.portions!.map((portion) => _buildProduct(context, portion)).toList(), SizedBox(height: 30.v), /*...state.photos != null ? state.photos!.isEmpty @@ -574,14 +542,8 @@ class DiaryEditScreen extends StatelessWidget { child: Text('Отмена'), ), TextButton( - onPressed: triggerModeration( - context, - state.productNameController!.text, - state.caloriesController!.text, - state.proteinsController!.text, - state.fatsController!.text, - state.carbsController!.text, - dialogContext), + onPressed: triggerModeration(context, state.productNameController!.text, state.caloriesController!.text, state.proteinsController!.text, + state.fatsController!.text, state.carbsController!.text, dialogContext), child: Text('Отправить'), ), ], @@ -590,30 +552,19 @@ class DiaryEditScreen extends StatelessWidget { ); } - triggerModeration( - BuildContext context, - String name, - String energyOn100gr, - String proteinsOn100gr, - String fatsOn100gr, - String carbohydratesOn100gr, + triggerModeration(BuildContext context, String name, String energyOn100gr, String proteinsOn100gr, String fatsOn100gr, String carbohydratesOn100gr, BuildContext dialogContext) { return () { // Call function to send moderation with the entered values context.read().add(SendModeration( - name: name, - energyOn100gr: energyOn100gr, - proteinsOn100gr: proteinsOn100gr, - fatsOn100gr: fatsOn100gr, - carbohydratesOn100gr: carbohydratesOn100gr)); + name: name, energyOn100gr: energyOn100gr, proteinsOn100gr: proteinsOn100gr, fatsOn100gr: fatsOn100gr, carbohydratesOn100gr: carbohydratesOn100gr)); Navigator.of(dialogContext).pop(); // Dismiss the dialog }; } popDialog(BuildContext dialogContext) { return () { - Navigator.of(dialogContext) - .pop(); // Dismiss the dialog on Cancel button press + Navigator.of(dialogContext).pop(); // Dismiss the dialog on Cancel button press }; } @@ -623,8 +574,7 @@ class DiaryEditScreen extends StatelessWidget { }; } - selectProduct(AutocompleteOnSelected onSelected, String option, - DiaryEditLoadedState state) { + selectProduct(AutocompleteOnSelected onSelected, String option, DiaryEditLoadedState state) { state.productNameController?.text = option; return () { onSelected(option); @@ -652,9 +602,7 @@ class DiaryEditScreen extends StatelessWidget { changeMeal(BuildContext context, DiaryEditLoadedState state) { return (value) { - context - .read() - .add(ChangeMeal(meals: state.meals, value: value)); + context.read().add(ChangeMeal(meals: state.meals, value: value)); }; } @@ -671,8 +619,7 @@ class DiaryEditScreen extends StatelessWidget { return CarouselSlider( options: CarouselOptions(height: 340.0), items: state.photos!.map((photo) { - return _buildPhoto( - context, photo); // Assuming photos are stored as strings + return _buildPhoto(context, photo); // Assuming photos are stored as strings }).toList(), ); } @@ -698,9 +645,7 @@ class DiaryEditScreen extends StatelessWidget { top: 5.v, ), child: Text( - portion['product_name'].length > 8 - ? portion['product_name'].substring(0, 8) - : portion['product_name'], + portion['product_name'].length > 8 ? portion['product_name'].substring(0, 8) : portion['product_name'], style: CustomTextStyles.titleMedium16, )), Padding( @@ -710,9 +655,7 @@ class DiaryEditScreen extends StatelessWidget { bottom: 3.v, ), child: Text( - portion['quantity'].toString() + - ' ' + - portion['portion'].toString().tr, + portion['quantity'].toString() + ' ' + portion['portion'].toString().tr, ), ), Spacer(), @@ -772,15 +715,12 @@ class DiaryEditScreen extends StatelessWidget { removePhoto(BuildContext context, Map photo) { return () { - context - .read() - .add(RemoveImageEvent(photo['id'].toString(), photo['path'])); + context.read().add(RemoveImageEvent(photo['id'].toString(), photo['path'])); }; } _buildNutritionChart(BuildContext context, DiaryEditLoadedState state) { - final List recommendedValues = state - .recommendedChartValues; // Рекомендуемые значения: калории, жиры, белки, углеводы + final List recommendedValues = state.recommendedChartValues; // Рекомендуемые значения: калории, жиры, белки, углеводы final List consumedValues = state.consumedChartValues; // return Container( margin: EdgeInsets.symmetric(horizontal: 16.h), @@ -842,37 +782,25 @@ class DiaryEditScreen extends StatelessWidget { BarChartGroupData(x: 0, barRods: [ BarChartRodData( toY: consumedValues[0] / recommendedValues[0] * 100, - color: checkConsumedValues( - [recommendedValues[0]], [consumedValues[1]]) - ? Colors.red - : Colors.blue, + color: checkConsumedValues([recommendedValues[0]], [consumedValues[1]]) ? Colors.red : Colors.blue, ) ]), BarChartGroupData(x: 1, barRods: [ BarChartRodData( toY: consumedValues[1] / recommendedValues[1] * 100, - color: checkConsumedValues( - [recommendedValues[1]], [consumedValues[1]]) - ? Colors.red - : Colors.blue, + color: checkConsumedValues([recommendedValues[1]], [consumedValues[1]]) ? Colors.red : Colors.blue, ) ]), BarChartGroupData(x: 2, barRods: [ BarChartRodData( toY: consumedValues[2] / recommendedValues[2] * 100, - color: checkConsumedValues( - [recommendedValues[2]], [consumedValues[2]]) - ? Colors.red - : Colors.blue, + color: checkConsumedValues([recommendedValues[2]], [consumedValues[2]]) ? Colors.red : Colors.blue, ) ]), BarChartGroupData(x: 3, barRods: [ BarChartRodData( toY: consumedValues[3] / recommendedValues[3] * 100, - color: checkConsumedValues( - [recommendedValues[3]], [consumedValues[3]]) - ? Colors.red - : Colors.blue, + color: checkConsumedValues([recommendedValues[3]], [consumedValues[3]]) ? Colors.red : Colors.blue, ) ]), ], @@ -1062,9 +990,7 @@ class DiaryEditScreen extends StatelessWidget { r"Подъем \ отход ко сну", style: CustomTextStyles.bodyMediumBlack900, ), - Icon(state.wakeupExpanded - ? Icons.arrow_drop_up - : Icons.arrow_drop_down), + Icon(state.wakeupExpanded ? Icons.arrow_drop_up : Icons.arrow_drop_down), ], ), ), @@ -1078,8 +1004,7 @@ class DiaryEditScreen extends StatelessWidget { onTap: () => _selectWakeupTime(context, state), child: AbsorbPointer( child: CustomTextFormField( - hintText: - state.wakeupController?.format(context).toString(), + hintText: state.wakeupController?.format(context).toString(), prefix: Icon(Icons.sunny), autofocus: false, ), @@ -1090,8 +1015,7 @@ class DiaryEditScreen extends StatelessWidget { onTap: () => _selectRetireTime(context, state), child: AbsorbPointer( child: CustomTextFormField( - hintText: - state.retireController?.format(context).toString(), + hintText: state.retireController?.format(context).toString(), prefix: Icon(Icons.bed), autofocus: false, ), @@ -1138,8 +1062,7 @@ class DiaryEditScreen extends StatelessWidget { }; } - Function() sendFinishDayData( - BuildContext dialogContext, BuildContext context) { + Function() sendFinishDayData(BuildContext dialogContext, BuildContext context) { return () { closeDialog(dialogContext); context.read().add(FinishDay()); @@ -1181,9 +1104,7 @@ class DiaryEditScreen extends StatelessWidget { "Ваше настроение после приема пищи", style: CustomTextStyles.bodyMediumBlack900, ), - Icon(state.moodExpanded - ? Icons.arrow_drop_up - : Icons.arrow_drop_down), + Icon(state.moodExpanded ? Icons.arrow_drop_up : Icons.arrow_drop_down), ], ), ), @@ -1241,8 +1162,7 @@ class DiaryEditScreen extends StatelessWidget { }; } - Future _selectWakeupTime( - BuildContext context, DiaryEditLoadedState state) async { + Future _selectWakeupTime(BuildContext context, DiaryEditLoadedState state) async { final TimeOfDay? pickedTime = await showTimePicker( context: context, initialTime: state.wakeupController ?? TimeOfDay.now(), @@ -1253,8 +1173,7 @@ class DiaryEditScreen extends StatelessWidget { } } - Future _selectRetireTime( - BuildContext context, DiaryEditLoadedState state) async { + Future _selectRetireTime(BuildContext context, DiaryEditLoadedState state) async { final TimeOfDay? pickedTime = await showTimePicker( context: context, initialTime: state.retireController ?? TimeOfDay.now(), @@ -1265,8 +1184,7 @@ class DiaryEditScreen extends StatelessWidget { } } - Future _selectMealTime( - BuildContext context, DiaryEditLoadedState state) async { + Future _selectMealTime(BuildContext context, DiaryEditLoadedState state) async { final TimeOfDay? pickedTime = await showTimePicker( context: context, initialTime: state.mealTimeController ?? TimeOfDay.now(), @@ -1278,8 +1196,7 @@ class DiaryEditScreen extends StatelessWidget { } } -bool checkConsumedValues( - List recommendedValues, List consumedValues) { +bool checkConsumedValues(List recommendedValues, List consumedValues) { for (int i = 0; i < recommendedValues.length; i++) { if (consumedValues[i] > recommendedValues[i]) { // Если хотя бы одно потребленное значение превышает рекомендованное diff --git a/lib/presentation/diets/diet_create_screen/bloc/diet_create_bloc.dart b/lib/presentation/diets/diet_create_screen/bloc/diet_create_bloc.dart index e517c1a..e4e0cbc 100644 --- a/lib/presentation/diets/diet_create_screen/bloc/diet_create_bloc.dart +++ b/lib/presentation/diets/diet_create_screen/bloc/diet_create_bloc.dart @@ -1,13 +1,16 @@ import 'dart:async'; +import 'package:eatvio_app/app_router.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:go_router/go_router.dart'; import 'package:hive/hive.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; import '/core/app_export.dart'; import 'package:eatvio_app/presentation/diets/diet_create_screen/models/diet_create_model.dart'; + part 'diet_create_event.dart'; part 'diet_create_state.dart'; @@ -25,9 +28,7 @@ class DietCreateBloc extends Bloc { final box = await Hive.openBox('token'); final token = box.get('token'); if (token == null) { - NavigatorService.popAndPushNamed( - AppRoutes.authScreen, - ); + AppRouter.navigatorKey.currentContext?.goNamed('authScreen'); } if (state is DietCreateLoadedState) { var currentState = state as DietCreateLoadedState; @@ -55,7 +56,7 @@ class DietCreateBloc extends Bloc { ); if (response.statusCode == 200) { - NavigatorService.pushNamed(AppRoutes.dietSelectScreen); + AppRouter.navigatorKey.currentContext?.goNamed('dietSelectScreen'); } else { print( 'Failed to create blog post. Status Code: ${response.statusCode} ${response.body}'); diff --git a/lib/presentation/diets/diet_create_screen/diet_create_screen.dart b/lib/presentation/diets/diet_create_screen/diet_create_screen.dart index 0ba4988..516a931 100644 --- a/lib/presentation/diets/diet_create_screen/diet_create_screen.dart +++ b/lib/presentation/diets/diet_create_screen/diet_create_screen.dart @@ -2,16 +2,14 @@ import 'bloc/diet_create_bloc.dart'; import 'models/diet_create_model.dart'; import 'package:eatvio_app/core/app_export.dart'; import 'package:flutter/material.dart'; -import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; class DietCreateScreen extends StatelessWidget { const DietCreateScreen({Key? key}) : super(key: key); + static Widget builder(BuildContext context) { final dietID = ModalRoute.of(context)!.settings.arguments; return BlocProvider( - create: (context) => DietCreateBloc(DietCreateState( - dietModelObj: DietCreateModel(dietID: dietID.toString()))) - ..add(DietCreateInitialEvent()), + create: (context) => DietCreateBloc(DietCreateState(dietModelObj: DietCreateModel(dietID: dietID.toString())))..add(DietCreateInitialEvent()), child: DietCreateScreen()); } @@ -39,58 +37,55 @@ class DietCreateScreen extends StatelessWidget { _buildDietCreate(BuildContext context, DietCreateLoadedState state) { mediaQueryData = MediaQuery.of(context); - return Scaffold( - resizeToAvoidBottomInset: false, - body: SafeArea( - child: Container( - width: double.maxFinite, - decoration: AppDecoration.fillGray, - child: ListView(children: [ - Container( - margin: EdgeInsets.only( - left: 16.h, - right: 20.h, - bottom: 10.h, + return SafeArea( + child: Scaffold( + resizeToAvoidBottomInset: false, + body: Container( + width: double.maxFinite, + decoration: AppDecoration.fillGray, + child: ListView(children: [ + SizedBox(height: 30.v), + Container( + margin: EdgeInsets.only( + left: 16.h, + right: 20.h, + bottom: 10.h, + ), + padding: EdgeInsets.all(12.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(12)), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), ), - padding: EdgeInsets.all(12.h), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: const BorderRadius.all(Radius.circular(12)), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 2, - blurRadius: 5, - offset: Offset(0, 3), - ), - ], - ), - child: Column(mainAxisSize: MainAxisSize.min, children: [ - SizedBox(height: 16.0), - TextFormField( - controller: state.name, - decoration: InputDecoration(labelText: 'Заголовок'), - ), - SizedBox(height: 16.0), - TextFormField( - controller: state.details, - decoration: InputDecoration(labelText: 'Описание'), - ), - SizedBox(height: 16.0), - // Здесь нужно добавить виджет для загрузки картинки - ElevatedButton( - onPressed: () => - context.read().add(CreateDiet()), - child: Text('Создать'), - ) - ]), + ], + ), + child: Column(mainAxisSize: MainAxisSize.min, children: [ + SizedBox(height: 16.0), + TextFormField( + controller: state.name, + decoration: InputDecoration(labelText: 'Заголовок'), + ), + SizedBox(height: 16.0), + TextFormField( + controller: state.details, + decoration: InputDecoration(labelText: 'Описание'), + ), + SizedBox(height: 16.0), + // Здесь нужно добавить виджет для загрузки картинки + ElevatedButton( + onPressed: () => context.read().add(CreateDiet()), + child: Text('Создать'), ) ]), - ), - ), bottomNavigationBar: _buildBottomBar(context)); + ) + ]), + ), + ), + ); } } - -_buildBottomBar(BuildContext context) { - return CustomBottomBar(onChanged: (BottomBarEnum type) {}); -} diff --git a/lib/presentation/diets/diet_edit_screen/bloc/diet_edit_bloc.dart b/lib/presentation/diets/diet_edit_screen/bloc/diet_edit_bloc.dart index d816779..e69101c 100644 --- a/lib/presentation/diets/diet_edit_screen/bloc/diet_edit_bloc.dart +++ b/lib/presentation/diets/diet_edit_screen/bloc/diet_edit_bloc.dart @@ -1,14 +1,18 @@ import 'dart:async'; +import 'package:eatvio_app/app_router.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:go_router/go_router.dart'; import 'package:hive/hive.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; import '/core/app_export.dart'; import 'package:eatvio_app/presentation/diets/diet_edit_screen/models/diet_edit_model.dart'; + part 'diet_edit_event.dart'; + part 'diet_edit_state.dart'; /// A bloc that manages the state of a DietEdit according to the event that is dispatched to it. @@ -26,9 +30,7 @@ class DietEditBloc extends Bloc { final box = await Hive.openBox('token'); final token = box.get('token'); if (token == null) { - NavigatorService.popAndPushNamed( - AppRoutes.authScreen, - ); + AppRouter.navigatorKey.currentContext?.goNamed('authScreen'); } final String? dietID = state.dietModelObj?.dietID; final response = await http.get( @@ -74,15 +76,15 @@ class DietEditBloc extends Bloc { var currentState = state as DietEditLoadedState; int duration = int.parse(currentState.duration!.text) * 7; print(duration); - var response = await sendDiet(currentState.diet, currentState.name?.text, - currentState.details?.text, duration.toString()); + var response = await sendDiet(currentState.diet, currentState.name?.text, currentState.details?.text, duration.toString()); if (response.statusCode == 200) { - NavigatorService.pushNamed(AppRoutes.dietScreen, - arguments: currentState.diet?['id'].toString()); + AppRouter.navigatorKey.currentContext?.goNamed( + 'dietScreen', + extra: currentState.diet?['id'].toString(), + ); } else { - print( - 'Failed to edit blog post. Status Code: ${response.statusCode} ${response.body}'); + print('Failed to edit blog post. Status Code: ${response.statusCode} ${response.body}'); } } } diff --git a/lib/presentation/diets/diet_edit_screen/diet_edit_screen.dart b/lib/presentation/diets/diet_edit_screen/diet_edit_screen.dart index 0a9ef1c..a0f988d 100644 --- a/lib/presentation/diets/diet_edit_screen/diet_edit_screen.dart +++ b/lib/presentation/diets/diet_edit_screen/diet_edit_screen.dart @@ -2,18 +2,16 @@ import 'bloc/diet_edit_bloc.dart'; import 'models/diet_edit_model.dart'; import 'package:eatvio_app/core/app_export.dart'; import 'package:flutter/material.dart'; -import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; class DietEditScreen extends StatelessWidget { const DietEditScreen({Key? key}) : super(key: key); + static Widget builder(BuildContext context) { final dietID = ModalRoute.of(context)!.settings.arguments; return BlocProvider( - create: (context) => DietEditBloc(DietEditState( - dietModelObj: DietEditModel(dietID: dietID.toString()))) - ..add(DietEditInitialEvent()), + create: (context) => DietEditBloc(DietEditState(dietModelObj: DietEditModel(dietID: dietID.toString())))..add(DietEditInitialEvent()), child: DietEditScreen()); } @@ -41,42 +39,44 @@ class DietEditScreen extends StatelessWidget { _buildDietEdit(BuildContext context, DietEditLoadedState state) { mediaQueryData = MediaQuery.of(context); - return Scaffold( - resizeToAvoidBottomInset: false, - body: SafeArea( - child: Container( - width: double.maxFinite, - decoration: AppDecoration.fillGray, - child: ListView(children: [ - Container( - margin: EdgeInsets.only( - left: 16.h, - right: 20.h, - bottom: 10.h, - ), - padding: EdgeInsets.all(12.h), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: const BorderRadius.all(Radius.circular(12)), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 2, - blurRadius: 5, - offset: Offset(0, 3), - ), - ], - ), - child: Column(mainAxisSize: MainAxisSize.min, children: [ + return SafeArea( + child: Scaffold( + resizeToAvoidBottomInset: false, + body: Container( + width: double.maxFinite, + decoration: AppDecoration.fillGray, + child: ListView( + children: [ + SizedBox(height: 30.v), + Container( + margin: EdgeInsets.only( + left: 16.h, + right: 20.h, + bottom: 10.h, + ), + padding: EdgeInsets.all(12.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(12)), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), + ), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ CachedNetworkImage( - imageUrl: dotenv.env['STORAGE_URL']! + - (state.diet?['image'] ?? ''), + imageUrl: dotenv.env['STORAGE_URL']! + (state.diet?['image'] ?? ''), imageBuilder: (context, imageProvider) => CircleAvatar( backgroundImage: imageProvider, radius: 25, ), - placeholder: (context, url) => - CircularProgressIndicator(), + placeholder: (context, url) => CircularProgressIndicator(), errorWidget: (context, url, error) => CircleAvatar( child: Icon(Icons.person), backgroundColor: Colors.white, @@ -96,18 +96,16 @@ class DietEditScreen extends StatelessWidget { SizedBox(height: 16.0), // Здесь нужно добавить виджет для загрузки картинки ElevatedButton( - onPressed: () => - context.read().add(EditDiet()), + onPressed: () => context.read().add(EditDiet()), child: Text('Сохранить'), ) - ]), - ) - ]), - ), - ), bottomNavigationBar: _buildBottomBar(context)); + ], + ), + ) + ], + ), + ), + ), + ); } } - -_buildBottomBar(BuildContext context) { - return CustomBottomBar(onChanged: (BottomBarEnum type) {}); -} diff --git a/lib/presentation/diets/diet_select_screen/bloc/diet_select_bloc.dart b/lib/presentation/diets/diet_select_screen/bloc/diet_select_bloc.dart index 49def2d..dbc622f 100644 --- a/lib/presentation/diets/diet_select_screen/bloc/diet_select_bloc.dart +++ b/lib/presentation/diets/diet_select_screen/bloc/diet_select_bloc.dart @@ -1,6 +1,7 @@ //import 'package:eatvio_app/presentation/auth/auth_screen/auth_screen.dart'; import 'dart:async'; +import 'package:eatvio_app/app_router.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import '/core/app_export.dart'; @@ -10,6 +11,7 @@ import 'dart:convert'; import 'package:hive/hive.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:go_router/go_router.dart'; part 'diet_select_event.dart'; part 'diet_select_state.dart'; @@ -28,9 +30,7 @@ class DietSelectBloc extends Bloc { final box = await Hive.openBox('token'); final token = box.get('token'); if (token == null) { - NavigatorService.popAndPushNamed( - AppRoutes.authScreen, - ); // Если токен есть, запускаем приложение + AppRouter.navigatorKey.currentContext?.goNamed('authScreen'); } final response = await http.get( Uri.parse(dotenv.env['SERVER_ROOT']! + '/diets/index'), diff --git a/lib/presentation/diets/diet_select_screen/diet_select_screen.dart b/lib/presentation/diets/diet_select_screen/diet_select_screen.dart index 38670d3..68834d0 100644 --- a/lib/presentation/diets/diet_select_screen/diet_select_screen.dart +++ b/lib/presentation/diets/diet_select_screen/diet_select_screen.dart @@ -1,10 +1,11 @@ +import 'package:go_router/go_router.dart'; + import 'bloc/diet_select_bloc.dart'; import 'models/diet_select_model.dart'; import 'package:eatvio_app/core/app_export.dart'; import 'package:eatvio_app/widgets/app_bar/appbar_subtitle.dart'; import 'package:eatvio_app/widgets/app_bar/appbar_leading_image.dart'; import 'package:eatvio_app/widgets/app_bar/custom_app_bar.dart'; -import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter/material.dart'; @@ -12,10 +13,7 @@ import 'package:flutter/material.dart'; class DietSelectScreen extends StatelessWidget { static Widget builder(BuildContext context) { return BlocProvider( - create: (context) => DietSelectBloc( - DietSelectState(myProfileModelObj: DietSelectModel())) - ..add(DietSelectInitialEvent()), - child: DietSelectScreen()); + create: (context) => DietSelectBloc(DietSelectState(myProfileModelObj: DietSelectModel()))..add(DietSelectInitialEvent()), child: DietSelectScreen()); } Widget build(BuildContext context) { @@ -23,92 +21,93 @@ class DietSelectScreen extends StatelessWidget { builder: (context, state) { if (state is DietSelectInitialState) { // Это начальное состояние, данные ещё не загружены. - return CircularProgressIndicator( - color: Colors.white, - backgroundColor: Colors.white, + return Scaffold( + body: Center( + child: CircularProgressIndicator(), + ), ); } else if (state is DietSelectLoadedState) { // Данные загружены, можно строить экран. return _buildSetingsScreen(context, state); } else { // Другие состояния, если они есть. - return Container(); // Пустой контейнер для необработанных состояний. + return Scaffold( + body: Center( + child: CircularProgressIndicator(), + ), + ); } }, ); } _buildSetingsScreen(BuildContext context, DietSelectLoadedState state) { - mediaQueryData = MediaQuery.of(context); - return Scaffold( - backgroundColor: Colors.grey[200], - resizeToAvoidBottomInset: true, - body: SafeArea( - child: SingleChildScrollView( - child: Container( - width: double.maxFinite, - decoration: BoxDecoration(color: Colors.grey[200]), - child: Padding( - padding: EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - SizedBox(height: 10), - Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: const BorderRadius.all(Radius.circular(12)), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 2, - blurRadius: 5, - offset: Offset(0, 3), - ), - ], - ), - child: CustomAppBar( - height: 54.v, - leadingWidth: 28.h, - leading: AppbarLeadingImage( - imagePath: ImageConstant.imgArrowLeft, - margin: EdgeInsets.only( - left: 16.h, top: 15.v, bottom: 15.v), - onTap: onTapBack(context), + return Scaffold( + appBar: AppBar( + toolbarHeight: 0, + automaticallyImplyLeading: false, + ), + backgroundColor: Colors.grey[200], + resizeToAvoidBottomInset: true, + body: SingleChildScrollView( + child: Container( + width: double.maxFinite, + decoration: BoxDecoration(color: Colors.grey[200]), + child: Padding( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SizedBox(height: 10), + Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(12)), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), ), - title: Padding( - padding: EdgeInsets.only(left: 20.h), - child: Row( - children: [ - AppbarSubtitle( - text: "Диеты", - margin: EdgeInsets.only( - left: 80.h, top: 12.v, bottom: 9.v), + ], + ), + child: CustomAppBar( + height: 54.v, + leadingWidth: 28.h, + leading: AppbarLeadingImage( + imagePath: ImageConstant.imgArrowLeft, + margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v), + onTap: () => GoRouter.of(context).pop(), + ), + title: Padding( + padding: EdgeInsets.only(left: 20.h), + child: Row( + children: [ + AppbarSubtitle( + text: "Диеты", + margin: EdgeInsets.only(left: 80.h, top: 12.v, bottom: 9.v), + ), + Spacer(), + Padding( + padding: const EdgeInsets.only(right: 20.0), + child: GestureDetector( + onTap: () => GoRouter.of(context).goNamed('dietCreateScreen'), + child: Icon(Icons.create_outlined), ), - Spacer(), - Padding( - padding: const EdgeInsets.only(right: 20.0), - child: GestureDetector( - onTap: () => Navigator.pushNamed( - context, - AppRoutes.dietCreateScreen, - ), - child: Icon(Icons.create_outlined)), - ) - ], - ), + ) + ], ), ), ), - SizedBox(height: 20), - _buildDietList(context, state) - ], - ), + ), + SizedBox(height: 20), + _buildDietList(context, state) + ], ), ), ), ), - bottomNavigationBar: _buildBottomBar(context), ); } @@ -151,18 +150,17 @@ class DietSelectScreen extends StatelessWidget { style: CustomTextStyles.titleMediumSemiBold, ), subtitle: Text(post?['details'] ?? ''), - onTap: () => Navigator.pushNamed( - context, - AppRoutes.dietScreen, - arguments: post?['id'].toString(), + onTap: () => GoRouter.of(context).goNamed( + 'dietScreen', + extra: post?['id'].toString(), ), trailing: GestureDetector( - onTap: () => Navigator.pushNamed( - context, - AppRoutes.dietEditScreen, - arguments: post?['id'].toString(), - ), - child: Icon(Icons.edit)), + onTap: () => GoRouter.of(context).goNamed( + 'dietEditScreen', + extra: post?['id'].toString(), + ), + child: Icon(Icons.edit), + ), )); }, ); @@ -174,11 +172,7 @@ class DietSelectScreen extends StatelessWidget { }; } - _buildBottomBar(BuildContext context) { - return CustomBottomBar(onChanged: (BottomBarEnum type) {}); - } - onTapArrowLeft(BuildContext context) { - NavigatorService.goBack(); + GoRouter.of(context).pop(); } } diff --git a/lib/presentation/diets/diet_show_screen/bloc/diet_show_bloc.dart b/lib/presentation/diets/diet_show_screen/bloc/diet_show_bloc.dart index a1216bd..5fcd3a1 100644 --- a/lib/presentation/diets/diet_show_screen/bloc/diet_show_bloc.dart +++ b/lib/presentation/diets/diet_show_screen/bloc/diet_show_bloc.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:eatvio_app/app_router.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; @@ -8,7 +9,10 @@ import 'package:http/http.dart' as http; import 'dart:convert'; import '/core/app_export.dart'; import 'package:eatvio_app/presentation/diets/diet_show_screen/models/diet_show_model.dart'; +import 'package:go_router/go_router.dart'; + part 'diet_show_event.dart'; + part 'diet_show_state.dart'; /// A bloc that manages the state of a Diet according to the event that is dispatched to it. @@ -26,9 +30,7 @@ class DietBloc extends Bloc { final box = await Hive.openBox('token'); final token = box.get('token'); if (token == null) { - NavigatorService.popAndPushNamed( - AppRoutes.authScreen, - ); + AppRouter.navigatorKey.currentContext?.goNamed('authScreen'); } final String? dietID = state.dietModelObj?.dietID; final response = await http.get( @@ -42,9 +44,7 @@ class DietBloc extends Bloc { if (data is Map) { if (state is DietLoadedState) { var currentState = state as DietLoadedState; - emit(currentState.copyWith( - dietModelObj: currentState.dietModelObj, - diet: currentState.diet)); + emit(currentState.copyWith(dietModelObj: currentState.dietModelObj, diet: currentState.diet)); } else { emit(DietLoadedState(diet: data)); } @@ -62,14 +62,11 @@ class DietBloc extends Bloc { } } - FutureOr _deleteEntry( - DeleteEntry event, Emitter emit) async { + FutureOr _deleteEntry(DeleteEntry event, Emitter emit) async { final box = await Hive.openBox('token'); final token = box.get('token'); if (token == null) { - NavigatorService.popAndPushNamed( - AppRoutes.authScreen, - ); + AppRouter.navigatorKey.currentContext?.goNamed('authScreen'); } final int id = event.id; final response = await http.post( @@ -79,7 +76,7 @@ class DietBloc extends Bloc { }, ); if (response.statusCode == 200) { - NavigatorService.pushNamed(AppRoutes.dietSelectScreen); + AppRouter.navigatorKey.currentContext?.goNamed('dietSelectScreen'); } else { // Статус ответа не равен 200, обрабатываем ошибку emit(DietErrorState('Ошибка: ${response.statusCode}')); diff --git a/lib/presentation/diets/diet_show_screen/diet_show_screen.dart b/lib/presentation/diets/diet_show_screen/diet_show_screen.dart index 5922a35..0c06f5d 100644 --- a/lib/presentation/diets/diet_show_screen/diet_show_screen.dart +++ b/lib/presentation/diets/diet_show_screen/diet_show_screen.dart @@ -1,20 +1,19 @@ +import 'package:go_router/go_router.dart'; + import 'bloc/diet_show_bloc.dart'; import 'models/diet_show_model.dart'; import 'package:eatvio_app/core/app_export.dart'; import 'package:flutter/material.dart'; -import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; class DietScreen extends StatelessWidget { const DietScreen({Key? key}) : super(key: key); + static Widget builder(BuildContext context) { final dietID = ModalRoute.of(context)!.settings.arguments; return BlocProvider( - create: (context) => DietBloc( - DietState(dietModelObj: DietModel(dietID: dietID.toString()))) - ..add(DietInitialEvent()), - child: DietScreen()); + create: (context) => DietBloc(DietState(dietModelObj: DietModel(dietID: dietID.toString())))..add(DietInitialEvent()), child: DietScreen()); } Widget build(BuildContext context) { @@ -41,93 +40,78 @@ class DietScreen extends StatelessWidget { _buildDiet(BuildContext context, DietLoadedState state) { mediaQueryData = MediaQuery.of(context); - return Scaffold( - resizeToAvoidBottomInset: false, - body: SafeArea( - child: Container( - width: double.maxFinite, - decoration: AppDecoration.fillGray, - child: ListView( - children: [ - SizedBox(height: 30.v), - Container( - margin: EdgeInsets.only( - left: 16.h, - right: 20.h, - bottom: 10.h, + return SafeArea( + child: Scaffold( + resizeToAvoidBottomInset: false, + body: Container( + width: double.maxFinite, + decoration: AppDecoration.fillGray, + child: ListView( + children: [ + SizedBox(height: 30.v), + Container( + margin: EdgeInsets.only( + left: 16.h, + right: 20.h, + bottom: 10.h, + ), + padding: EdgeInsets.all(12.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(12)), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), ), - padding: EdgeInsets.all(12.h), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: const BorderRadius.all(Radius.circular(12)), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 2, - blurRadius: 5, - offset: Offset(0, 3), - ), - ], + ], + ), + child: Column(mainAxisSize: MainAxisSize.min, children: [ + CachedNetworkImage( + imageUrl: dotenv.env['STORAGE_URL']! + (state.diet?['image'] ?? ''), + imageBuilder: (context, imageProvider) => CircleAvatar( + backgroundImage: imageProvider, + radius: 25, ), - child: Column(mainAxisSize: MainAxisSize.min, children: [ - CachedNetworkImage( - imageUrl: dotenv.env['STORAGE_URL']! + - (state.diet?['image'] ?? ''), - imageBuilder: (context, imageProvider) => CircleAvatar( - backgroundImage: imageProvider, - radius: 25, - ), - placeholder: (context, url) => - CircularProgressIndicator(), - errorWidget: (context, url, error) => CircleAvatar( - child: Icon(Icons.person), - backgroundColor: Colors.white, - radius: 25, + placeholder: (context, url) => CircularProgressIndicator(), + errorWidget: (context, url, error) => CircleAvatar( + child: Icon(Icons.person), + backgroundColor: Colors.white, + radius: 25, + ), + ), + SizedBox(height: 16.0), + Text(state.diet?['name'], style: CustomTextStyles.titleMediumBlack), + SizedBox(height: 8.0), + Text( + state.diet?['details'], + ), + SizedBox(height: 16.0), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + ElevatedButton( + onPressed: () => GoRouter.of(context).goNamed( + 'dietEditScreen', + extra: state.diet?['id'].toString(), ), + child: Text('Редактировать'), ), - SizedBox(height: 16.0), - Text(state.diet?['name'], - style: CustomTextStyles.titleMediumBlack), - SizedBox(height: 8.0), - Text( - state.diet?['details'], + ElevatedButton( + onPressed: () => context.read().add(DeleteEntry(state.diet?['id'])), + child: Text('Удалить'), ), - SizedBox(height: 16.0), - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - ElevatedButton( - onPressed: () => Navigator.pushNamed( - context, - AppRoutes.dietEditScreen, - arguments: state.diet?['id'].toString(), - ), - child: Text('Редактировать'), - ), - ElevatedButton( - onPressed: () => context - .read() - .add(DeleteEntry(state.diet?['id'])), - child: Text('Удалить'), - ), - ], - ) - ]), + ], ) - ], - ), - ), - ), bottomNavigationBar: _buildBottomBar(context)); - } - - ///Handling route based on bottom click actions - String getCurrentRoute(BottomBarEnum type) { - switch (type) { - case BottomBarEnum.tf: - return AppRoutes.dietScreen; - default: - return "/"; - } + ]), + ) + ], + ), + ), + ), + ); } ///Handling page based on route @@ -136,19 +120,15 @@ class DietScreen extends StatelessWidget { String currentRoute, ) { switch (currentRoute) { - case AppRoutes.dietScreen: + case '/diet': return DietScreen.builder(context); default: return DietScreen(); } } - _buildBottomBar(BuildContext context) { - return CustomBottomBar(onChanged: (BottomBarEnum type) {}); - } - /// Navigates to the previous screen. onTapArrowleftone(BuildContext context) { - NavigatorService.goBack(); + GoRouter.of(context).pop(); } } diff --git a/lib/presentation/feed/blog_post_create_screen/bloc/blog_post_create_bloc.dart b/lib/presentation/feed/blog_post_create_screen/bloc/blog_post_create_bloc.dart index e07d2dc..77f0f1a 100644 --- a/lib/presentation/feed/blog_post_create_screen/bloc/blog_post_create_bloc.dart +++ b/lib/presentation/feed/blog_post_create_screen/bloc/blog_post_create_bloc.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:eatvio_app/app_router.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; @@ -8,6 +9,7 @@ import 'package:http/http.dart' as http; import 'dart:convert'; import '/core/app_export.dart'; import 'package:eatvio_app/presentation/feed/blog_post_create_screen/models/blog_post_create_model.dart'; +import 'package:go_router/go_router.dart'; part 'blog_post_create_event.dart'; part 'blog_post_create_state.dart'; @@ -26,9 +28,7 @@ class BlogPostCreateBloc final box = await Hive.openBox('token'); final token = box.get('token'); if (token == null) { - NavigatorService.popAndPushNamed( - AppRoutes.authScreen, - ); + AppRouter.navigatorKey.currentContext?.goNamed('authScreen'); } if (state is BlogPostCreateLoadedState) { var currentState = state as BlogPostCreateLoadedState; @@ -59,7 +59,7 @@ class BlogPostCreateBloc ); if (response.statusCode == 200) { - NavigatorService.pushNamed(AppRoutes.feedScreen); + AppRouter.navigatorKey.currentContext?.goNamed('feedScreen'); } else { print( 'Failed to create blog post. Status Code: ${response.statusCode} ${response.body}'); diff --git a/lib/presentation/feed/blog_post_create_screen/blog_post_create_screen.dart b/lib/presentation/feed/blog_post_create_screen/blog_post_create_screen.dart index fc1d7c1..be78f7e 100644 --- a/lib/presentation/feed/blog_post_create_screen/blog_post_create_screen.dart +++ b/lib/presentation/feed/blog_post_create_screen/blog_post_create_screen.dart @@ -2,18 +2,16 @@ import 'bloc/blog_post_create_bloc.dart'; import 'models/blog_post_create_model.dart'; import 'package:eatvio_app/core/app_export.dart'; import 'package:flutter/material.dart'; -import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; import 'package:eatvio_app/widgets/custom_text_form_field.dart'; import 'package:eatvio_app/widgets/custom_outlined_button.dart'; class BlogPostCreateScreen extends StatelessWidget { const BlogPostCreateScreen({Key? key}) : super(key: key); + static Widget builder(BuildContext context) { final blog_postID = ModalRoute.of(context)!.settings.arguments; return BlocProvider( - create: (context) => BlogPostCreateBloc(BlogPostCreateState( - blog_postModelObj: - BlogPostCreateModel(blog_postID: blog_postID.toString()))) + create: (context) => BlogPostCreateBloc(BlogPostCreateState(blog_postModelObj: BlogPostCreateModel(blog_postID: blog_postID.toString()))) ..add(BlogPostCreateInitialEvent()), child: BlogPostCreateScreen()); } @@ -41,92 +39,85 @@ class BlogPostCreateScreen extends StatelessWidget { } _buildBlogPostCreate(BuildContext context, BlogPostCreateLoadedState state) { - mediaQueryData = MediaQuery.of(context); - return SafeArea( - child: Scaffold( - resizeToAvoidBottomInset: false, - body: Container( - width: double.maxFinite, - decoration: AppDecoration.fillGray, - child: ListView(children: [ - SizedBox(height: 30.v), - Container( - margin: EdgeInsets.only( - left: 16.h, - right: 20.h, - bottom: 10.h, + return Scaffold( + resizeToAvoidBottomInset: false, + body: Container( + width: double.maxFinite, + decoration: AppDecoration.fillGray, + child: ListView( + children: [ + SizedBox(height: 30.v), + Container( + margin: EdgeInsets.only( + left: 16.h, + right: 20.h, + bottom: 10.h, + ), + padding: EdgeInsets.all(12.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(12)), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), + ), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Column( + children: [ + Text("Заголовок новости", style: theme.textTheme.bodyLarge), + CustomTextFormField( + controller: state.title, + validator: _validateTitle, ), - padding: EdgeInsets.all(12.h), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: - const BorderRadius.all(Radius.circular(12)), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 2, - blurRadius: 5, - offset: Offset(0, 3), - ), - ], + SizedBox(height: 8.0), + Text("Ссылка на YouTube", style: theme.textTheme.bodyLarge), + CustomTextFormField( + controller: state.youtube_url, + validator: _validateYouTubeLink, ), - child: Column(mainAxisSize: MainAxisSize.min, children: [ - Column( - children: [ - Text("Заголовок новости", - style: theme.textTheme.bodyLarge), - CustomTextFormField( - controller: state.title, - validator: _validateTitle, - ), - SizedBox(height: 8.0), - Text("Ссылка на YouTube", - style: theme.textTheme.bodyLarge), - CustomTextFormField( - controller: state.youtube_url, - validator: _validateYouTubeLink, - ), - SizedBox(height: 8.0), - Text("Описание", style: theme.textTheme.bodyLarge), - CustomTextFormField( - controller: state.description, - validator: _validateDescription, - maxLines: 6, - ), - SizedBox(height: 16.0), - // Add widget for image upload here - CustomOutlinedButton( - onPressed: () => _submitForm( - context, - state.title!.text, - state.description!.text, - state.youtube_url!.text), - buttonStyle: - CustomButtonStyles.outlinePrimaryTL15, - text: 'Создать', - ), - ], - ), - ])) - ])), - bottomNavigationBar: _buildBottomBar(context))); + SizedBox(height: 8.0), + Text("Описание", style: theme.textTheme.bodyLarge), + CustomTextFormField( + controller: state.description, + validator: _validateDescription, + maxLines: 6, + ), + SizedBox(height: 16.0), + // Add widget for image upload here + CustomOutlinedButton( + onPressed: () => _submitForm(context, state.title!.text, state.description!.text, state.youtube_url!.text), + buttonStyle: CustomButtonStyles.outlinePrimaryTL15, + text: 'Создать', + ), + ], + ), + ], + ), + ) + ], + ), + ), + ); } } -_submitForm( - BuildContext context, String title, String description, String youtubeURL) { +_submitForm(BuildContext context, String title, String description, String youtubeURL) { String? titleError = _validateTitle(title); String? descriptionError = _validateDescription(description); String? youtubeURLError = _validateYouTubeLink(youtubeURL); - if (titleError != null || - descriptionError != null || - youtubeURLError != null) { + if (titleError != null || descriptionError != null || youtubeURLError != null) { // Display error messages or handle invalid input as needed // For example, show a snackbar with the error message ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(titleError ?? descriptionError ?? youtubeURLError!)), + SnackBar(content: Text(titleError ?? descriptionError ?? youtubeURLError!)), ); } else { // Proceed with form submission @@ -158,11 +149,6 @@ String? _validateYouTubeLink(value) { } bool _isValidUrl(String url) { - final RegExp urlRegExp = - new RegExp(r"^(http|https):\/\/([\w-]+\.)+[\w-]+(\/[\w-./?%&=]*)?$"); + final RegExp urlRegExp = new RegExp(r"^(http|https):\/\/([\w-]+\.)+[\w-]+(\/[\w-./?%&=]*)?$"); return urlRegExp.hasMatch(url); } - -_buildBottomBar(BuildContext context) { - return CustomBottomBar(onChanged: (BottomBarEnum type) {}); -} diff --git a/lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_bloc.dart b/lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_bloc.dart index f3b193e..b3a3027 100644 --- a/lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_bloc.dart +++ b/lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_bloc.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:eatvio_app/app_router.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; @@ -8,7 +9,10 @@ import 'package:http/http.dart' as http; import 'dart:convert'; import '/core/app_export.dart'; import 'package:eatvio_app/presentation/feed/blog_post_edit_screen/models/blog_post_edit_model.dart'; +import 'package:go_router/go_router.dart'; + part 'blog_post_edit_event.dart'; + part 'blog_post_edit_state.dart'; /// A bloc that manages the state of a BlogPostEdit according to the event that is dispatched to it. @@ -26,9 +30,7 @@ class BlogPostEditBloc extends Bloc { final box = await Hive.openBox('token'); final token = box.get('token'); if (token == null) { - NavigatorService.popAndPushNamed( - AppRoutes.authScreen, - ); + AppRouter.navigatorKey.currentContext?.goNamed('authScreen'); } final String? blog_postID = state.blog_postModelObj?.blog_postID; final response = await http.get( @@ -57,8 +59,7 @@ class BlogPostEditBloc extends Bloc { } } else { // Один или несколько ключевых полей отсутствуют, обрабатываем ошибку - emit(BlogPostEditErrorState( - 'Некоторые необходимые данные отсутствуют.')); + emit(BlogPostEditErrorState('Некоторые необходимые данные отсутствуют.')); } } else { // Статус ответа не равен 200, обрабатываем ошибку @@ -70,8 +71,7 @@ class BlogPostEditBloc extends Bloc { } } - FutureOr _editBlogPost( - EditBlogPost event, Emitter emit) async { + FutureOr _editBlogPost(EditBlogPost event, Emitter emit) async { if (state is BlogPostEditLoadedState) { var currentState = state as BlogPostEditLoadedState; @@ -83,11 +83,9 @@ class BlogPostEditBloc extends Bloc { ); if (response.statusCode == 200) { - NavigatorService.pushNamed(AppRoutes.blogPostScreen, - arguments: currentState.blog_post?['id'].toString()); + AppRouter.navigatorKey.currentContext?.goNamed('blogPostScreen', extra: currentState.blog_post?['id'].toString()); } else { - print( - 'Failed to edit blog post. Status Code: ${response.statusCode} ${response.body}'); + print('Failed to edit blog post. Status Code: ${response.statusCode} ${response.body}'); } } } diff --git a/lib/presentation/feed/blog_post_edit_screen/blog_post_edit_screen.dart b/lib/presentation/feed/blog_post_edit_screen/blog_post_edit_screen.dart index 2098819..5c38ffb 100644 --- a/lib/presentation/feed/blog_post_edit_screen/blog_post_edit_screen.dart +++ b/lib/presentation/feed/blog_post_edit_screen/blog_post_edit_screen.dart @@ -2,19 +2,16 @@ import 'bloc/blog_post_edit_bloc.dart'; import 'models/blog_post_edit_model.dart'; import 'package:eatvio_app/core/app_export.dart'; import 'package:flutter/material.dart'; -import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; class BlogPostEditScreen extends StatelessWidget { const BlogPostEditScreen({Key? key}) : super(key: key); - static Widget builder(BuildContext context) { - final blog_postID = ModalRoute.of(context)!.settings.arguments; + + static Widget builder(BuildContext context, blog_postID) { return BlocProvider( - create: (context) => BlogPostEditBloc(BlogPostEditState( - blog_postModelObj: - BlogPostEditModel(blog_postID: blog_postID.toString()))) - ..add(BlogPostEditInitialEvent()), + create: (context) => + BlogPostEditBloc(BlogPostEditState(blog_postModelObj: BlogPostEditModel(blog_postID: blog_postID.toString())))..add(BlogPostEditInitialEvent()), child: BlogPostEditScreen()); } @@ -22,9 +19,10 @@ class BlogPostEditScreen extends StatelessWidget { return BlocBuilder( builder: (context, state) { if (state is BlogPostEditInitialState) { - // Это начальное состояние, данные ещё не загружены. - return Container( - color: Colors.white, + return Scaffold( + body: Center( + child: CircularProgressIndicator(), + ), ); } else if (state is BlogPostEditLoadedState) { // Данные загружены, можно строить экран. @@ -33,8 +31,11 @@ class BlogPostEditScreen extends StatelessWidget { // Произошла ошибка при загрузке данных. return Center(child: Text(state.errorMessage)); } else { - // Другие состояния, если они есть. - return Container(); // Пустой контейнер для необработанных состояний. + return Scaffold( + body: Center( + child: CircularProgressIndicator(), + ), + ); } }, ); @@ -43,77 +44,75 @@ class BlogPostEditScreen extends StatelessWidget { _buildBlogPostEdit(BuildContext context, BlogPostEditLoadedState state) { mediaQueryData = MediaQuery.of(context); return Scaffold( - resizeToAvoidBottomInset: false, - body: SafeArea( - child: Container( - width: double.maxFinite, - decoration: AppDecoration.fillGray, - child: ListView(children: [ - Container( - margin: EdgeInsets.only( - left: 16.h, - right: 20.h, - bottom: 10.h, + resizeToAvoidBottomInset: false, + body: Container( + width: double.maxFinite, + decoration: AppDecoration.fillGray, + child: ListView( + children: [ + SizedBox(height: 30.v), + Container( + margin: EdgeInsets.only( + left: 16.h, + right: 20.h, + bottom: 10.h, + ), + padding: EdgeInsets.all(12.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(12)), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), ), - padding: EdgeInsets.all(12.h), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: const BorderRadius.all(Radius.circular(12)), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 2, - blurRadius: 5, - offset: Offset(0, 3), - ), - ], + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + CachedNetworkImage( + imageUrl: dotenv.env['STORAGE_URL']! + (state.blog_post?['image'] ?? ''), + imageBuilder: (context, imageProvider) => CircleAvatar( + backgroundImage: imageProvider, + radius: 25, + ), + placeholder: (context, url) => CircularProgressIndicator(), + errorWidget: (context, url, error) => CircleAvatar( + child: Icon(Icons.person), + backgroundColor: Colors.white, + radius: 25, + ), ), - child: Column(mainAxisSize: MainAxisSize.min, children: [ - CachedNetworkImage( - imageUrl: dotenv.env['STORAGE_URL']! + - (state.blog_post?['image'] ?? ''), - imageBuilder: (context, imageProvider) => CircleAvatar( - backgroundImage: imageProvider, - radius: 25, - ), - placeholder: (context, url) => - CircularProgressIndicator(), - errorWidget: (context, url, error) => CircleAvatar( - child: Icon(Icons.person), - backgroundColor: Colors.white, - radius: 25, - ), - ), - SizedBox(height: 16.0), - TextFormField( - controller: state.title, - decoration: InputDecoration(labelText: 'Заголовок'), - ), - SizedBox(height: 16.0), - TextFormField( - controller: state.description, - decoration: InputDecoration(labelText: 'Описание'), - ), - SizedBox(height: 16.0), - TextFormField( - controller: state.youtube_url, - decoration: InputDecoration(labelText: 'Ссылка'), - ), - SizedBox(height: 16.0), - // Здесь нужно добавить виджет для загрузки картинки - ElevatedButton( - onPressed: () => - context.read().add(EditBlogPost()), - child: Text('Сохранить'), - ) - ]), - ) - ]), - ), - ), bottomNavigationBar: _buildBottomBar(context)); + SizedBox(height: 16.0), + TextFormField( + controller: state.title, + decoration: InputDecoration(labelText: 'Заголовок'), + ), + SizedBox(height: 16.0), + TextFormField( + controller: state.description, + decoration: InputDecoration(labelText: 'Описание'), + ), + SizedBox(height: 16.0), + TextFormField( + controller: state.youtube_url, + decoration: InputDecoration(labelText: 'Ссылка'), + ), + SizedBox(height: 16.0), + // Здесь нужно добавить виджет для загрузки картинки + ElevatedButton( + onPressed: () => context.read().add(EditBlogPost()), + child: Text('Сохранить'), + ) + ], + ), + ) + ], + ), + ), + ); } } - -_buildBottomBar(BuildContext context) { - return CustomBottomBar(onChanged: (BottomBarEnum type) {}); -} diff --git a/lib/presentation/feed/blog_post_screen/bloc/blog_post_bloc.dart b/lib/presentation/feed/blog_post_screen/bloc/blog_post_bloc.dart index d065533..9f5f11f 100644 --- a/lib/presentation/feed/blog_post_screen/bloc/blog_post_bloc.dart +++ b/lib/presentation/feed/blog_post_screen/bloc/blog_post_bloc.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:eatvio_app/app_router.dart'; import 'package:eatvio_app/presentation/feed/blog_post_screen/models/comment_model.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; @@ -10,6 +11,7 @@ import 'package:intl/intl.dart'; import 'dart:convert'; import '/core/app_export.dart'; import 'package:eatvio_app/presentation/feed/blog_post_screen/models/blog_post_model.dart'; +import 'package:go_router/go_router.dart'; part 'blog_post_event.dart'; part 'blog_post_state.dart'; @@ -29,9 +31,7 @@ class BlogPostBloc extends Bloc { final box = await Hive.openBox('token'); final token = box.get('token'); if (token == null) { - NavigatorService.popAndPushNamed( - AppRoutes.authScreen, - ); + AppRouter.navigatorKey.currentContext?.goNamed('authScreen'); } final String? blog_postID = state.blog_postModelObj?.blog_postID; final response = await http.get( @@ -98,9 +98,7 @@ class BlogPostBloc extends Bloc { final box = await Hive.openBox('token'); final token = box.get('token'); if (token == null) { - NavigatorService.popAndPushNamed( - AppRoutes.authScreen, - ); + AppRouter.navigatorKey.currentContext?.goNamed('authScreen'); } final int id = event.id; final response = await http.post( @@ -110,7 +108,7 @@ class BlogPostBloc extends Bloc { }, ); if (response.statusCode == 200) { - NavigatorService.pushNamed(AppRoutes.feedScreen); + AppRouter.navigatorKey.currentContext?.goNamed('feedScreen'); } else { // Статус ответа не равен 200, обрабатываем ошибку emit(BlogPostErrorState('Ошибка: ${response.statusCode}')); @@ -122,9 +120,7 @@ class BlogPostBloc extends Bloc { final token = box.get('token'); if (token == null) { - NavigatorService.popAndPushNamed( - AppRoutes.authScreen, - ); + AppRouter.navigatorKey.currentContext?.goNamed('authScreen'); } final response = await http.get( @@ -141,9 +137,7 @@ class BlogPostBloc extends Bloc { final box = await Hive.openBox('token'); final token = box.get('token'); if (token == null) { - NavigatorService.popAndPushNamed( - AppRoutes.authScreen, - ); + AppRouter.navigatorKey.currentContext?.goNamed('authScreen'); } final response = await http.get( Uri.parse(dotenv.env['SERVER_ROOT']! + '/feed/getComments/$id'), diff --git a/lib/presentation/feed/blog_post_screen/blog_post_screen.dart b/lib/presentation/feed/blog_post_screen/blog_post_screen.dart index c974e81..37a2ce2 100644 --- a/lib/presentation/feed/blog_post_screen/blog_post_screen.dart +++ b/lib/presentation/feed/blog_post_screen/blog_post_screen.dart @@ -1,10 +1,11 @@ +import 'package:go_router/go_router.dart'; + import 'bloc/blog_post_bloc.dart'; import 'models/blog_post_model.dart'; import 'widgets/comment_widget.dart'; import 'package:eatvio_app/core/app_export.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; -import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; import 'package:eatvio_app/widgets/custom_edit_text_form_field.dart'; import 'package:eatvio_app/widgets/custom_outlined_button.dart'; import 'package:cached_network_image/cached_network_image.dart'; @@ -12,59 +13,73 @@ import 'package:flutter_dotenv/flutter_dotenv.dart'; class BlogPostScreen extends StatelessWidget { const BlogPostScreen({Key? key}) : super(key: key); - static Widget builder(BuildContext context) { - final blog_postID = ModalRoute.of(context)!.settings.arguments; + + static Widget builder(BuildContext context, String blog_postID) { return BlocProvider( - create: (context) => BlogPostBloc(BlogPostState( - blog_postModelObj: - BlogPostModel(blog_postID: blog_postID.toString()))) - ..add(BlogPostInitialEvent()), - child: BlogPostScreen()); + create: (context) => BlogPostBloc( + BlogPostState( + blog_postModelObj: BlogPostModel( + blog_postID: blog_postID.toString(), + ), + ), + )..add( + BlogPostInitialEvent(), + ), + child: BlogPostScreen(), + ); } Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { if (state is BlogPostInitialState) { - // Это начальное состояние, данные ещё не загружены. - return Container( - color: Colors.white, + return Scaffold( + body: Center( + child: CircularProgressIndicator(), + ), ); } else if (state is BlogPostLoadedState) { // Данные загружены, можно строить экран. return _buildBlogPost(context, state); } else if (state is BlogPostErrorState) { // Произошла ошибка при загрузке данных. - return Center(child: Text(state.errorMessage)); + return Center( + child: Text(state.errorMessage), + ); } else { + return Scaffold( + body: Center( + child: CircularProgressIndicator(), + ), + ); // Другие состояния, если они есть. - return Container(); // Пустой контейнер для необработанных состояний. } }, ); } _buildBlogPost(BuildContext context, BlogPostLoadedState state) { - mediaQueryData = MediaQuery.of(context); - return Scaffold( - resizeToAvoidBottomInset: false, - body: SafeArea( - child: Container( - width: double.maxFinite, - decoration: AppDecoration.fillGray, - child: ListView( - children: [ - _buildHeaderRow(context, state), - SizedBox(height: 15.v), - _buildBlogPostData(context, state), - SizedBox(height: 15.v), - _buildTextField(context, state), - SizedBox(height: 15.v), - _buildCommentSection(context, state), - ], - ), - ), - ),bottomNavigationBar: _buildBottomBar(context)); + return SafeArea( + child: Scaffold( + resizeToAvoidBottomInset: false, + body: Container( + width: double.maxFinite, + decoration: AppDecoration.fillGray, + child: ListView( + children: [ + SizedBox(height: 15.v), + _buildHeaderRow(context, state), + SizedBox(height: 15.v), + _buildBlogPostData(context, state), + SizedBox(height: 15.v), + _buildTextField(context, state), + SizedBox(height: 15.v), + _buildCommentSection(context, state), + ], + ), + ), + ), + ); } _buildHeaderRow(BuildContext context, BlogPostLoadedState state) { @@ -91,8 +106,9 @@ class BlogPostScreen extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ CustomImageView( - imagePath: ImageConstant.imgArrowLeft, - onTap: () => NavigatorService.goBack()), + imagePath: ImageConstant.imgArrowLeft, + onTap: () => GoRouter.of(context).pop(), + ), Padding( padding: EdgeInsets.only(top: 1.v), child: Row( @@ -105,8 +121,7 @@ class BlogPostScreen extends StatelessWidget { ), ), CachedNetworkImage( - imageUrl: dotenv.env['STORAGE_URL']! + - (state.author?['profile_photo_path'] ?? ''), + imageUrl: dotenv.env['STORAGE_URL']! + (state.author?['profile_photo_path'] ?? ''), imageBuilder: (context, imageProvider) => CircleAvatar( backgroundImage: imageProvider, radius: 25, @@ -180,8 +195,7 @@ class BlogPostScreen extends StatelessWidget { Align( alignment: Alignment.topLeft, child: Text( - DateFormat('dd.MM.yyyy') - .format(DateTime.parse(state.blog_post?['updated_at'])), + DateFormat('dd.MM.yyyy').format(DateTime.parse(state.blog_post?['updated_at'])), style: theme.textTheme.bodyMedium, )), SizedBox(height: 16.0), @@ -294,10 +308,9 @@ class BlogPostScreen extends StatelessWidget { void tapMenu(String value, BuildContext context, BlogPostLoadedState state) { if (value == 'edit') { - Navigator.pushNamed( - context, - AppRoutes.blogPostEditScreen, - arguments: state.blog_post?['id'].toString(), + GoRouter.of(context).goNamed( + 'blogPostEditScreen', + extra: state.blog_post?['id'].toString(), ); } else if (value == 'delete') { context.read().add(DeleteEntry(state.blog_post?['id'])); @@ -341,9 +354,7 @@ class BlogPostScreen extends StatelessWidget { decoration: BoxDecoration( color: Colors.transparent, ), - onPressed: () => context - .read() - .add(CreateComment(state.commentController!.text)), + onPressed: () => context.read().add(CreateComment(state.commentController!.text)), // Установите прозрачный фон ) ], @@ -376,12 +387,8 @@ class BlogPostScreen extends StatelessWidget { } } - _buildBottomBar(BuildContext context) { - return CustomBottomBar(onChanged: (BottomBarEnum type) {}); - } - /// Navigates to the previous screen. onTapArrowleftone(BuildContext context) { - NavigatorService.goBack(); + GoRouter.of(context).pop(); } } diff --git a/lib/presentation/feed/feed_screen/bloc/feed_bloc.dart b/lib/presentation/feed/feed_screen/bloc/feed_bloc.dart index 3932f49..1eda1fe 100644 --- a/lib/presentation/feed/feed_screen/bloc/feed_bloc.dart +++ b/lib/presentation/feed/feed_screen/bloc/feed_bloc.dart @@ -1,6 +1,7 @@ //import 'package:eatvio_app/presentation/auth/auth_screen/auth_screen.dart'; import 'dart:async'; +import 'package:eatvio_app/app_router.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import '/core/app_export.dart'; @@ -10,6 +11,7 @@ import 'dart:convert'; import 'package:hive/hive.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:go_router/go_router.dart'; part 'feed_event.dart'; part 'feed_state.dart'; @@ -28,9 +30,7 @@ class FeedBloc extends Bloc { final box = await Hive.openBox('token'); final token = box.get('token'); if (token == null) { - NavigatorService.popAndPushNamed( - AppRoutes.authScreen, - ); // Если токен есть, запускаем приложение + AppRouter.navigatorKey.currentContext?.goNamed('authScreen'); } final response = await http.get( Uri.parse(dotenv.env['SERVER_ROOT']! + '/feed/index'), diff --git a/lib/presentation/feed/feed_screen/feed_screen.dart b/lib/presentation/feed/feed_screen/feed_screen.dart index d5d2500..1af8a84 100644 --- a/lib/presentation/feed/feed_screen/feed_screen.dart +++ b/lib/presentation/feed/feed_screen/feed_screen.dart @@ -1,9 +1,10 @@ +import 'package:go_router/go_router.dart'; + import 'bloc/feed_bloc.dart'; import 'models/feed_model.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:eatvio_app/presentation/feed/feed_screen/widgets/feed_search_view.dart'; import 'package:eatvio_app/presentation/feed/feed_screen/widgets/feed_scroll_item.dart'; -import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; import 'package:eatvio_app/widgets/custom_outlined_button.dart'; import 'package:eatvio_app/core/app_export.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; @@ -12,10 +13,7 @@ import 'package:intl/intl.dart'; class FeedScreen extends StatelessWidget { static Widget builder(BuildContext context) { - return BlocProvider( - create: (context) => FeedBloc(FeedState(myProfileModelObj: FeedModel())) - ..add(FeedInitialEvent()), - child: FeedScreen()); + return BlocProvider(create: (context) => FeedBloc(FeedState(myProfileModelObj: FeedModel()))..add(FeedInitialEvent()), child: FeedScreen()); } Widget build(BuildContext context) { @@ -64,7 +62,6 @@ class FeedScreen extends StatelessWidget { ), ), ), - bottomNavigationBar: _buildBottomBar(context), ), ); } @@ -106,10 +103,7 @@ class FeedScreen extends StatelessWidget { flex: 1, child: CustomOutlinedButton( text: "+", - onPressed: () => Navigator.pushNamed( - context, - AppRoutes.blogPostCreateScreen, - ), + onPressed: () => GoRouter.of(context).goNamed('blogPostCreateScreen'), buttonStyle: CustomButtonStyles.outlinePrimaryTL15, ), ), @@ -154,8 +148,7 @@ class FeedScreen extends StatelessWidget { child: Column(children: [ ListTile( leading: CachedNetworkImage( - imageUrl: dotenv.env['STORAGE_URL']! + - (post["author"]['profile_photo_path'] ?? ''), + imageUrl: dotenv.env['STORAGE_URL']! + (post["author"]['profile_photo_path'] ?? ''), imageBuilder: (context, imageProvider) => CircleAvatar( backgroundImage: imageProvider, radius: 25, @@ -172,14 +165,12 @@ class FeedScreen extends StatelessWidget { style: CustomTextStyles.titleMediumSemiBold, ), subtitle: Text( - DateFormat('dd.MM.yyyy') - .format(DateTime.parse(post?['post']['updated_at'])), + DateFormat('dd.MM.yyyy').format(DateTime.parse(post?['post']['updated_at'])), style: theme.textTheme.bodyMedium, ), - onTap: () => Navigator.pushNamed( - context, - AppRoutes.blogPostScreen, - arguments: post?['post']['id'].toString(), + onTap: () => GoRouter.of(context).goNamed( + 'blogPostScreen', + extra: post?['post']['id'].toString(), ), trailing: PopupMenuButton( onSelected: (value) => print("tap"), @@ -220,13 +211,11 @@ class FeedScreen extends StatelessWidget { ), SizedBox(height: 16.0), CachedNetworkImage( - imageUrl: dotenv.env['STORAGE_URL']! + - (post?['post']['image'] ?? ''), + imageUrl: dotenv.env['STORAGE_URL']! + (post?['post']['image'] ?? ''), imageBuilder: (context, imageProvider) => Stack( children: [ ClipRRect( - borderRadius: - const BorderRadius.all(Radius.circular(8.0)), + borderRadius: const BorderRadius.all(Radius.circular(8.0)), child: Image( image: imageProvider, ), @@ -277,12 +266,8 @@ class FeedScreen extends StatelessWidget { }; } - _buildBottomBar(BuildContext context) { - return CustomBottomBar(onChanged: (BottomBarEnum type) {}); - } - onTapArrowLeft(BuildContext context) { - NavigatorService.goBack(); + GoRouter.of(context).pop(); } _buildMenu(BuildContext context, FeedLoadedState state) { diff --git a/lib/presentation/glossary/glossary_screen/bloc/glossary_screen_bloc.dart b/lib/presentation/glossary/glossary_screen/bloc/glossary_screen_bloc.dart index d106526..5f63f29 100644 --- a/lib/presentation/glossary/glossary_screen/bloc/glossary_screen_bloc.dart +++ b/lib/presentation/glossary/glossary_screen/bloc/glossary_screen_bloc.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:eatvio_app/app_router.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; @@ -8,6 +9,7 @@ import 'package:http/http.dart' as http; import 'dart:convert'; import '/core/app_export.dart'; import 'package:eatvio_app/presentation/glossary/glossary_screen/models/glossary_screen_model.dart'; +import 'package:go_router/go_router.dart'; part 'glossary_screen_event.dart'; part 'glossary_screen_state.dart'; @@ -26,9 +28,7 @@ class GlossaryBloc extends Bloc { final box = await Hive.openBox('token'); final token = box.get('token'); if (token == null) { - NavigatorService.popAndPushNamed( - AppRoutes.authScreen, - ); + AppRouter.navigatorKey.currentContext?.goNamed('authScreen'); } final responseProductGroups = await http.get( Uri.parse(dotenv.env['SERVER_ROOT']! + '/glossary/getProductGroups'), diff --git a/lib/presentation/glossary/glossary_screen/glossary_screen.dart b/lib/presentation/glossary/glossary_screen/glossary_screen.dart index 65dc65c..ada1e86 100644 --- a/lib/presentation/glossary/glossary_screen/glossary_screen.dart +++ b/lib/presentation/glossary/glossary_screen/glossary_screen.dart @@ -4,7 +4,6 @@ import 'package:eatvio_app/core/app_export.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; -import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; import 'package:eatvio_app/widgets/custom_outlined_button.dart'; import 'package:eatvio_app/widgets/custom_drop_down.dart'; import 'package:eatvio_app/presentation/glossary/glossary_screen/widgets/glossary_search_widget.dart'; @@ -12,12 +11,10 @@ import 'package:eatvio_app/presentation/glossary/glossary_screen/widgets/glossar class GlossaryScreen extends StatelessWidget { const GlossaryScreen({Key? key}) : super(key: key); + static Widget builder(BuildContext context) { return BlocProvider( - create: (context) => - GlossaryBloc(GlossaryState(glossaryModelObj: GlossaryModel())) - ..add(GlossaryInitialEvent()), - child: GlossaryScreen()); + create: (context) => GlossaryBloc(GlossaryState(glossaryModelObj: GlossaryModel()))..add(GlossaryInitialEvent()), child: GlossaryScreen()); } Widget build(BuildContext context) { @@ -25,8 +22,10 @@ class GlossaryScreen extends StatelessWidget { builder: (context, state) { if (state is GlossaryInitialState) { // Это начальное состояние, данные ещё не загружены. - return Container( - color: Colors.white, + return Scaffold( + body: Center( + child: CircularProgressIndicator(), + ), ); } else if (state is GlossaryLoadedState) { // Данные загружены, можно строить экран. @@ -36,39 +35,43 @@ class GlossaryScreen extends StatelessWidget { return Center(child: Text(state.errorMessage)); } else { // Другие состояния, если они есть. - return Container(); // Пустой контейнер для необработанных состояний. + return Scaffold( + body: Center( + child: CircularProgressIndicator(), + ), + ); } }, ); } _buildGlossary(BuildContext context, GlossaryLoadedState state) { - mediaQueryData = MediaQuery.of(context); - return SafeArea( - child: Scaffold( - backgroundColor: Colors.grey[200], - resizeToAvoidBottomInset: true, - body: SingleChildScrollView( - child: Container( - width: double.maxFinite, - decoration: BoxDecoration(color: Colors.grey[200]), - child: Padding( - padding: EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - SizedBox(height: 20), - _buildSearch(context), - SizedBox(height: 20), - _buildMenu(context), - SizedBox(height: 20), - _buildProducts(context, state) - ], - ), + return Scaffold( + appBar: AppBar( + toolbarHeight: 0, + automaticallyImplyLeading: false, + ), + backgroundColor: Colors.grey[200], + resizeToAvoidBottomInset: true, + body: SingleChildScrollView( + child: Container( + width: double.maxFinite, + decoration: BoxDecoration(color: Colors.grey[200]), + child: Padding( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SizedBox(height: 20), + _buildSearch(context), + SizedBox(height: 20), + _buildMenu(context), + SizedBox(height: 20), + _buildProducts(context, state) + ], ), ), ), - bottomNavigationBar: _buildBottomBar(context), ), ); } @@ -184,16 +187,12 @@ class GlossaryScreen extends StatelessWidget { vertical: 9, ), onChanged: _changeProductGroup(context, state), - width: MediaQuery.of(context) - .size - .width, // Set a percentage value as per your layout + width: MediaQuery.of(context).size.width, // Set a percentage value as per your layout ), SizedBox( height: 8, ), - Align( - alignment: Alignment.centerLeft, - child: Text("*КБЖУ указано за 100гр")), + Align(alignment: Alignment.centerLeft, child: Text("*КБЖУ указано за 100гр")), SizedBox( height: 8, ), @@ -268,8 +267,7 @@ class GlossaryScreen extends StatelessWidget { child: Column(children: [ Text("Жиры - ${product['fats_on_100gr'].toString()}"), SizedBox(height: 8), - Text( - "Углеводы - ${product['carbohydrates_on_100gr'].toString()}") + Text("Углеводы - ${product['carbohydrates_on_100gr'].toString()}") ]), ) ]) @@ -298,33 +296,19 @@ class GlossaryScreen extends StatelessWidget { return result; } - ///Handling route based on bottom click actions - String getCurrentRoute(BottomBarEnum type) { - switch (type) { - case BottomBarEnum.tf: - return AppRoutes.glossaryScreen; - default: - return "/"; - } - } - ///Handling page based on route Widget getCurrentPage( BuildContext context, String currentRoute, ) { switch (currentRoute) { - case AppRoutes.glossaryScreen: + case '/glossary': return GlossaryScreen.builder(context); default: return GlossaryScreen(); } } - _buildBottomBar(BuildContext context) { - return CustomBottomBar(onChanged: (BottomBarEnum type) {}); - } - _changeProductGroup(BuildContext context, GlossaryLoadedState state) { return (value) { context.read().add(ChangeProductGroup(value.id)); diff --git a/lib/presentation/plans/plan_create_screen/bloc/plan_create_bloc.dart b/lib/presentation/plans/plan_create_screen/bloc/plan_create_bloc.dart index b06092c..51e08b9 100644 --- a/lib/presentation/plans/plan_create_screen/bloc/plan_create_bloc.dart +++ b/lib/presentation/plans/plan_create_screen/bloc/plan_create_bloc.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:eatvio_app/app_router.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; @@ -8,6 +9,7 @@ import 'package:http/http.dart' as http; import 'dart:convert'; import '/core/app_export.dart'; import 'package:eatvio_app/presentation/plans/plan_create_screen/models/plan_create_model.dart'; +import 'package:go_router/go_router.dart'; part 'plan_create_event.dart'; part 'plan_create_state.dart'; @@ -25,9 +27,7 @@ class PlanCreateBloc extends Bloc { final box = await Hive.openBox('token'); final token = box.get('token'); if (token == null) { - NavigatorService.popAndPushNamed( - AppRoutes.authScreen, - ); + AppRouter.navigatorKey.currentContext?.goNamed('authScreen'); } if (state is PlanCreateLoadedState) { var currentState = state as PlanCreateLoadedState; @@ -55,7 +55,7 @@ class PlanCreateBloc extends Bloc { ); if (response.statusCode == 200) { - NavigatorService.pushNamed(AppRoutes.planSelectScreen); + AppRouter.navigatorKey.currentContext?.goNamed('planSelectScreen'); } else { print( 'Failed to create blog post. Status Code: ${response.statusCode} ${response.body}'); diff --git a/lib/presentation/plans/plan_create_screen/plan_create_screen.dart b/lib/presentation/plans/plan_create_screen/plan_create_screen.dart index bc871c6..bd354af 100644 --- a/lib/presentation/plans/plan_create_screen/plan_create_screen.dart +++ b/lib/presentation/plans/plan_create_screen/plan_create_screen.dart @@ -2,16 +2,14 @@ import 'bloc/plan_create_bloc.dart'; import 'models/plan_create_model.dart'; import 'package:eatvio_app/core/app_export.dart'; import 'package:flutter/material.dart'; -import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; class PlanCreateScreen extends StatelessWidget { const PlanCreateScreen({Key? key}) : super(key: key); + static Widget builder(BuildContext context) { final planID = ModalRoute.of(context)!.settings.arguments; return BlocProvider( - create: (context) => PlanCreateBloc(PlanCreateState( - planModelObj: PlanCreateModel(planID: planID.toString()))) - ..add(PlanCreateInitialEvent()), + create: (context) => PlanCreateBloc(PlanCreateState(planModelObj: PlanCreateModel(planID: planID.toString())))..add(PlanCreateInitialEvent()), child: PlanCreateScreen()); } @@ -39,58 +37,57 @@ class PlanCreateScreen extends StatelessWidget { _buildPlanCreate(BuildContext context, PlanCreateLoadedState state) { mediaQueryData = MediaQuery.of(context); - return Scaffold( - resizeToAvoidBottomInset: false, - body: SafeArea( - child: Container( - width: double.maxFinite, - decoration: AppDecoration.fillGray, - child: ListView(children: [ - Container( - margin: EdgeInsets.only( - left: 16.h, - right: 20.h, - bottom: 10.h, - ), - padding: EdgeInsets.all(12.h), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: const BorderRadius.all(Radius.circular(12)), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 2, - blurRadius: 5, - offset: Offset(0, 3), - ), - ], - ), - child: Column(mainAxisSize: MainAxisSize.min, children: [ - SizedBox(height: 16.0), - TextFormField( - controller: state.name, - decoration: InputDecoration(labelText: 'Заголовок'), + return SafeArea( + child: Scaffold( + resizeToAvoidBottomInset: false, + body: Container( + width: double.maxFinite, + decoration: AppDecoration.fillGray, + child: ListView( + children: [ + SizedBox(height: 30.v), + Container( + margin: EdgeInsets.only( + left: 16.h, + right: 20.h, + bottom: 10.h, + ), + padding: EdgeInsets.all(12.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(12)), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), ), - SizedBox(height: 16.0), - TextFormField( - controller: state.details, - decoration: InputDecoration(labelText: 'Описание'), - ), - SizedBox(height: 16.0), - // Здесь нужно добавить виджет для загрузки картинки - ElevatedButton( - onPressed: () => - context.read().add(CreatePlan()), - child: Text('Создать'), - ) - ]), - ) - ]), - ), - ), bottomNavigationBar: _buildBottomBar(context)); + ], + ), + child: Column(mainAxisSize: MainAxisSize.min, children: [ + SizedBox(height: 16.0), + TextFormField( + controller: state.name, + decoration: InputDecoration(labelText: 'Заголовок'), + ), + SizedBox(height: 16.0), + TextFormField( + controller: state.details, + decoration: InputDecoration(labelText: 'Описание'), + ), + SizedBox(height: 16.0), + // Здесь нужно добавить виджет для загрузки картинки + ElevatedButton( + onPressed: () => context.read().add(CreatePlan()), + child: Text('Создать'), + ) + ]), + ) + ], + ), + ), + ), + ); } } - -_buildBottomBar(BuildContext context) { - return CustomBottomBar(onChanged: (BottomBarEnum type) {}); -} diff --git a/lib/presentation/plans/plan_edit_screen/bloc/plan_edit_bloc.dart b/lib/presentation/plans/plan_edit_screen/bloc/plan_edit_bloc.dart index e8fa9ea..1e7e4f2 100644 --- a/lib/presentation/plans/plan_edit_screen/bloc/plan_edit_bloc.dart +++ b/lib/presentation/plans/plan_edit_screen/bloc/plan_edit_bloc.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:eatvio_app/app_router.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; @@ -8,7 +9,10 @@ import 'package:http/http.dart' as http; import 'dart:convert'; import '/core/app_export.dart'; import 'package:eatvio_app/presentation/plans/plan_edit_screen/models/plan_edit_model.dart'; +import 'package:go_router/go_router.dart'; + part 'plan_edit_event.dart'; + part 'plan_edit_state.dart'; /// A bloc that manages the state of a PlanEdit according to the event that is dispatched to it. @@ -26,9 +30,7 @@ class PlanEditBloc extends Bloc { final box = await Hive.openBox('token'); final token = box.get('token'); if (token == null) { - NavigatorService.popAndPushNamed( - AppRoutes.authScreen, - ); + AppRouter.navigatorKey.currentContext?.goNamed('authScreen'); } final String? planID = state.planModelObj?.planID; final response = await http.get( @@ -53,8 +55,7 @@ class PlanEditBloc extends Bloc { plan: data, name: TextEditingController(text: data['name']), details: TextEditingController(text: data['details']), - duration: - TextEditingController(text: data['duration'].toString()))); + duration: TextEditingController(text: data['duration'].toString()))); } } else { // Один или несколько ключевых полей отсутствуют, обрабатываем ошибку @@ -75,15 +76,15 @@ class PlanEditBloc extends Bloc { var currentState = state as PlanEditLoadedState; int duration = int.parse(currentState.duration!.text) * 7; print(duration); - var response = await sendPlan(currentState.plan, currentState.name?.text, - currentState.details?.text, duration.toString()); + var response = await sendPlan(currentState.plan, currentState.name?.text, currentState.details?.text, duration.toString()); if (response.statusCode == 200) { - NavigatorService.pushNamed(AppRoutes.planScreen, - arguments: currentState.plan?['id'].toString()); + AppRouter.navigatorKey.currentContext?.goNamed( + 'planScreen', + extra: currentState.plan?['id'].toString(), + ); } else { - print( - 'Failed to edit blog post. Status Code: ${response.statusCode} ${response.body}'); + print('Failed to edit blog post. Status Code: ${response.statusCode} ${response.body}'); } } } diff --git a/lib/presentation/plans/plan_edit_screen/plan_edit_screen.dart b/lib/presentation/plans/plan_edit_screen/plan_edit_screen.dart index b4cef92..7baa33d 100644 --- a/lib/presentation/plans/plan_edit_screen/plan_edit_screen.dart +++ b/lib/presentation/plans/plan_edit_screen/plan_edit_screen.dart @@ -2,18 +2,16 @@ import 'bloc/plan_edit_bloc.dart'; import 'models/plan_edit_model.dart'; import 'package:eatvio_app/core/app_export.dart'; import 'package:flutter/material.dart'; -import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; class PlanEditScreen extends StatelessWidget { const PlanEditScreen({Key? key}) : super(key: key); + static Widget builder(BuildContext context) { final planID = ModalRoute.of(context)!.settings.arguments; return BlocProvider( - create: (context) => PlanEditBloc(PlanEditState( - planModelObj: PlanEditModel(planID: planID.toString()))) - ..add(PlanEditInitialEvent()), + create: (context) => PlanEditBloc(PlanEditState(planModelObj: PlanEditModel(planID: planID.toString())))..add(PlanEditInitialEvent()), child: PlanEditScreen()); } @@ -41,42 +39,44 @@ class PlanEditScreen extends StatelessWidget { _buildPlanEdit(BuildContext context, PlanEditLoadedState state) { mediaQueryData = MediaQuery.of(context); - return Scaffold( - resizeToAvoidBottomInset: false, - body: SafeArea( - child: Container( - width: double.maxFinite, - decoration: AppDecoration.fillGray, - child: ListView(children: [ - Container( - margin: EdgeInsets.only( - left: 16.h, - right: 20.h, - bottom: 10.h, - ), - padding: EdgeInsets.all(12.h), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: const BorderRadius.all(Radius.circular(12)), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 2, - blurRadius: 5, - offset: Offset(0, 3), - ), - ], - ), - child: Column(mainAxisSize: MainAxisSize.min, children: [ + return SafeArea( + child: Scaffold( + resizeToAvoidBottomInset: false, + body: Container( + width: double.maxFinite, + decoration: AppDecoration.fillGray, + child: ListView( + children: [ + SizedBox(height: 30.v), + Container( + margin: EdgeInsets.only( + left: 16.h, + right: 20.h, + bottom: 10.h, + ), + padding: EdgeInsets.all(12.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(12)), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), + ), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ CachedNetworkImage( - imageUrl: dotenv.env['STORAGE_URL']! + - (state.plan?['image'] ?? ''), + imageUrl: dotenv.env['STORAGE_URL']! + (state.plan?['image'] ?? ''), imageBuilder: (context, imageProvider) => CircleAvatar( backgroundImage: imageProvider, radius: 25, ), - placeholder: (context, url) => - CircularProgressIndicator(), + placeholder: (context, url) => CircularProgressIndicator(), errorWidget: (context, url, error) => CircleAvatar( child: Icon(Icons.person), backgroundColor: Colors.white, @@ -96,18 +96,16 @@ class PlanEditScreen extends StatelessWidget { SizedBox(height: 16.0), // Здесь нужно добавить виджет для загрузки картинки ElevatedButton( - onPressed: () => - context.read().add(EditPlan()), + onPressed: () => context.read().add(EditPlan()), child: Text('Сохранить'), ) - ]), - ) - ]), - ), - ), bottomNavigationBar: _buildBottomBar(context)); + ], + ), + ) + ], + ), + ), + ), + ); } } - -_buildBottomBar(BuildContext context) { - return CustomBottomBar(onChanged: (BottomBarEnum type) {}); -} diff --git a/lib/presentation/plans/plan_select_screen/bloc/plan_select_bloc.dart b/lib/presentation/plans/plan_select_screen/bloc/plan_select_bloc.dart index 40d5f6f..b74c0ed 100644 --- a/lib/presentation/plans/plan_select_screen/bloc/plan_select_bloc.dart +++ b/lib/presentation/plans/plan_select_screen/bloc/plan_select_bloc.dart @@ -1,6 +1,7 @@ //import 'package:eatvio_app/presentation/auth/auth_screen/auth_screen.dart'; import 'dart:async'; +import 'package:eatvio_app/app_router.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import '/core/app_export.dart'; @@ -10,6 +11,7 @@ import 'dart:convert'; import 'package:hive/hive.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:go_router/go_router.dart'; part 'plan_select_event.dart'; part 'plan_select_state.dart'; @@ -28,9 +30,7 @@ class PlanSelectBloc extends Bloc { final box = await Hive.openBox('token'); final token = box.get('token'); if (token == null) { - NavigatorService.popAndPushNamed( - AppRoutes.authScreen, - ); // Если токен есть, запускаем приложение + AppRouter.navigatorKey.currentContext?.goNamed('authScreen'); } final response = await http.get( Uri.parse(dotenv.env['SERVER_ROOT']! + '/plans/index'), diff --git a/lib/presentation/plans/plan_select_screen/plan_select_screen.dart b/lib/presentation/plans/plan_select_screen/plan_select_screen.dart index 586f6a5..810bd3a 100644 --- a/lib/presentation/plans/plan_select_screen/plan_select_screen.dart +++ b/lib/presentation/plans/plan_select_screen/plan_select_screen.dart @@ -1,10 +1,11 @@ +import 'package:go_router/go_router.dart'; + import 'bloc/plan_select_bloc.dart'; import 'models/plan_select_model.dart'; import 'package:eatvio_app/core/app_export.dart'; import 'package:eatvio_app/widgets/app_bar/appbar_subtitle.dart'; import 'package:eatvio_app/widgets/app_bar/appbar_leading_image.dart'; import 'package:eatvio_app/widgets/app_bar/custom_app_bar.dart'; -import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter/material.dart'; @@ -12,10 +13,7 @@ import 'package:flutter/material.dart'; class PlanSelectScreen extends StatelessWidget { static Widget builder(BuildContext context) { return BlocProvider( - create: (context) => PlanSelectBloc( - PlanSelectState(myProfileModelObj: PlanSelectModel())) - ..add(PlanSelectInitialEvent()), - child: PlanSelectScreen()); + create: (context) => PlanSelectBloc(PlanSelectState(myProfileModelObj: PlanSelectModel()))..add(PlanSelectInitialEvent()), child: PlanSelectScreen()); } Widget build(BuildContext context) { @@ -23,92 +21,93 @@ class PlanSelectScreen extends StatelessWidget { builder: (context, state) { if (state is PlanSelectInitialState) { // Это начальное состояние, данные ещё не загружены. - return CircularProgressIndicator( - color: Colors.white, - backgroundColor: Colors.white, + return Scaffold( + body: Center( + child: CircularProgressIndicator(), + ), ); } else if (state is PlanSelectLoadedState) { // Данные загружены, можно строить экран. return _buildSetingsScreen(context, state); } else { // Другие состояния, если они есть. - return Container(); // Пустой контейнер для необработанных состояний. + return Scaffold( + body: Center( + child: CircularProgressIndicator(), + ), + ); } }, ); } _buildSetingsScreen(BuildContext context, PlanSelectLoadedState state) { - mediaQueryData = MediaQuery.of(context); return Scaffold( - backgroundColor: Colors.grey[200], - resizeToAvoidBottomInset: true, - body: SafeArea( - child: SingleChildScrollView( - child: Container( - width: double.maxFinite, - decoration: BoxDecoration(color: Colors.grey[200]), - child: Padding( - padding: EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - SizedBox(height: 10), - Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: const BorderRadius.all(Radius.circular(12)), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 2, - blurRadius: 5, - offset: Offset(0, 3), - ), - ], - ), - child: CustomAppBar( - height: 54.v, - leadingWidth: 28.h, - leading: AppbarLeadingImage( - imagePath: ImageConstant.imgArrowLeft, - margin: EdgeInsets.only( - left: 16.h, top: 15.v, bottom: 15.v), - onTap: onTapBack(context), + appBar: AppBar( + toolbarHeight: 0, + automaticallyImplyLeading: false, + ), + backgroundColor: Colors.grey[200], + resizeToAvoidBottomInset: true, + body: SingleChildScrollView( + child: Container( + width: double.maxFinite, + decoration: BoxDecoration(color: Colors.grey[200]), + child: Padding( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SizedBox(height: 10), + Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(12)), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), ), - title: Padding( - padding: EdgeInsets.only(left: 20.h), - child: Row( - children: [ - AppbarSubtitle( - text: "Планы", - margin: EdgeInsets.only( - left: 80.h, top: 12.v, bottom: 9.v), + ], + ), + child: CustomAppBar( + height: 54.v, + leadingWidth: 28.h, + leading: AppbarLeadingImage( + imagePath: ImageConstant.imgArrowLeft, + margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v), + onTap: () => GoRouter.of(context).pop(), + ), + title: Padding( + padding: EdgeInsets.only(left: 20.h), + child: Row( + children: [ + AppbarSubtitle( + text: "Планы", + margin: EdgeInsets.only(left: 80.h, top: 12.v, bottom: 9.v), + ), + Spacer(), + Padding( + padding: const EdgeInsets.only(right: 20.0), + child: GestureDetector( + onTap: () => GoRouter.of(context).goNamed('planCreateScreen'), + child: Icon(Icons.create_outlined), ), - Spacer(), - Padding( - padding: const EdgeInsets.only(right: 20.0), - child: GestureDetector( - onTap: () => Navigator.pushNamed( - context, - AppRoutes.planCreateScreen, - ), - child: Icon(Icons.create_outlined)), - ) - ], - ), + ) + ], ), ), ), - SizedBox(height: 20), - _buildPlanList(context, state) - ], - ), + ), + SizedBox(height: 20), + _buildPlanList(context, state) + ], ), ), - ), + ), ), - bottomNavigationBar: _buildBottomBar(context), ); } @@ -119,51 +118,51 @@ class PlanSelectScreen extends StatelessWidget { itemBuilder: (context, index) { final post = state.posts?[index]; return Container( - margin: EdgeInsets.only(bottom: 10), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: const BorderRadius.all(Radius.circular(12)), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 2, - blurRadius: 5, - offset: Offset(0, 3), - ), - ], + margin: EdgeInsets.only(bottom: 10), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(12)), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), + ), + ], + ), + child: ListTile( + leading: CachedNetworkImage( + imageUrl: dotenv.env['STORAGE_URL']! + post['image'], + imageBuilder: (context, imageProvider) => CircleAvatar( + backgroundImage: imageProvider, + radius: 25, + ), + placeholder: (context, url) => CircularProgressIndicator(), + errorWidget: (context, url, error) => CircleAvatar( + child: Icon(Icons.person), + backgroundColor: Colors.white, + radius: 25, + ), ), - child: ListTile( - leading: CachedNetworkImage( - imageUrl: dotenv.env['STORAGE_URL']! + post['image'], - imageBuilder: (context, imageProvider) => CircleAvatar( - backgroundImage: imageProvider, - radius: 25, - ), - placeholder: (context, url) => CircularProgressIndicator(), - errorWidget: (context, url, error) => CircleAvatar( - child: Icon(Icons.person), - backgroundColor: Colors.white, - radius: 25, - ), + title: Text( + post?['name'] ?? '', + style: CustomTextStyles.titleMediumSemiBold, + ), + subtitle: Text(post?['details'] ?? ''), + onTap: () => GoRouter.of(context).goNamed( + 'planScreen', + extra: post?['id'].toString(), + ), + trailing: GestureDetector( + onTap: () => GoRouter.of(context).goNamed( + 'planEditScreen', + extra: post?['id'].toString(), ), - title: Text( - post?['name'] ?? '', - style: CustomTextStyles.titleMediumSemiBold, - ), - subtitle: Text(post?['details'] ?? ''), - onTap: () => Navigator.pushNamed( - context, - AppRoutes.planScreen, - arguments: post?['id'].toString(), - ), - trailing: GestureDetector( - onTap: () => Navigator.pushNamed( - context, - AppRoutes.planEditScreen, - arguments: post?['id'].toString(), - ), - child: Icon(Icons.edit)), - )); + child: Icon(Icons.edit), + ), + ), + ); }, ); } @@ -174,11 +173,7 @@ class PlanSelectScreen extends StatelessWidget { }; } - _buildBottomBar(BuildContext context) { - return CustomBottomBar(onChanged: (BottomBarEnum type) {}); - } - onTapArrowLeft(BuildContext context) { - NavigatorService.goBack(); + GoRouter.of(context).pop(); } } diff --git a/lib/presentation/plans/plan_show_screen/bloc/plan_show_bloc.dart b/lib/presentation/plans/plan_show_screen/bloc/plan_show_bloc.dart index 616b521..e920db8 100644 --- a/lib/presentation/plans/plan_show_screen/bloc/plan_show_bloc.dart +++ b/lib/presentation/plans/plan_show_screen/bloc/plan_show_bloc.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:eatvio_app/app_router.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; @@ -8,7 +9,10 @@ import 'package:http/http.dart' as http; import 'dart:convert'; import '/core/app_export.dart'; import 'package:eatvio_app/presentation/plans/plan_show_screen/models/plan_show_model.dart'; +import 'package:go_router/go_router.dart'; + part 'plan_show_event.dart'; + part 'plan_show_state.dart'; /// A bloc that manages the state of a Plan according to the event that is dispatched to it. @@ -26,9 +30,7 @@ class PlanBloc extends Bloc { final box = await Hive.openBox('token'); final token = box.get('token'); if (token == null) { - NavigatorService.popAndPushNamed( - AppRoutes.authScreen, - ); + AppRouter.navigatorKey.currentContext?.goNamed('authScreen'); } final String? planID = state.planModelObj?.planID; final response = await http.get( @@ -42,9 +44,7 @@ class PlanBloc extends Bloc { if (data is Map) { if (state is PlanLoadedState) { var currentState = state as PlanLoadedState; - emit(currentState.copyWith( - planModelObj: currentState.planModelObj, - plan: currentState.plan)); + emit(currentState.copyWith(planModelObj: currentState.planModelObj, plan: currentState.plan)); } else { emit(PlanLoadedState(plan: data)); } @@ -62,14 +62,11 @@ class PlanBloc extends Bloc { } } - FutureOr _deleteEntry( - DeleteEntry event, Emitter emit) async { + FutureOr _deleteEntry(DeleteEntry event, Emitter emit) async { final box = await Hive.openBox('token'); final token = box.get('token'); if (token == null) { - NavigatorService.popAndPushNamed( - AppRoutes.authScreen, - ); + AppRouter.navigatorKey.currentContext?.goNamed('authScreen'); } final int id = event.id; final response = await http.post( @@ -79,7 +76,7 @@ class PlanBloc extends Bloc { }, ); if (response.statusCode == 200) { - NavigatorService.pushNamed(AppRoutes.planSelectScreen); + AppRouter.navigatorKey.currentContext?.goNamed('planSelectScreen'); } else { // Статус ответа не равен 200, обрабатываем ошибку emit(PlanErrorState('Ошибка: ${response.statusCode}')); diff --git a/lib/presentation/plans/plan_show_screen/plan_show_screen.dart b/lib/presentation/plans/plan_show_screen/plan_show_screen.dart index eb91c2b..a19f6da 100644 --- a/lib/presentation/plans/plan_show_screen/plan_show_screen.dart +++ b/lib/presentation/plans/plan_show_screen/plan_show_screen.dart @@ -1,20 +1,20 @@ + +import 'package:go_router/go_router.dart'; + import 'bloc/plan_show_bloc.dart'; import 'models/plan_show_model.dart'; import 'package:eatvio_app/core/app_export.dart'; import 'package:flutter/material.dart'; -import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; class PlanScreen extends StatelessWidget { const PlanScreen({Key? key}) : super(key: key); + static Widget builder(BuildContext context) { final planID = ModalRoute.of(context)!.settings.arguments; return BlocProvider( - create: (context) => PlanBloc( - PlanState(planModelObj: PlanModel(planID: planID.toString()))) - ..add(PlanInitialEvent()), - child: PlanScreen()); + create: (context) => PlanBloc(PlanState(planModelObj: PlanModel(planID: planID.toString())))..add(PlanInitialEvent()), child: PlanScreen()); } Widget build(BuildContext context) { @@ -41,92 +41,77 @@ class PlanScreen extends StatelessWidget { _buildPlan(BuildContext context, PlanLoadedState state) { mediaQueryData = MediaQuery.of(context); - return Scaffold( - resizeToAvoidBottomInset: false, - body: SafeArea( - child: Container( - width: double.maxFinite, - decoration: AppDecoration.fillGray, - child: ListView( - children: [ - SizedBox(height: 30.v), - Container( - margin: EdgeInsets.only( - left: 16.h, - right: 20.h, - bottom: 10.h, + return SafeArea( + child: Scaffold( + resizeToAvoidBottomInset: false, + body: Container( + width: double.maxFinite, + decoration: AppDecoration.fillGray, + child: ListView( + children: [ + SizedBox(height: 30.v), + Container( + margin: EdgeInsets.only( + left: 16.h, + right: 20.h, + bottom: 10.h, + ), + padding: EdgeInsets.all(12.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(12)), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), ), - padding: EdgeInsets.all(12.h), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: const BorderRadius.all(Radius.circular(12)), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 2, - blurRadius: 5, - offset: Offset(0, 3), - ), - ], + ], + ), + child: Column(mainAxisSize: MainAxisSize.min, children: [ + CachedNetworkImage( + imageUrl: dotenv.env['STORAGE_URL']! + (state.plan?['image'] ?? ''), + imageBuilder: (context, imageProvider) => CircleAvatar( + backgroundImage: imageProvider, + radius: 25, ), - child: Column(mainAxisSize: MainAxisSize.min, children: [ - CachedNetworkImage( - imageUrl: dotenv.env['STORAGE_URL']! + - (state.plan?['image'] ?? ''), - imageBuilder: (context, imageProvider) => CircleAvatar( - backgroundImage: imageProvider, - radius: 25, - ), - placeholder: (context, url) => - CircularProgressIndicator(), - errorWidget: (context, url, error) => CircleAvatar( - child: Icon(Icons.person), - backgroundColor: Colors.white, - radius: 25, - ), + placeholder: (context, url) => CircularProgressIndicator(), + errorWidget: (context, url, error) => CircleAvatar( + child: Icon(Icons.person), + backgroundColor: Colors.white, + radius: 25, + ), + ), + SizedBox(height: 16.0), + Text(state.plan?['name'], style: CustomTextStyles.titleMediumBlack), + SizedBox(height: 8.0), + Text( + state.plan?['details'], + ), + SizedBox(height: 16.0), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + ElevatedButton( + onPressed: () { + // Действие для редактирования + }, + child: Text('Редактировать'), ), - SizedBox(height: 16.0), - Text(state.plan?['name'], - style: CustomTextStyles.titleMediumBlack), - SizedBox(height: 8.0), - Text( - state.plan?['details'], + ElevatedButton( + onPressed: () => context.read().add(DeleteEntry(state.plan?['id'])), + child: Text('Удалить'), ), - SizedBox(height: 16.0), - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - ElevatedButton( - onPressed: () { - // Действие для редактирования - }, - child: Text('Редактировать'), - ), - ElevatedButton( - onPressed: () => context - .read() - .add(DeleteEntry(state.plan?['id'])), - child: Text('Удалить'), - ), - ], - ) - ]), + ], ) - ], - ), - ), - ), bottomNavigationBar: _buildBottomBar(context)); - } - - - ///Handling route based on bottom click actions - String getCurrentRoute(BottomBarEnum type) { - switch (type) { - case BottomBarEnum.tf: - return AppRoutes.planScreen; - default: - return "/"; - } + ]), + ) + ], + ), + ), + ), + ); } ///Handling page based on route @@ -135,19 +120,15 @@ class PlanScreen extends StatelessWidget { String currentRoute, ) { switch (currentRoute) { - case AppRoutes.planScreen: + case '/plan': return PlanScreen.builder(context); default: return PlanScreen(); } } - _buildBottomBar(BuildContext context) { - return CustomBottomBar(onChanged: (BottomBarEnum type) {}); - } - /// Navigates to the previous screen. onTapArrowleftone(BuildContext context) { - NavigatorService.goBack(); + GoRouter.of(context).pop(); } } diff --git a/lib/presentation/profile/my_profile_screen/bloc/my_profile_bloc.dart b/lib/presentation/profile/my_profile_screen/bloc/my_profile_bloc.dart index baf7cdc..db799d2 100644 --- a/lib/presentation/profile/my_profile_screen/bloc/my_profile_bloc.dart +++ b/lib/presentation/profile/my_profile_screen/bloc/my_profile_bloc.dart @@ -1,6 +1,7 @@ //import 'package:eatvio_app/presentation/auth/auth_screen/auth_screen.dart'; import 'dart:async'; +import 'package:eatvio_app/app_router.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import '/core/app_export.dart'; @@ -10,6 +11,7 @@ import 'dart:convert'; import 'package:hive/hive.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:go_router/go_router.dart'; part 'my_profile_event.dart'; part 'my_profile_state.dart'; @@ -29,9 +31,7 @@ class MyProfileBloc extends Bloc { final box = await Hive.openBox('token'); final token = box.get('token'); if (token == null) { - NavigatorService.popAndPushNamed( - AppRoutes.authScreen, - ); // Если токен есть, запускаем приложение + AppRouter.navigatorKey.currentContext?.goNamed('authScreen'); } //final token = // "9|laravel_sanctum_KFXbccDcWEvTPfJmV99EQv1ne AggmpR2TidVGDPN34fa201a"; diff --git a/lib/presentation/profile/my_profile_screen/my_profile_screen.dart b/lib/presentation/profile/my_profile_screen/my_profile_screen.dart index 08c3a9c..e6f9706 100644 --- a/lib/presentation/profile/my_profile_screen/my_profile_screen.dart +++ b/lib/presentation/profile/my_profile_screen/my_profile_screen.dart @@ -1,8 +1,9 @@ +import 'package:go_router/go_router.dart'; + import 'bloc/my_profile_bloc.dart'; import 'models/my_profile_model.dart'; import 'package:eatvio_app/core/app_export.dart'; import 'package:eatvio_app/widgets/app_bar/appbar_subtitle.dart'; -import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; import 'package:eatvio_app/widgets/custom_text_form_field.dart'; import 'package:carousel_slider/carousel_slider.dart'; import 'package:image_picker/image_picker.dart'; @@ -19,10 +20,13 @@ import 'dart:convert'; class MyProfileScreen extends StatelessWidget { static Widget builder(BuildContext context) { return BlocProvider( - create: (context) => - MyProfileBloc(MyProfileState(myProfileModelObj: MyProfileModel())) - ..add(MyProfileInitialEvent()), - child: MyProfileScreen()); + create: (context) => MyProfileBloc( + MyProfileState( + myProfileModelObj: MyProfileModel(), + ), + )..add(MyProfileInitialEvent()), + child: MyProfileScreen(), + ); } Widget build(BuildContext context) { @@ -30,9 +34,10 @@ class MyProfileScreen extends StatelessWidget { builder: (context, state) { if (state is MyProfileInitialState) { // Это начальное состояние, данные ещё не загружены. - return CircularProgressIndicator( - color: Colors.white, - backgroundColor: Colors.white, + return Scaffold( + body: Center( + child: CircularProgressIndicator(), + ), ); } else if (state is MyProfileLoadedState) { // Данные загружены, можно строить экран. @@ -42,7 +47,11 @@ class MyProfileScreen extends StatelessWidget { return Center(child: Text(state.errorMessage)); } else { // Другие состояния, если они есть. - return Container(); // Пустой контейнер для необработанных состояний. + return Scaffold( + body: Center( + child: CircularProgressIndicator(), + ), + ); } }, ); @@ -51,89 +60,91 @@ class MyProfileScreen extends StatelessWidget { _buildMyProfileScreen(BuildContext context, MyProfileLoadedState state) { mediaQueryData = MediaQuery.of(context); return Scaffold( - backgroundColor: Colors.grey[200], - resizeToAvoidBottomInset: true, - body: SafeArea( - child: SingleChildScrollView( - child: Container( - width: double.maxFinite, - decoration: BoxDecoration(color: Colors.grey[200]), - child: Padding( - padding: EdgeInsets.all(16), - child: Column(children: [ - Container( - padding: EdgeInsets.symmetric( - horizontal: 24.h, - vertical: 12.v, - ), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: - const BorderRadius.all(Radius.circular(12)), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 2, - blurRadius: 5, - offset: Offset(0, 3), - ), - ], - ), - child: _buildInfo(context, state)), - SizedBox(height: 15.v), - Container( - padding: EdgeInsets.symmetric( - horizontal: 24.h, - vertical: 12.v, - ), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: - const BorderRadius.all(Radius.circular(12)), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 2, - blurRadius: 5, - offset: Offset(0, 3), - ), - ], - ), - child: _buildCarousel(context, state)), - SizedBox(height: 15.v), - if (!state.plans!.isEmpty) - Container( - padding: EdgeInsets.symmetric( - horizontal: 24.h, - vertical: 12.v, - ), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: const BorderRadius.all( - Radius.circular(12)), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 2, - blurRadius: 5, - offset: Offset(0, 3), - ), - ], - ), - child: _buildPlans( - context, - state.plans - ?.map((plan) => plan['name']) - .toList(), - state.plans - ?.map((plan) => plan['image']) - .toList(), - state.plans - ?.map((plan) => plan['state']) - .toList(), - )), - SizedBox(height: 5.v), - ]))))), bottomNavigationBar: _buildBottomBar(context)); + appBar: AppBar( + toolbarHeight: 0, + automaticallyImplyLeading: false, + ), + backgroundColor: Colors.grey[200], + resizeToAvoidBottomInset: true, + body: SingleChildScrollView( + child: Container( + width: double.maxFinite, + decoration: BoxDecoration(color: Colors.grey[200]), + child: Padding( + padding: EdgeInsets.all(16), + child: Column( + children: [ + SizedBox(height: 30.v), + Container( + padding: EdgeInsets.symmetric( + horizontal: 24.h, + vertical: 12.v, + ), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(12)), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), + ), + ], + ), + child: _buildInfo(context, state), + ), + SizedBox(height: 15.v), + Container( + padding: EdgeInsets.symmetric( + horizontal: 24.h, + vertical: 12.v, + ), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(12)), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), + ), + ], + ), + child: _buildCarousel(context, state)), + SizedBox(height: 15.v), + if (!state.plans!.isEmpty) + Container( + padding: EdgeInsets.symmetric( + horizontal: 24.h, + vertical: 12.v, + ), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(12)), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), + ), + ], + ), + child: _buildPlans( + context, + state.plans?.map((plan) => plan['name']).toList(), + state.plans?.map((plan) => plan['image']).toList(), + state.plans?.map((plan) => plan['state']).toList(), + )), + SizedBox(height: 5.v), + ], + ), + ), + ), + ), + ); } /// Section Widget @@ -143,8 +154,7 @@ class MyProfileScreen extends StatelessWidget { GestureDetector( onTap: () => addImage(context), child: CachedNetworkImage( - imageUrl: dotenv.env['STORAGE_URL']! + - (state.user?['profile_photo_path'] ?? ''), + imageUrl: dotenv.env['STORAGE_URL']! + (state.user?['profile_photo_path'] ?? ''), imageBuilder: (context, imageProvider) => CircleAvatar( backgroundImage: imageProvider, radius: 60, @@ -176,14 +186,10 @@ class MyProfileScreen extends StatelessWidget { onTapSettings(BuildContext context) { return () { - Navigator.pushNamed(context, AppRoutes.mySettings); + GoRouter.of(context).goNamed('mySettingsScreen'); }; } - _buildBottomBar(BuildContext context) { - return CustomBottomBar(onChanged: (BottomBarEnum type) {}); - } - _buildCarousel(BuildContext contex, MyProfileLoadedState state) { return CarouselSlider( options: CarouselOptions(height: 320.0), @@ -204,12 +210,7 @@ class MyProfileScreen extends StatelessWidget { SizedBox(height: 50.v), _buildChart( context, - state.chartPoints?.values - .map((e) => e['weight']) - .toList() - .map((i) => i.toDouble()) - .toList() ?? - [0.0, 0.0, 0.0, 0.0], + state.chartPoints?.values.map((e) => e['weight']).toList().map((i) => i.toDouble()).toList() ?? [0.0, 0.0, 0.0, 0.0], state.chartDates, ) ])); @@ -224,13 +225,7 @@ class MyProfileScreen extends StatelessWidget { SizedBox(height: 50.v), _buildChart( context, - _bodyMassIndex( - state.chartPoints?.values - .map((e) => e['weight']) - .toList() - .map((i) => i.toDouble()) - .toList() ?? - [0.0, 0.0, 0.0, 0.0], + _bodyMassIndex(state.chartPoints?.values.map((e) => e['weight']).toList().map((i) => i.toDouble()).toList() ?? [0.0, 0.0, 0.0, 0.0], state.userDetails?['height'].toInt() ?? 160), state.chartDates), ]); @@ -245,8 +240,7 @@ class MyProfileScreen extends StatelessWidget { ), ), SizedBox(height: 30.v), - _buildCircumferenceChart( - context, state.circumferences, state.chartDates), + _buildCircumferenceChart(context, state.circumferences, state.chartDates), _buildChartLegend() ])); default: @@ -290,8 +284,7 @@ class MyProfileScreen extends StatelessWidget { ); } - _buildChart( - BuildContext context, List chartPoints, List? dates) { + _buildChart(BuildContext context, List chartPoints, List? dates) { List spots = []; for (int i = 0; i < 4; i++) { spots.add( @@ -299,8 +292,7 @@ class MyProfileScreen extends StatelessWidget { ); } - Widget bottomTitleWidgets( - double value, TitleMeta meta, double chartWidth, List? dates) { + Widget bottomTitleWidgets(double value, TitleMeta meta, double chartWidth, List? dates) { if (value % 1 != 0) { return Container(); } @@ -312,8 +304,7 @@ class MyProfileScreen extends StatelessWidget { int index = value.toInt(); if (index >= 0 && index < dates!.length) { // Проверка на допустимый индекс в массиве dates - String formattedDate = - DateFormat('dd.MM').format(DateTime.parse(dates[index])); + String formattedDate = DateFormat('dd.MM').format(DateTime.parse(dates[index])); return SideTitleWidget( axisSide: meta.axisSide, space: 16, @@ -370,18 +361,15 @@ class MyProfileScreen extends StatelessWidget { dotData: const FlDotData(show: false), ), ], - minY: chartPoints.reduce( - (value, element) => value < element ? value : element), - maxY: chartPoints.reduce( - (value, element) => value > element ? value : element), + minY: chartPoints.reduce((value, element) => value < element ? value : element), + maxY: chartPoints.reduce((value, element) => value > element ? value : element), minX: 1, maxX: 4, titlesData: FlTitlesData( leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, - getTitlesWidget: (value, meta) => - leftTitleWidgets(value, meta, constraints.maxWidth), + getTitlesWidget: (value, meta) => leftTitleWidgets(value, meta, constraints.maxWidth), reservedSize: 56, ), drawBelowEverything: true, @@ -392,8 +380,7 @@ class MyProfileScreen extends StatelessWidget { bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, - getTitlesWidget: (value, meta) => bottomTitleWidgets( - value, meta, constraints.maxWidth, dates), + getTitlesWidget: (value, meta) => bottomTitleWidgets(value, meta, constraints.maxWidth, dates), reservedSize: 36, interval: 1, ), @@ -453,11 +440,9 @@ class MyProfileScreen extends StatelessWidget { }; } - _buildCircumferenceChart( - BuildContext context, chartPoints, List? dates) { + _buildCircumferenceChart(BuildContext context, chartPoints, List? dates) { // Определение осей графика - final chestCircumferences = - chartPoints.values.map((e) => e['chest_circumference']).toList(); + final chestCircumferences = chartPoints.values.map((e) => e['chest_circumference']).toList(); List spotsChest = []; for (int i = 0; i < 4; i++) { chestCircumferences[i] = chestCircumferences[i] * 10 / 10; @@ -465,8 +450,7 @@ class MyProfileScreen extends StatelessWidget { FlSpot((i * 10 / 10) + 1, chestCircumferences[i] * 10 / 10), ); } - final waistCircumferences = - chartPoints.values.map((e) => e['waist_circumference']).toList(); + final waistCircumferences = chartPoints.values.map((e) => e['waist_circumference']).toList(); List spotsWaist = []; for (int i = 0; i < 4; i++) { waistCircumferences[i] = waistCircumferences[i] * 10 / 10; @@ -474,8 +458,7 @@ class MyProfileScreen extends StatelessWidget { FlSpot((i * 10 / 10) + 1, waistCircumferences[i] * 10 / 10), ); } - final hipCircumferences = - chartPoints.values.map((e) => e['hip_circumference']).toList(); + final hipCircumferences = chartPoints.values.map((e) => e['hip_circumference']).toList(); List spotsHip = []; for (int i = 0; i < 4; i++) { hipCircumferences[i] = hipCircumferences[i] * 10 / 10; @@ -484,14 +467,9 @@ class MyProfileScreen extends StatelessWidget { ); } - final allCircumferences = [ - ...chestCircumferences, - ...hipCircumferences, - ...waistCircumferences - ]; + final allCircumferences = [...chestCircumferences, ...hipCircumferences, ...waistCircumferences]; - Widget bottomTitleWidgets( - double value, TitleMeta meta, double chartWidth, List? dates) { + Widget bottomTitleWidgets(double value, TitleMeta meta, double chartWidth, List? dates) { if (value % 1 != 0) { return Container(); } @@ -503,8 +481,7 @@ class MyProfileScreen extends StatelessWidget { int index = value.toInt(); if (index >= 0 && index < dates!.length) { // Проверка на допустимый индекс в массиве dates - String formattedDate = - DateFormat('dd.MM').format(DateTime.parse(dates[index])); + String formattedDate = DateFormat('dd.MM').format(DateTime.parse(dates[index])); return SideTitleWidget( axisSide: meta.axisSide, space: 16, @@ -583,18 +560,15 @@ class MyProfileScreen extends StatelessWidget { dotData: const FlDotData(show: false), ), ], - minY: allCircumferences.reduce( - (value, element) => value < element ? value : element), - maxY: allCircumferences.reduce( - (value, element) => value > element ? value : element), + minY: allCircumferences.reduce((value, element) => value < element ? value : element), + maxY: allCircumferences.reduce((value, element) => value > element ? value : element), minX: 1, maxX: 4, titlesData: FlTitlesData( leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, - getTitlesWidget: (value, meta) => - leftTitleWidgets(value, meta, constraints.maxWidth), + getTitlesWidget: (value, meta) => leftTitleWidgets(value, meta, constraints.maxWidth), reservedSize: 56, ), drawBelowEverything: true, @@ -605,8 +579,7 @@ class MyProfileScreen extends StatelessWidget { bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, - getTitlesWidget: (value, meta) => bottomTitleWidgets( - value, meta, constraints.maxWidth, dates), + getTitlesWidget: (value, meta) => bottomTitleWidgets(value, meta, constraints.maxWidth, dates), reservedSize: 36, interval: 1, ), @@ -661,125 +634,80 @@ class MyProfileScreen extends StatelessWidget { } /// Section Widget - _buildPlans(BuildContext context, List? plans, - List? imagePaths, List? states) { + _buildPlans(BuildContext context, List? plans, List? imagePaths, List? states) { return Padding( padding: EdgeInsets.only(left: 7.h), child: Column(children: [ Padding( padding: EdgeInsets.only(left: 15.h), - child: Text("plans_complete".tr, - style: CustomTextStyles.titleMediumBlack - .copyWith(color: appTheme.black900))), + child: Text("plans_complete".tr, style: CustomTextStyles.titleMediumBlack.copyWith(color: appTheme.black900))), SizedBox(height: 5.v), Container( - decoration: AppDecoration.outlineBlueGray - .copyWith(borderRadius: BorderRadiusStyle.roundedBorder5), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - height: 105.v, - width: 343.h, - child: - Stack(alignment: Alignment.bottomCenter, children: [ - Align( - alignment: Alignment.topCenter, - child: ListView.builder( - itemCount: plans?.length ?? 0, - itemBuilder: (context, i) { - String state = states?[i]; - var boxColor = appTheme.greenA700; - switch (states?[i]) { - case 'active': - boxColor = appTheme.greenA700; - break; - case 'archived': // График ИМТ - boxColor = appTheme.gray400; - break; - case 'draft': - boxColor = appTheme.deepOrange900; - break; - case 'in_progress': //График обхватов - boxColor = appTheme.lightGreen300; - break; - } - return SizedBox( - height: 40.v, - width: 343.h, - child: Stack( - alignment: Alignment.bottomRight, - children: [ - Align( - alignment: - Alignment.topCenter, - child: Container( - margin: EdgeInsets.only( - bottom: 11.v), - padding: - EdgeInsets.symmetric( - horizontal: 6.h, - vertical: 4.v), - decoration: AppDecoration - .fillGray - .copyWith( - borderRadius: - BorderRadiusStyle - .roundedBorder5), - child: Row( - mainAxisAlignment: - MainAxisAlignment - .spaceBetween, - children: [ - Padding( - padding: EdgeInsets - .only( - bottom: 1 - .v), - child: Text( - plans![i], - style: CustomTextStyles - .titleSmallBluegray800Bold)), - Container( - height: 19.v, - width: 100.h, - decoration: BoxDecoration( - color: - boxColor, - borderRadius: - BorderRadius.circular( - 5.h))) - ]))), - Align( - alignment: Alignment.center, - child: Container( - margin: EdgeInsets.only( - bottom: 12.h), - child: Image.network( - dotenv.env[ - 'STORAGE_URL']! + - imagePaths?[i], - height: 25.adaptSize, - width: 25.adaptSize, - ))), - Align( - alignment: - Alignment.bottomRight, - child: Container( - width: 70.h, - margin: EdgeInsets.only( - bottom: 14.h, - right: 22.h), - child: Text("$state".tr, - maxLines: 3, - overflow: TextOverflow - .ellipsis, - style: theme.textTheme - .titleSmall))) - ])); - })), - ])) - ])) + decoration: AppDecoration.outlineBlueGray.copyWith(borderRadius: BorderRadiusStyle.roundedBorder5), + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + SizedBox( + height: 105.v, + width: 343.h, + child: Stack(alignment: Alignment.bottomCenter, children: [ + Align( + alignment: Alignment.topCenter, + child: ListView.builder( + itemCount: plans?.length ?? 0, + itemBuilder: (context, i) { + String state = states?[i]; + var boxColor = appTheme.greenA700; + switch (states?[i]) { + case 'active': + boxColor = appTheme.greenA700; + break; + case 'archived': // График ИМТ + boxColor = appTheme.gray400; + break; + case 'draft': + boxColor = appTheme.deepOrange900; + break; + case 'in_progress': //График обхватов + boxColor = appTheme.lightGreen300; + break; + } + return SizedBox( + height: 40.v, + width: 343.h, + child: Stack(alignment: Alignment.bottomRight, children: [ + Align( + alignment: Alignment.topCenter, + child: Container( + margin: EdgeInsets.only(bottom: 11.v), + padding: EdgeInsets.symmetric(horizontal: 6.h, vertical: 4.v), + decoration: AppDecoration.fillGray.copyWith(borderRadius: BorderRadiusStyle.roundedBorder5), + child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + Padding( + padding: EdgeInsets.only(bottom: 1.v), + child: Text(plans![i], style: CustomTextStyles.titleSmallBluegray800Bold)), + Container( + height: 19.v, + width: 100.h, + decoration: BoxDecoration(color: boxColor, borderRadius: BorderRadius.circular(5.h))) + ]))), + Align( + alignment: Alignment.center, + child: Container( + margin: EdgeInsets.only(bottom: 12.h), + child: Image.network( + dotenv.env['STORAGE_URL']! + imagePaths?[i], + height: 25.adaptSize, + width: 25.adaptSize, + ))), + Align( + alignment: Alignment.bottomRight, + child: Container( + width: 70.h, + margin: EdgeInsets.only(bottom: 14.h, right: 22.h), + child: Text("$state".tr, maxLines: 3, overflow: TextOverflow.ellipsis, style: theme.textTheme.titleSmall))) + ])); + })), + ])) + ])) ])); } @@ -807,44 +735,38 @@ class MyProfileScreen extends StatelessWidget { children: [ Padding( padding: EdgeInsets.symmetric(vertical: 5.v), - child: Text("chest_circumference".tr, - style: theme.textTheme.bodyLarge), + child: Text("chest_circumference".tr, style: theme.textTheme.bodyLarge), ), SizedBox(height: 5.v), CustomTextFormField( controller: state.chestCircumferenceController, keyboardType: TextInputType.number, inputFormatters: [ - FilteringTextInputFormatter - .digitsOnly, // Ограничение ввода только цифрами + FilteringTextInputFormatter.digitsOnly, // Ограничение ввода только цифрами ], ), Padding( padding: EdgeInsets.symmetric(vertical: 15.v), - child: Text("waist_circumference".tr, - style: theme.textTheme.bodyLarge), + child: Text("waist_circumference".tr, style: theme.textTheme.bodyLarge), ), SizedBox(height: 5.v), CustomTextFormField( controller: state.waistCircumferenceController, keyboardType: TextInputType.number, inputFormatters: [ - FilteringTextInputFormatter - .digitsOnly, // Ограничение ввода только цифрами + FilteringTextInputFormatter.digitsOnly, // Ограничение ввода только цифрами ], ), Padding( padding: EdgeInsets.symmetric(vertical: 15.v), - child: Text("hip_circumference".tr, - style: theme.textTheme.bodyLarge), + child: Text("hip_circumference".tr, style: theme.textTheme.bodyLarge), ), SizedBox(height: 5.v), CustomTextFormField( controller: state.hipCircumferenceController, keyboardType: TextInputType.number, inputFormatters: [ - FilteringTextInputFormatter - .digitsOnly, // Ограничение ввода только цифрами + FilteringTextInputFormatter.digitsOnly, // Ограничение ввода только цифрами ], ), ], @@ -863,8 +785,7 @@ class MyProfileScreen extends StatelessWidget { ), ], backgroundColor: Colors.white, - titlePadding: EdgeInsets.all( - 20.0), // Можете настроить отступы по вашему желанию + titlePadding: EdgeInsets.all(20.0), // Можете настроить отступы по вашему желанию ); }, ); @@ -894,14 +815,10 @@ class MyProfileScreen extends StatelessWidget { ); if (response.statusCode == 201) { - Navigator.pushNamed( - context, - AppRoutes.myProfile, - ); + GoRouter.of(context).go('/my-profile-screen'); } else { // Ошибка от сервера - print( - "Ошибка при отправке данных. Код ответа: ${response.statusCode}"); + print("Ошибка при отправке данных. Код ответа: ${response.statusCode}"); } } catch (e) { // Обработка ошибок при отправке запроса @@ -987,14 +904,10 @@ class MyProfileScreen extends StatelessWidget { ); if (response.statusCode == 201) { - Navigator.pushNamed( - context, - AppRoutes.myProfile, - ); + GoRouter.of(context).go('/my-profile-screen'); } else { // Ошибка от сервера - print( - "Ошибка при отправке данных. Код ответа: ${response.statusCode}"); + print("Ошибка при отправке данных. Код ответа: ${response.statusCode}"); } } catch (e) { // Обработка ошибок при отправке запроса diff --git a/lib/presentation/profile/my_profile_screen/my_settings_screen.dart b/lib/presentation/profile/my_profile_screen/my_settings_screen.dart index df45cdb..a5be7c0 100644 --- a/lib/presentation/profile/my_profile_screen/my_settings_screen.dart +++ b/lib/presentation/profile/my_profile_screen/my_settings_screen.dart @@ -1,10 +1,11 @@ +import 'package:go_router/go_router.dart'; + import 'bloc/my_profile_bloc.dart'; import 'models/my_profile_model.dart'; import 'package:eatvio_app/core/app_export.dart'; import 'package:eatvio_app/widgets/app_bar/appbar_subtitle.dart'; import 'package:eatvio_app/widgets/app_bar/appbar_leading_image.dart'; import 'package:eatvio_app/widgets/app_bar/custom_app_bar.dart'; -import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; import 'package:eatvio_app/widgets/custom_about_list.dart'; import 'package:eatvio_app/widgets/custom_expandable_list.dart'; import 'package:eatvio_app/widgets/custom_notification_screen.dart'; @@ -15,10 +16,7 @@ import 'package:flutter/material.dart'; class MySettingsScreen extends StatelessWidget { static Widget builder(BuildContext context) { return BlocProvider( - create: (context) => - MyProfileBloc(MyProfileState(myProfileModelObj: MyProfileModel())) - ..add(MyProfileInitialEvent()), - child: MySettingsScreen()); + create: (context) => MyProfileBloc(MyProfileState(myProfileModelObj: MyProfileModel()))..add(MyProfileInitialEvent()), child: MySettingsScreen()); } Widget build(BuildContext context) { @@ -26,153 +24,152 @@ class MySettingsScreen extends StatelessWidget { builder: (context, state) { if (state is MyProfileInitialState) { // Это начальное состояние, данные ещё не загружены. - return CircularProgressIndicator( - color: Colors.white, - backgroundColor: Colors.white, + return Scaffold( + body: Center( + child: CircularProgressIndicator(), + ), ); } else if (state is MyProfileLoadedState) { // Данные загружены, можно строить экран. return _buildSetingsScreen(context, state); } else { // Другие состояния, если они есть. - return Container(); // Пустой контейнер для необработанных состояний. + return Scaffold( + body: Center( + child: CircularProgressIndicator(), + ), + ); } }, ); } _buildSetingsScreen(BuildContext context, MyProfileLoadedState state) { - mediaQueryData = MediaQuery.of(context); - return Scaffold( - backgroundColor: Colors.grey[200], - resizeToAvoidBottomInset: true, - body: SafeArea( - child: SingleChildScrollView( - child: Container( - width: double.maxFinite, - decoration: BoxDecoration(color: Colors.grey[200]), - child: Padding( - padding: EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - SizedBox(height: 30), - Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: - const BorderRadius.all(Radius.circular(12)), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 2, - blurRadius: 5, - offset: Offset(0, 3), - ), - ], + return Scaffold( + appBar: AppBar( + toolbarHeight: 0, + automaticallyImplyLeading: false, + ), + backgroundColor: Colors.grey[200], + resizeToAvoidBottomInset: true, + body: SingleChildScrollView( + child: Container( + width: double.maxFinite, + decoration: BoxDecoration(color: Colors.grey[200]), + child: Padding( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SizedBox(height: 30), + Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(12)), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), + ), + ], + ), + child: CustomAppBar( + height: 54.v, + leadingWidth: 28.h, + leading: AppbarLeadingImage( + imagePath: ImageConstant.imgArrowLeft, + margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v), + onTap: () => GoRouter.of(context).pop(), ), - child: CustomAppBar( - height: 54.v, - leadingWidth: 28.h, - leading: AppbarLeadingImage( - imagePath: ImageConstant.imgArrowLeft, - margin: EdgeInsets.only( - left: 16.h, top: 15.v, bottom: 15.v), - onTap: onTapBack(context)), - title: Padding( - padding: EdgeInsets.only(left: 20.h), - child: Row(children: [ - AppbarSubtitle( - text: state.user?['name'], - margin: EdgeInsets.only( - left: 80.h, top: 12.v, bottom: 9.v)) - ])), - )), - SizedBox(height: 20), - Container( - padding: EdgeInsets.symmetric( - horizontal: 24.h, - vertical: 12.v, - ), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: const BorderRadius.all(Radius.circular(12)), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 2, - blurRadius: 5, - offset: Offset(0, 3), - ), - ], - ), - child: Column( - children: [ - SectionTitle('Язык'), - LanguageDropdown(), - ], - ), + title: Padding( + padding: EdgeInsets.only(left: 20.h), + child: Row(children: [AppbarSubtitle(text: state.user?['name'], margin: EdgeInsets.only(left: 80.h, top: 12.v, bottom: 9.v))])), + )), + SizedBox(height: 20), + Container( + padding: EdgeInsets.symmetric( + horizontal: 24.h, + vertical: 12.v, ), - SizedBox(height: 20), - Container( - padding: EdgeInsets.symmetric( - horizontal: 24.h, - vertical: 12.v, - ), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: const BorderRadius.all(Radius.circular(12)), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 2, - blurRadius: 5, - offset: Offset(0, 3), - ), - ], - ), - child: Column( - children: [ - SectionTitle('Уведомления'), - NotificationSwitch('Уведомление об обновлениях'), - NotificationSwitch('Напоминания о заполнении'), - ], - ), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(12)), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), + ), + ], ), - SizedBox(height: 20), - Container( - padding: EdgeInsets.symmetric( - horizontal: 24.h, - vertical: 12.v, - ), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: const BorderRadius.all(Radius.circular(12)), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 2, - blurRadius: 5, - offset: Offset(0, 3), - ), - ], - ), - child: Column( - children: [ - SectionTitle('Дополнительная информация'), - ExpandableList('Персональные данные'), - AboutList('О нас', ['ООО Итвио', '2023-н.в.']), - ], - ), + child: Column( + children: [ + SectionTitle('Язык'), + LanguageDropdown(), + ], ), - ], - ), + ), + SizedBox(height: 20), + Container( + padding: EdgeInsets.symmetric( + horizontal: 24.h, + vertical: 12.v, + ), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(12)), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), + ), + ], + ), + child: Column( + children: [ + SectionTitle('Уведомления'), + NotificationSwitch('Уведомление об обновлениях'), + NotificationSwitch('Напоминания о заполнении'), + ], + ), + ), + SizedBox(height: 20), + Container( + padding: EdgeInsets.symmetric( + horizontal: 24.h, + vertical: 12.v, + ), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(12)), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), + ), + ], + ), + child: Column( + children: [ + SectionTitle('Дополнительная информация'), + ExpandableList('Персональные данные'), + AboutList('О нас', ['ООО Итвио', '2023-н.в.']), + ], + ), + ), + ], ), ), - ), + ), ), - bottomNavigationBar: _buildBottomBar(context), ); } @@ -182,11 +179,7 @@ class MySettingsScreen extends StatelessWidget { }; } - _buildBottomBar(BuildContext context) { - return CustomBottomBar(onChanged: (BottomBarEnum type) {}); - } - onTapArrowLeft(BuildContext context) { - NavigatorService.goBack(); + GoRouter.of(context).pop(); } } diff --git a/lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_bloc.dart b/lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_bloc.dart index f637dbf..881e14a 100644 --- a/lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_bloc.dart +++ b/lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_bloc.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:eatvio_app/app_router.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; @@ -8,6 +9,7 @@ import 'package:http/http.dart' as http; import 'dart:convert'; import '/core/app_export.dart'; import 'package:eatvio_app/presentation/recipe/recipe_create_screen/models/recipe_create_model.dart'; +import 'package:go_router/go_router.dart'; part 'recipe_create_event.dart'; part 'recipe_create_state.dart'; @@ -16,6 +18,9 @@ class RecipeCreateBloc extends Bloc { RecipeCreateBloc(RecipeCreateState initialState) : super(initialState) { on(_onInitialize); on(_createRecipe); + on(_addStep); + on(_removeStep); + on(_reloadScreen); } _onInitialize( @@ -25,31 +30,72 @@ class RecipeCreateBloc extends Bloc { final box = await Hive.openBox('token'); final token = box.get('token'); if (token == null) { - NavigatorService.popAndPushNamed( - AppRoutes.authScreen, - ); + AppRouter.navigatorKey.currentContext?.goNamed('authScreen'); } if (state is RecipeCreateLoadedState) { var currentState = state as RecipeCreateLoadedState; emit(currentState.copyWith( recipeModelObj: currentState.recipeModelObj, title: currentState.title, + token: currentState.token, description: currentState.description, video_url: currentState.video_url, - steps: currentState.steps)); + steps: currentState.steps, + stepDescriptions: currentState.stepDescriptions, + tags: currentState.tags)); } else { final Map step = { 'description': '', 'image': '', }; emit(RecipeCreateLoadedState( + token: token, title: TextEditingController(), description: TextEditingController(), video_url: TextEditingController(), + tags: TextEditingController(), + stepDescriptions: [TextEditingController()], steps: [step])); } } + FutureOr _addStep( + AddStep event, Emitter emit) async { + if (state is RecipeCreateLoadedState) { + var currentState = state as RecipeCreateLoadedState; + + List? updatedSteps = currentState.steps; + List? updatedStepDescriptions = + currentState.stepDescriptions; + + updatedSteps!.add({ + 'description': '', + 'image': '', + }); + updatedStepDescriptions!.add(TextEditingController()); + emit(currentState.copyWith( + steps: updatedSteps, stepDescriptions: updatedStepDescriptions)); + await _reloadScreen(event, emit); + } + } + + FutureOr _removeStep( + RemoveStep event, Emitter emit) async { + if (state is RecipeCreateLoadedState) { + var currentState = state as RecipeCreateLoadedState; + + List? updatedSteps = currentState.steps; + List? updatedStepDescriptions = + currentState.stepDescriptions; + + updatedSteps!.removeAt(event.id); + updatedStepDescriptions!.removeAt(event.id); + emit(currentState.copyWith( + steps: updatedSteps, stepDescriptions: updatedStepDescriptions)); + await _reloadScreen(event, emit); + } + } + FutureOr _createRecipe( CreateRecipe event, Emitter emit) async { if (state is RecipeCreateLoadedState) { @@ -59,10 +105,11 @@ class RecipeCreateBloc extends Bloc { currentState.title?.text, currentState.video_url?.text, currentState.description?.text, + currentState.steps, ); if (response.statusCode == 200) { - NavigatorService.pushNamed(AppRoutes.recipeSelectScreen); + AppRouter.navigatorKey.currentContext?.goNamed('recipeSelectScreen'); } else { print( 'Failed to create blog post. Status Code: ${response.statusCode} ${response.body}'); @@ -70,17 +117,14 @@ class RecipeCreateBloc extends Bloc { } } - FutureOr sendRecipe( - String? title, - String? video_url, - String? description, - ) async { + FutureOr sendRecipe(String? title, String? video_url, + String? description, List? steps) async { String url = '${dotenv.env['SERVER_ROOT']!}/recipe/create'; - final Map credentials = { 'title': title, 'description': description, 'video_url': video_url, + 'steps': steps, 'status': 'active', }; @@ -95,4 +139,26 @@ class RecipeCreateBloc extends Bloc { return response; } + + FutureOr _reloadScreen( + RecipeCreateEvent? event, Emitter emit) async { + print('Reloading..'); + final box = await Hive.openBox('token'); + final token = box.get('token'); + if (token == null) { + AppRouter.navigatorKey.currentContext?.goNamed('authScreen'); + } + if (state is RecipeCreateLoadedState) { + var currentState = state as RecipeCreateLoadedState; + emit(currentState.copyWith( + recipeModelObj: currentState.recipeModelObj, + title: currentState.title, + token: currentState.token, + description: currentState.description, + video_url: currentState.video_url, + steps: currentState.steps, + stepDescriptions: currentState.stepDescriptions, + tags: currentState.tags)); + } + } } diff --git a/lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_event.dart b/lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_event.dart index fcea807..085045d 100644 --- a/lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_event.dart +++ b/lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_event.dart @@ -19,3 +19,22 @@ class CreateRecipe extends RecipeCreateEvent { @override List get props => []; } + +class AddStep extends RecipeCreateEvent { + AddStep(); + @override + List get props => []; +} + +class RemoveStep extends RecipeCreateEvent { + RemoveStep(this.id); + final int id; + @override + List get props => [id]; +} + +class ReloadScreen extends RecipeCreateEvent { + ReloadScreen(); + @override + List get props => []; +} diff --git a/lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_state.dart b/lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_state.dart index 2a331cb..8545b83 100644 --- a/lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_state.dart +++ b/lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_state.dart @@ -24,33 +24,52 @@ class RecipeCreateState extends Equatable { class RecipeCreateLoadedState extends RecipeCreateState { RecipeCreateLoadedState( {this.recipeModelObj, + this.token, this.title, this.description, this.video_url, + this.tags, + this.stepDescriptions, this.steps}); RecipeCreateModel? recipeModelObj; + String? token; TextEditingController? title; TextEditingController? description; TextEditingController? video_url; + TextEditingController? tags; List? steps; + List? stepDescriptions; @override - List get props => - [recipeModelObj, title, description, video_url, steps]; + List get props => [ + recipeModelObj, + title, + token, + description, + video_url, + steps, + tags, + stepDescriptions + ]; RecipeCreateLoadedState copyWith({ RecipeCreateModel? recipeModelObj, + String? token, TextEditingController? title, TextEditingController? description, TextEditingController? video_url, + TextEditingController? tags, List? steps, + List? stepDescriptions, }) { return RecipeCreateLoadedState( - recipeModelObj: recipeModelObj ?? this.recipeModelObj, - title: title ?? this.title, - description: description ?? this.description, - video_url: video_url ?? this.video_url, - steps: steps ?? this.steps, - ); + recipeModelObj: recipeModelObj ?? this.recipeModelObj, + title: title ?? this.title, + description: description ?? this.description, + video_url: video_url ?? this.video_url, + token: token ?? this.token, + steps: steps ?? this.steps, + tags: tags ?? this.tags, + stepDescriptions: stepDescriptions ?? this.stepDescriptions); } } diff --git a/lib/presentation/recipe/recipe_create_screen/recipe_create_screen.dart b/lib/presentation/recipe/recipe_create_screen/recipe_create_screen.dart index b663db2..3a81754 100644 --- a/lib/presentation/recipe/recipe_create_screen/recipe_create_screen.dart +++ b/lib/presentation/recipe/recipe_create_screen/recipe_create_screen.dart @@ -1,3 +1,5 @@ +import 'package:go_router/go_router.dart'; + import 'bloc/recipe_create_bloc.dart'; import 'models/recipe_create_model.dart'; import 'package:eatvio_app/core/app_export.dart'; @@ -5,18 +7,17 @@ import 'package:flutter/material.dart'; import 'package:eatvio_app/widgets/app_bar/custom_app_bar.dart'; import 'package:eatvio_app/widgets/app_bar/appbar_leading_image.dart'; import 'package:eatvio_app/widgets/app_bar/appbar_subtitle.dart'; -import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; import 'package:eatvio_app/widgets/custom_text_form_field.dart'; import 'package:eatvio_app/widgets/custom_outlined_button.dart'; class RecipeCreateScreen extends StatelessWidget { const RecipeCreateScreen({Key? key}) : super(key: key); + static Widget builder(BuildContext context) { final recipeID = ModalRoute.of(context)!.settings.arguments; return BlocProvider( - create: (context) => RecipeCreateBloc(RecipeCreateState( - recipeModelObj: RecipeCreateModel(recipeID: recipeID.toString()))) - ..add(RecipeCreateInitialEvent()), + create: (context) => + RecipeCreateBloc(RecipeCreateState(recipeModelObj: RecipeCreateModel(recipeID: recipeID.toString())))..add(RecipeCreateInitialEvent()), child: RecipeCreateScreen()); } @@ -51,8 +52,13 @@ class RecipeCreateScreen extends StatelessWidget { } } - return SafeArea( + return RefreshIndicator( + onRefresh: refreshPage(context), child: Scaffold( + appBar: AppBar( + toolbarHeight: 0, + automaticallyImplyLeading: false, + ), resizeToAvoidBottomInset: false, body: Container( width: double.maxFinite, @@ -69,8 +75,7 @@ class RecipeCreateScreen extends StatelessWidget { SizedBox(height: 20.v), _buildAdditional(context, state) ]), - ), - bottomNavigationBar: _buildBottomBar(context))); + ))); } _buldAppBar(BuildContext context) { @@ -162,45 +167,50 @@ class RecipeCreateScreen extends StatelessWidget { ]), ); } -} -_buildAddPhoto(BuildContext context, RecipeCreateLoadedState state) { - bool isLoading = false; - return GestureDetector( - child: Stack( - children: [ - if (!isLoading) - Container( - padding: EdgeInsets.symmetric( - horizontal: 40.h, - vertical: 80.v, - ), - decoration: AppDecoration.fillGray.copyWith( - borderRadius: BorderRadiusStyle.roundedBorder5, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: EdgeInsets.only( - left: 7.h, - top: 5.v, + refreshPage(BuildContext context) { + return () async { + context.read().add(ReloadScreen()); + }; + } + + _buildAddPhoto(BuildContext context, RecipeCreateLoadedState state) { + bool isLoading = false; + return GestureDetector( + child: Stack( + children: [ + if (!isLoading) + Container( + padding: EdgeInsets.symmetric( + horizontal: 40.h, + vertical: 80.v, + ), + decoration: AppDecoration.fillGray.copyWith( + borderRadius: BorderRadiusStyle.roundedBorder5, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: EdgeInsets.only( + left: 7.h, + top: 5.v, + ), + child: Text( + "Добавить фото", + selectionColor: appTheme.blueA400, + ), ), - child: Text( - "Добавить фото", - selectionColor: appTheme.blueA400, - ), - ), - Spacer(), - Icon( - Icons.photo_library, - color: appTheme.blueGray300, - ) - ], + Spacer(), + Icon( + Icons.photo_library, + color: appTheme.blueGray300, + ) + ], + ), ), - ), - /*if (isLoading) + /*if (isLoading) Container( margin: EdgeInsets.symmetric(horizontal: 10.h), padding: EdgeInsets.symmetric( @@ -216,161 +226,166 @@ _buildAddPhoto(BuildContext context, RecipeCreateLoadedState state) { .blueGray300), // Замените на свой прелоадер при необходимости ), ),*/ - ], - ), - ); -} - -_buildStep(BuildContext context, RecipeCreateLoadedState state, int i) { - bool isLastStep = i == state.steps!.length - 1; - bool isFirstStep = i == 0; - - return Container( - margin: EdgeInsets.only( - left: 16.h, - right: 16.h, - bottom: 10.h, - ), - padding: EdgeInsets.all(12.h), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: const BorderRadius.all(Radius.circular(12)), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 2, - blurRadius: 5, - offset: Offset(0, 3), - ), - ], - ), - child: Column(mainAxisSize: MainAxisSize.min, children: [ - Align( - alignment: Alignment.centerLeft, - child: Text( - "Шаг ${i + 1}", - style: CustomTextStyles.titleMediumSemiBold, - ), + ], ), - SizedBox(height: 8.0), - CustomTextFormField( - controller: state.description, - hintText: "Введите описание шага", - maxLines: 4, - fillColor: appTheme.gray50, - borderDecoration: OutlineInputBorder( - borderRadius: BorderRadius.circular(5.h), - borderSide: BorderSide( - color: appTheme.gray50, - width: 1, + ); + } + + _buildStep(BuildContext context, RecipeCreateLoadedState state, int i) { + bool isLastStep = i == state.steps!.length - 1; + bool isFirstStep = i == 0; + + return Container( + margin: EdgeInsets.only( + left: 16.h, + right: 16.h, + bottom: 10.h, + ), + padding: EdgeInsets.all(12.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(12)), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), ), - ), + ], ), - SizedBox(height: 16.0), - _buildAddPhoto(context, state), - if (!isFirstStep) - Row( - children: [ - Flexible( - flex: 5, - child: CustomOutlinedButton( - text: "Добавить шаг", - buttonStyle: CustomButtonStyles.outlinePrimaryTL15, - ), - ), - SizedBox(width: 8), // Add some spacing between the buttons - Flexible( - flex: 1, - child: CustomOutlinedButton( - text: "х", - onPressed: () => Navigator.pushNamed( - context, - AppRoutes.blogPostCreateScreen, - ), - buttonStyle: CustomButtonStyles.outlinePrimaryTL15, - ), - ), - ], - ), - if (isLastStep) // Добавляем кнопку "Добавить шаг" только для последнего шага - SizedBox(height: 16.0), - CustomOutlinedButton( - text: "Добавить шаг", - buttonStyle: CustomButtonStyles.outlinePrimaryTL15, - ), - ]), - ); -} - -_buildAdditional(BuildContext context, RecipeCreateLoadedState state) { - return Container( - margin: EdgeInsets.only( - left: 16.h, - right: 16.h, - bottom: 10.h, - ), - padding: EdgeInsets.all(12.h), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: const BorderRadius.all(Radius.circular(12)), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 2, - blurRadius: 5, - offset: Offset(0, 3), - ), - ], - ), - child: Column(mainAxisSize: MainAxisSize.min, children: [ - Align(alignment: Alignment.centerLeft, child: Text("Теги")), - CustomTextFormField( - controller: state.title, - hintText: "Введите теги, через запятую", - fillColor: appTheme.gray50, - borderDecoration: OutlineInputBorder( - borderRadius: BorderRadius.circular(5.h), - borderSide: BorderSide( - color: appTheme.gray50, - width: 1, - ), - ), - ), - SizedBox(height: 8.0), - Align( + child: Column(mainAxisSize: MainAxisSize.min, children: [ + Align( alignment: Alignment.centerLeft, - child: Text("Ссылка на видео (необязательно)")), - CustomTextFormField( - controller: state.description, - hintText: "YouTube", - fillColor: appTheme.gray50, - borderDecoration: OutlineInputBorder( - borderRadius: BorderRadius.circular(5.h), - borderSide: BorderSide( - color: appTheme.gray50, - width: 1, + child: Text( + "Шаг ${i + 1}", + style: CustomTextStyles.titleMediumSemiBold, ), ), + SizedBox(height: 8.0), + CustomTextFormField( + controller: state.stepDescriptions?[i], + hintText: "Введите описание шага", + maxLines: 4, + fillColor: appTheme.gray50, + borderDecoration: OutlineInputBorder( + borderRadius: BorderRadius.circular(5.h), + borderSide: BorderSide( + color: appTheme.gray50, + width: 1, + ), + ), + ), + SizedBox(height: 16.0), + _buildAddPhoto(context, state), + SizedBox(height: 16.0), + if (isFirstStep && isLastStep) + CustomOutlinedButton( + onPressed: () => context.read().add(AddStep()), + text: "Добавить шаг", + buttonStyle: CustomButtonStyles.outlinePrimaryTL15, + ), + if (isLastStep && !isFirstStep) // Добавляем кнопку "Добавить шаг" только для последнего шага + Column( + children: [ + SizedBox(height: 16.0), + Row( + children: [ + Flexible( + flex: 5, + child: CustomOutlinedButton( + onPressed: () => context.read().add(AddStep()), + text: "Добавить шаг", + buttonStyle: CustomButtonStyles.outlinePrimaryTL15, + ), + ), + SizedBox(width: 8), // Add some spacing between the buttons + Flexible( + flex: 1, + child: CustomOutlinedButton( + text: "х", + onPressed: () => context.read().add(RemoveStep(i)), + buttonStyle: CustomButtonStyles.outlinePrimaryTL15, + ), + ), + ], + ), + ], + ), + if (!isLastStep && !isFirstStep) + CustomOutlinedButton( + onPressed: () => context.read().add(RemoveStep(i)), + text: "x", + buttonStyle: CustomButtonStyles.outlinePrimaryTL15, + ), + ]), + ); + } + + _buildAdditional(BuildContext context, RecipeCreateLoadedState state) { + return Container( + margin: EdgeInsets.only( + left: 16.h, + right: 16.h, + bottom: 10.h, ), - SizedBox(height: 16.0), - CustomOutlinedButton( - text: "Добавить рецепт", - buttonStyle: CustomButtonStyles.outlinePrimaryTL15, + padding: EdgeInsets.all(12.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(12)), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), + ), + ], ), - ]), - ); -} + child: Column(mainAxisSize: MainAxisSize.min, children: [ + Align(alignment: Alignment.centerLeft, child: Text("Теги")), + CustomTextFormField( + controller: state.tags, + hintText: "Введите теги, через запятую", + fillColor: appTheme.gray50, + borderDecoration: OutlineInputBorder( + borderRadius: BorderRadius.circular(5.h), + borderSide: BorderSide( + color: appTheme.gray50, + width: 1, + ), + ), + ), + SizedBox(height: 8.0), + Align(alignment: Alignment.centerLeft, child: Text("Ссылка на видео (необязательно)")), + CustomTextFormField( + controller: state.video_url, + hintText: "YouTube", + fillColor: appTheme.gray50, + borderDecoration: OutlineInputBorder( + borderRadius: BorderRadius.circular(5.h), + borderSide: BorderSide( + color: appTheme.gray50, + width: 1, + ), + ), + ), + SizedBox(height: 16.0), + CustomOutlinedButton( + text: "Добавить рецепт", + buttonStyle: CustomButtonStyles.outlinePrimaryTL15, + ), + ]), + ); + } -_buildBottomBar(BuildContext context) { - return CustomBottomBar(onChanged: (BottomBarEnum type) {}); -} + Function onTapBack(BuildContext context) { + return () { + onTapArrowLeft(context); + }; + } -Function onTapBack(BuildContext context) { - return () { - onTapArrowLeft(context); - }; -} - -onTapArrowLeft(BuildContext context) { - NavigatorService.goBack(); + onTapArrowLeft(BuildContext context) { + GoRouter.of(context).pop(); + } } diff --git a/lib/presentation/recipe/recipe_edit_screen/bloc/recipe_edit_bloc.dart b/lib/presentation/recipe/recipe_edit_screen/bloc/recipe_edit_bloc.dart index 8cafde3..9c982dd 100644 --- a/lib/presentation/recipe/recipe_edit_screen/bloc/recipe_edit_bloc.dart +++ b/lib/presentation/recipe/recipe_edit_screen/bloc/recipe_edit_bloc.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:eatvio_app/app_router.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; @@ -8,7 +9,10 @@ import 'package:http/http.dart' as http; import 'dart:convert'; import '/core/app_export.dart'; import 'package:eatvio_app/presentation/recipe/recipe_edit_screen/models/recipe_edit_model.dart'; +import 'package:go_router/go_router.dart'; + part 'recipe_edit_event.dart'; + part 'recipe_edit_state.dart'; /// A bloc that manages the state of a RecipeEdit according to the event that is dispatched to it. @@ -16,6 +20,9 @@ class RecipeEditBloc extends Bloc { RecipeEditBloc(RecipeEditState initialState) : super(initialState) { on(_onInitialize); on(_editRecipe); + on(_addStep); + on(_removeStep); + on(_reloadScreen); } _onInitialize( @@ -26,9 +33,7 @@ class RecipeEditBloc extends Bloc { final box = await Hive.openBox('token'); final token = box.get('token'); if (token == null) { - NavigatorService.popAndPushNamed( - AppRoutes.authScreen, - ); + AppRouter.navigatorKey.currentContext?.goNamed('authScreen'); } final String? recipeID = state.recipeModelObj?.recipeID; final response = await http.get( @@ -44,21 +49,47 @@ class RecipeEditBloc extends Bloc { var currentState = state as RecipeEditLoadedState; emit(currentState.copyWith( recipeModelObj: currentState.recipeModelObj, - recipe: currentState.recipe, title: currentState.title, + token: currentState.token, + recipe: currentState.recipe, description: currentState.description, - video_url: currentState.video_url)); + video_url: currentState.video_url, + steps: currentState.steps, + stepDescriptions: currentState.stepDescriptions, + tags: currentState.tags)); } else { + final Map defaultStep = { + 'description': '', + 'image': '', + }; + + List steps = data['steps']; + List stepDescriptions = []; + + if (data['steps'] == null) { + stepDescriptions.add(TextEditingController()); + data['steps'].add(defaultStep); + } else { + for (var step in steps) { + stepDescriptions.add(TextEditingController(text: step['description'])); + } + } + emit(RecipeEditLoadedState( - recipe: data, - title: TextEditingController(text: data['title']), - description: TextEditingController(text: data['description']), - video_url: TextEditingController(text: data['video_url']))); + token: token, + recipe: data, + title: TextEditingController(text: data['title']), + description: TextEditingController(text: data['description']), + video_url: TextEditingController(text: data['video_url']), + tagsController: TextEditingController(), + tags: await _fetchTags(data['id']), + stepDescriptions: stepDescriptions, + steps: steps, + )); } } else { // Один или несколько ключевых полей отсутствуют, обрабатываем ошибку - emit(RecipeEditErrorState( - 'Некоторые необходимые данные отсутствуют.')); + emit(RecipeEditErrorState('Некоторые необходимые данные отсутствуют.')); } } else { // Статус ответа не равен 200, обрабатываем ошибку @@ -70,8 +101,7 @@ class RecipeEditBloc extends Bloc { } } - FutureOr _editRecipe( - EditRecipe event, Emitter emit) async { + FutureOr _editRecipe(EditRecipe event, Emitter emit) async { if (state is RecipeEditLoadedState) { var currentState = state as RecipeEditLoadedState; @@ -83,15 +113,47 @@ class RecipeEditBloc extends Bloc { ); if (response.statusCode == 200) { - NavigatorService.pushNamed(AppRoutes.recipeScreen, - arguments: currentState.recipe?['id'].toString()); + AppRouter.navigatorKey.currentContext?.goNamed( + 'recipeScreen', + extra: currentState.recipe?['id'].toString(), + ); } else { - print( - 'Failed to edit blog post. Status Code: ${response.statusCode} ${response.body}'); + print('Failed to edit blog post. Status Code: ${response.statusCode} ${response.body}'); } } } + FutureOr _addStep(AddStep event, Emitter emit) async { + if (state is RecipeEditLoadedState) { + var currentState = state as RecipeEditLoadedState; + + List? updatedSteps = currentState.steps; + List? updatedStepDescriptions = currentState.stepDescriptions; + + updatedSteps!.add({ + 'description': '', + 'image': '', + }); + updatedStepDescriptions!.add(TextEditingController()); + emit(currentState.copyWith(steps: updatedSteps, stepDescriptions: updatedStepDescriptions)); + await _reloadScreen(event, emit); + } + } + + FutureOr _removeStep(RemoveStep event, Emitter emit) async { + if (state is RecipeEditLoadedState) { + var currentState = state as RecipeEditLoadedState; + + List? updatedSteps = currentState.steps; + List? updatedStepDescriptions = currentState.stepDescriptions; + + updatedSteps!.removeAt(event.id); + updatedStepDescriptions!.removeAt(event.id); + emit(currentState.copyWith(steps: updatedSteps, stepDescriptions: updatedStepDescriptions)); + await _reloadScreen(event, emit); + } + } + FutureOr sendRecipe( Map? recipe, String? title, @@ -123,4 +185,44 @@ class RecipeEditBloc extends Bloc { return response; } + + _fetchTags(int id) async { + final box = await Hive.openBox('token'); + final token = box.get('token'); + + if (token == null) { + AppRouter.navigatorKey.currentContext?.goNamed('authScreen'); + } + + final response = await http.get( + Uri.parse(dotenv.env['SERVER_ROOT']! + '/recipe/getTags/$id'), + headers: { + 'Authorization': 'Bearer $token', + }, + ); + final data = jsonDecode(response.body); + return data; + } + + FutureOr _reloadScreen(RecipeEditEvent? event, Emitter emit) async { + print('Reloading..'); + final box = await Hive.openBox('token'); + final token = box.get('token'); + if (token == null) { + AppRouter.navigatorKey.currentContext?.goNamed('authScreen'); + } + if (state is RecipeEditLoadedState) { + var currentState = state as RecipeEditLoadedState; + emit(currentState.copyWith( + recipeModelObj: currentState.recipeModelObj, + recipe: currentState.recipe, + title: currentState.title, + token: currentState.token, + description: currentState.description, + video_url: currentState.video_url, + steps: currentState.steps, + stepDescriptions: currentState.stepDescriptions, + tags: currentState.tags)); + } + } } diff --git a/lib/presentation/recipe/recipe_edit_screen/bloc/recipe_edit_event.dart b/lib/presentation/recipe/recipe_edit_screen/bloc/recipe_edit_event.dart index bcbe219..c6d49d3 100644 --- a/lib/presentation/recipe/recipe_edit_screen/bloc/recipe_edit_event.dart +++ b/lib/presentation/recipe/recipe_edit_screen/bloc/recipe_edit_event.dart @@ -19,3 +19,22 @@ class EditRecipe extends RecipeEditEvent { @override List get props => []; } + +class AddStep extends RecipeEditEvent { + AddStep(); + @override + List get props => []; +} + +class RemoveStep extends RecipeEditEvent { + RemoveStep(this.id); + final int id; + @override + List get props => [id]; +} + +class ReloadScreen extends RecipeEditEvent { + ReloadScreen(); + @override + List get props => []; +} diff --git a/lib/presentation/recipe/recipe_edit_screen/bloc/recipe_edit_state.dart b/lib/presentation/recipe/recipe_edit_screen/bloc/recipe_edit_state.dart index ec00127..af03fcf 100644 --- a/lib/presentation/recipe/recipe_edit_screen/bloc/recipe_edit_state.dart +++ b/lib/presentation/recipe/recipe_edit_screen/bloc/recipe_edit_state.dart @@ -24,32 +24,52 @@ class RecipeEditState extends Equatable { class RecipeEditLoadedState extends RecipeEditState { RecipeEditLoadedState( {this.recipeModelObj, + this.token, this.recipe, this.title, this.description, - this.video_url}); + this.video_url, + this.tagsController, + this.stepDescriptions, + this.tags, + this.steps}); RecipeEditModel? recipeModelObj; Map? recipe; + String? token; TextEditingController? title; TextEditingController? description; TextEditingController? video_url; + TextEditingController? tagsController; + List? steps; + List? tags; + List? stepDescriptions; @override List get props => [recipeModelObj, recipe, title, description, video_url]; RecipeEditLoadedState copyWith({ RecipeEditModel? recipeModelObj, Map? recipe, + String? token, TextEditingController? title, TextEditingController? description, TextEditingController? video_url, + TextEditingController? tagsController, + List? steps, + List? tags, + List? stepDescriptions, }) { return RecipeEditLoadedState( recipeModelObj: recipeModelObj ?? this.recipeModelObj, + token: token ?? this.token, recipe: recipe ?? this.recipe, title: title ?? this.title, description: description ?? this.description, video_url: video_url ?? this.video_url, + tagsController: tagsController ?? this.tagsController, + steps: steps ?? this.steps, + tags: tags ?? this.tags, + stepDescriptions: stepDescriptions ?? this.stepDescriptions, ); } } diff --git a/lib/presentation/recipe/recipe_edit_screen/recipe_edit_screen.dart b/lib/presentation/recipe/recipe_edit_screen/recipe_edit_screen.dart index 63a43e6..c9ce266 100644 --- a/lib/presentation/recipe/recipe_edit_screen/recipe_edit_screen.dart +++ b/lib/presentation/recipe/recipe_edit_screen/recipe_edit_screen.dart @@ -1,19 +1,24 @@ +import 'package:go_router/go_router.dart'; + import 'bloc/recipe_edit_bloc.dart'; import 'models/recipe_edit_model.dart'; -import 'package:eatvio_app/core/app_export.dart'; import 'package:flutter/material.dart'; -import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:eatvio_app/core/app_export.dart'; +import 'package:eatvio_app/widgets/app_bar/custom_app_bar.dart'; +import 'package:eatvio_app/widgets/app_bar/appbar_leading_image.dart'; +import 'package:eatvio_app/widgets/app_bar/appbar_subtitle.dart'; +import 'package:eatvio_app/widgets/custom_text_form_field.dart'; +import 'package:eatvio_app/widgets/custom_outlined_button.dart'; class RecipeEditScreen extends StatelessWidget { const RecipeEditScreen({Key? key}) : super(key: key); + static Widget builder(BuildContext context) { final recipeID = ModalRoute.of(context)!.settings.arguments; return BlocProvider( - create: (context) => RecipeEditBloc(RecipeEditState( - recipeModelObj: RecipeEditModel(recipeID: recipeID.toString()))) - ..add(RecipeEditInitialEvent()), + create: (context) => RecipeEditBloc(RecipeEditState(recipeModelObj: RecipeEditModel(recipeID: recipeID.toString())))..add(RecipeEditInitialEvent()), child: RecipeEditScreen()); } @@ -41,78 +46,388 @@ class RecipeEditScreen extends StatelessWidget { _buildRecipeEdit(BuildContext context, RecipeEditLoadedState state) { mediaQueryData = MediaQuery.of(context); - return Scaffold( - resizeToAvoidBottomInset: false, - body: SafeArea( - child: Container( - width: double.maxFinite, - decoration: AppDecoration.fillGray, - child: ListView(children: [ - Container( - margin: EdgeInsets.only( - left: 16.h, - right: 20.h, - bottom: 10.h, - ), - padding: EdgeInsets.all(12.h), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: const BorderRadius.all(Radius.circular(12)), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 2, - blurRadius: 5, - offset: Offset(0, 3), - ), - ], - ), - child: Column(mainAxisSize: MainAxisSize.min, children: [ - CachedNetworkImage( - imageUrl: dotenv.env['STORAGE_URL']! + - (state.recipe?['image'] ?? ''), - imageBuilder: (context, imageProvider) => CircleAvatar( - backgroundImage: imageProvider, - radius: 25, - ), - placeholder: (context, url) => - CircularProgressIndicator(), - errorWidget: (context, url, error) => CircleAvatar( - child: Icon(Icons.person), - backgroundColor: Colors.white, - radius: 25, - ), - ), - SizedBox(height: 16.0), - TextFormField( - controller: state.title, - decoration: InputDecoration(labelText: 'Заголовок'), - ), - SizedBox(height: 16.0), - TextFormField( - controller: state.description, - decoration: InputDecoration(labelText: 'Описание'), - ), - SizedBox(height: 16.0), - TextFormField( - controller: state.video_url, - decoration: InputDecoration(labelText: 'Ссылка'), - ), - SizedBox(height: 16.0), - // Здесь нужно добавить виджет для загрузки картинки - ElevatedButton( - onPressed: () => - context.read().add(EditRecipe()), - child: Text('Сохранить'), - ) - ]), - ) - ]), + List steps = []; + if (state.steps != null) { + for (int i = 0; i < state.steps!.length; i++) { + steps.add(_buildStep(context, state, i)); + } + } + + return RefreshIndicator( + onRefresh: refreshPage(context), + child: Scaffold( + appBar: AppBar( + toolbarHeight: 0, + automaticallyImplyLeading: false, + ), + resizeToAvoidBottomInset: false, + body: SafeArea( + child: Container( + width: double.maxFinite, + decoration: BoxDecoration(color: Colors.grey[200]), + child: ListView( + children: [ + _buldAppBar(context), + SizedBox(height: 20.v), + _buildRecipeInfo(state, context), + SizedBox(height: 20.v), + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: steps, + ), + SizedBox(height: 20.v), + _buildAdditional(context, state) + ], ), - ), bottomNavigationBar: _buildBottomBar(context)); + ), + ), + ), + ); + } + + _buldAppBar(BuildContext context) { + return Container( + margin: EdgeInsets.all(16.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(12)), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), + ), + ], + ), + child: CustomAppBar( + height: 54.v, + leadingWidth: 28.h, + leading: AppbarLeadingImage( + imagePath: ImageConstant.imgArrowLeft, + margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v), + onTap: onTapBack(context), + ), + title: Align( + alignment: Alignment.center, + child: AppbarSubtitle( + text: "Редактирование рецепта", + )), + ), + ); + } + + _buildRecipeInfo(RecipeEditLoadedState state, BuildContext context) { + return Container( + margin: EdgeInsets.only( + left: 16.h, + right: 16.h, + bottom: 10.h, + ), + padding: EdgeInsets.all(12.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(12)), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), + ), + ], + ), + child: Column(mainAxisSize: MainAxisSize.min, children: [ + SizedBox(height: 16.0), + Align(alignment: Alignment.centerLeft, child: Text("Изображение")), + if (state.recipe?['image'] != null) + CachedNetworkImage( + imageUrl: dotenv.env['STORAGE_URL']! + (state.recipe?['image']), + width: double.infinity, + // Установите ширину равной ширине родительского элемента + height: 200.0, + fit: BoxFit.cover, + placeholder: (context, url) => CircularProgressIndicator(), + errorWidget: (context, url, error) => Icon(Icons.error), + ) + else + _buildAddPhoto(context, state), + SizedBox(height: 16.0), + Align(alignment: Alignment.centerLeft, child: Text("Название рецепта")), + CustomTextFormField( + controller: state.title, + hintText: "Введите название рецепта", + fillColor: appTheme.gray50, + borderDecoration: OutlineInputBorder( + borderRadius: BorderRadius.circular(5.h), + borderSide: BorderSide( + color: appTheme.gray50, + width: 1, + ), + ), + ), + SizedBox(height: 16.0), + Align(alignment: Alignment.centerLeft, child: Text("Описание")), + CustomTextFormField( + controller: state.description, + hintText: "Введите описание рецепта", + maxLines: 4, + fillColor: appTheme.gray50, + borderDecoration: OutlineInputBorder( + borderRadius: BorderRadius.circular(5.h), + borderSide: BorderSide( + color: appTheme.gray50, + width: 1, + ), + ), + ), + SizedBox(height: 16.0), + ]), + ); + } + + refreshPage(BuildContext context) { + return () async { + context.read().add(ReloadScreen()); + }; + } + + _buildAddPhoto(BuildContext context, RecipeEditLoadedState state) { + bool isLoading = false; + return GestureDetector( + child: Stack( + children: [ + if (!isLoading) + Container( + padding: EdgeInsets.symmetric( + horizontal: 40.h, + vertical: 80.v, + ), + decoration: AppDecoration.fillGray.copyWith( + borderRadius: BorderRadiusStyle.roundedBorder5, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: EdgeInsets.only( + left: 7.h, + top: 5.v, + ), + child: Text( + "Добавить фото", + selectionColor: appTheme.blueA400, + ), + ), + Spacer(), + Icon( + Icons.photo_library, + color: appTheme.blueGray300, + ) + ], + ), + ), + /*if (isLoading) + Container( + margin: EdgeInsets.symmetric(horizontal: 10.h), + padding: EdgeInsets.symmetric( + horizontal: 40.h, + vertical: 80.v, + ), + decoration: AppDecoration.fillGray.copyWith( + borderRadius: BorderRadiusStyle.roundedBorder5, + ), + child: Center( + child: CircularProgressIndicator( + color: appTheme + .blueGray300), // Замените на свой прелоадер при необходимости + ), + ),*/ + ], + ), + ); + } + + _buildStep(BuildContext context, RecipeEditLoadedState state, int i) { + bool isLastStep = i == state.steps!.length - 1; + bool isFirstStep = i == 0; + + return Container( + margin: EdgeInsets.only( + left: 16.h, + right: 16.h, + bottom: 10.h, + ), + padding: EdgeInsets.all(12.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(12)), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), + ), + ], + ), + child: Column(mainAxisSize: MainAxisSize.min, children: [ + Align( + alignment: Alignment.centerLeft, + child: Text( + "Шаг ${i + 1}", + style: CustomTextStyles.titleMediumSemiBold, + ), + ), + SizedBox(height: 8.0), + CustomTextFormField( + controller: state.stepDescriptions?[i], + hintText: "Введите описание шага", + maxLines: 4, + fillColor: appTheme.gray50, + borderDecoration: OutlineInputBorder( + borderRadius: BorderRadius.circular(5.h), + borderSide: BorderSide( + color: appTheme.gray50, + width: 1, + ), + ), + ), + SizedBox(height: 16.0), + if (state.steps?[i]['image'] != null) + CachedNetworkImage( + imageUrl: dotenv.env['STORAGE_URL']! + (state.steps?[i]['image']), + width: double.infinity, + // Установите ширину равной ширине родительского элемента + height: 200.0, + // Задайте постоянную высоту элемента + fit: BoxFit.cover, + placeholder: (context, url) => CircularProgressIndicator(), + errorWidget: (context, url, error) => Icon(Icons.error), + ) + else + _buildAddPhoto(context, state), + SizedBox(height: 16.0), + if (isFirstStep && isLastStep) + CustomOutlinedButton( + onPressed: () => context.read().add(AddStep()), + text: "Добавить шаг", + buttonStyle: CustomButtonStyles.outlinePrimaryTL15, + ), + if (isLastStep && !isFirstStep) // Добавляем кнопку "Добавить шаг" только для последнего шага + Column( + children: [ + SizedBox(height: 16.0), + Row( + children: [ + Flexible( + flex: 5, + child: CustomOutlinedButton( + onPressed: () => context.read().add(AddStep()), + text: "Добавить шаг", + buttonStyle: CustomButtonStyles.outlinePrimaryTL15, + ), + ), + SizedBox(width: 8), // Add some spacing between the buttons + Flexible( + flex: 1, + child: CustomOutlinedButton( + text: "х", + onPressed: () => context.read().add(RemoveStep(i)), + buttonStyle: CustomButtonStyles.outlinePrimaryTL15, + ), + ), + ], + ), + ], + ), + if (!isLastStep && !isFirstStep) + CustomOutlinedButton( + onPressed: () => context.read().add(RemoveStep(i)), + text: "x", + buttonStyle: CustomButtonStyles.outlinePrimaryTL15, + ), + ]), + ); + } + + _buildAdditional(BuildContext context, RecipeEditLoadedState state) { + return Container( + margin: EdgeInsets.only( + left: 16.h, + right: 16.h, + bottom: 10.h, + ), + padding: EdgeInsets.all(12.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(12)), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), + ), + ], + ), + child: Column(mainAxisSize: MainAxisSize.min, children: [ + Align(alignment: Alignment.centerLeft, child: Text("Теги")), + _buildEditableTags(state.tags), + SizedBox(height: 8.0), + Align(alignment: Alignment.centerLeft, child: Text("Ссылка на видео (необязательно)")), + CustomTextFormField( + controller: state.video_url, + hintText: "YouTube", + fillColor: appTheme.gray50, + borderDecoration: OutlineInputBorder( + borderRadius: BorderRadius.circular(5.h), + borderSide: BorderSide( + color: appTheme.gray50, + width: 1, + ), + ), + ), + SizedBox(height: 16.0), + CustomOutlinedButton( + text: "Добавить рецепт", + buttonStyle: CustomButtonStyles.outlinePrimaryTL15, + ), + ]), + ); + } + + _buildEditableTags(List? tags) { + String initialText = tags!.map((tag) => tag['name']).join(', '); + + return CustomTextFormField( + initialValue: initialText, + fillColor: appTheme.gray50, + borderDecoration: OutlineInputBorder( + borderRadius: BorderRadius.circular(5.h), + borderSide: BorderSide( + color: appTheme.gray50, + width: 1, + ), + ), + onChanged: _trimTags, + ); + } + + void _trimTags(newValue) { + List newTagNames = newValue!.split(',').map((tag) => tag.trim()).toList(); + List> newTags = newTagNames.map((tagName) => {'name': tagName}).toList(); + print(newTags); + } + + Function onTapBack(BuildContext context) { + return () { + onTapArrowLeft(context); + }; + } + + onTapArrowLeft(BuildContext context) { + GoRouter.of(context).pop(); } } - -_buildBottomBar(BuildContext context) { - return CustomBottomBar(onChanged: (BottomBarEnum type) {}); -} diff --git a/lib/presentation/recipe/recipe_select_screen/bloc/recipe_select_bloc.dart b/lib/presentation/recipe/recipe_select_screen/bloc/recipe_select_bloc.dart index fc9a96f..a7b0d37 100644 --- a/lib/presentation/recipe/recipe_select_screen/bloc/recipe_select_bloc.dart +++ b/lib/presentation/recipe/recipe_select_screen/bloc/recipe_select_bloc.dart @@ -1,6 +1,6 @@ -//import 'package:eatvio_app/presentation/auth/auth_screen/auth_screen.dart'; import 'dart:async'; +import 'package:eatvio_app/app_router.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import '/core/app_export.dart'; @@ -10,6 +10,7 @@ import 'dart:convert'; import 'package:hive/hive.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:go_router/go_router.dart'; part 'recipe_select_event.dart'; part 'recipe_select_state.dart'; @@ -28,9 +29,7 @@ class RecipeSelectBloc extends Bloc { final box = await Hive.openBox('token'); final token = box.get('token'); if (token == null) { - NavigatorService.popAndPushNamed( - AppRoutes.authScreen, - ); // Если токен есть, запускаем приложение + AppRouter.navigatorKey.currentContext?.goNamed('authScreen'); } final response = await http.get( Uri.parse(dotenv.env['SERVER_ROOT']! + '/recipe/index'), diff --git a/lib/presentation/recipe/recipe_select_screen/recipe_select_screen.dart b/lib/presentation/recipe/recipe_select_screen/recipe_select_screen.dart index 0c143c6..2a3a108 100644 --- a/lib/presentation/recipe/recipe_select_screen/recipe_select_screen.dart +++ b/lib/presentation/recipe/recipe_select_screen/recipe_select_screen.dart @@ -1,10 +1,11 @@ +import 'package:go_router/go_router.dart'; + import 'bloc/recipe_select_bloc.dart'; import 'models/recipe_select_model.dart'; import 'package:eatvio_app/core/app_export.dart'; import 'package:eatvio_app/widgets/app_bar/appbar_subtitle.dart'; import 'package:eatvio_app/widgets/app_bar/appbar_leading_image.dart'; import 'package:eatvio_app/widgets/app_bar/custom_app_bar.dart'; -import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter/material.dart'; @@ -12,9 +13,7 @@ import 'package:flutter/material.dart'; class RecipeSelectScreen extends StatelessWidget { static Widget builder(BuildContext context) { return BlocProvider( - create: (context) => RecipeSelectBloc( - RecipeSelectState(myProfileModelObj: RecipeSelectModel())) - ..add(RecipeSelectInitialEvent()), + create: (context) => RecipeSelectBloc(RecipeSelectState(myProfileModelObj: RecipeSelectModel()))..add(RecipeSelectInitialEvent()), child: RecipeSelectScreen()); } @@ -22,93 +21,92 @@ class RecipeSelectScreen extends StatelessWidget { return BlocBuilder( builder: (context, state) { if (state is RecipeSelectInitialState) { - // Это начальное состояние, данные ещё не загружены. - return CircularProgressIndicator( - color: Colors.white, - backgroundColor: Colors.white, + return Scaffold( + body: Center( + child: CircularProgressIndicator(), + ), ); } else if (state is RecipeSelectLoadedState) { // Данные загружены, можно строить экран. return _buildRecipeScreen(context, state); } else { - // Другие состояния, если они есть. - return Container(); // Пустой контейнер для необработанных состояний. + return Scaffold( + body: Center( + child: CircularProgressIndicator(), + ), + ); } }, ); } _buildRecipeScreen(BuildContext context, RecipeSelectLoadedState state) { - mediaQueryData = MediaQuery.of(context); - return Scaffold( - backgroundColor: Colors.grey[200], - resizeToAvoidBottomInset: true, - body: SafeArea( - child: SingleChildScrollView( - child: Container( - width: double.maxFinite, - decoration: BoxDecoration(color: Colors.grey[200]), - child: Padding( - padding: EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - SizedBox(height: 10), - Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: const BorderRadius.all(Radius.circular(12)), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 2, - blurRadius: 5, - offset: Offset(0, 3), - ), - ], - ), - child: CustomAppBar( - height: 54.v, - leadingWidth: 28.h, - leading: AppbarLeadingImage( - imagePath: ImageConstant.imgArrowLeft, - margin: EdgeInsets.only( - left: 16.h, top: 15.v, bottom: 15.v), - onTap: onTapBack(context), + return Scaffold( + appBar: AppBar( + toolbarHeight: 0, + automaticallyImplyLeading: false, + ), + backgroundColor: Colors.grey[200], + resizeToAvoidBottomInset: true, + body: SingleChildScrollView( + child: Container( + width: double.maxFinite, + decoration: BoxDecoration(color: Colors.grey[200]), + child: Padding( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SizedBox(height: 10), + Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(12)), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), ), - title: Padding( - padding: EdgeInsets.only(left: 20.h), - child: Row( - children: [ - AppbarSubtitle( - text: "Рецепты", - margin: EdgeInsets.only( - left: 80.h, top: 12.v, bottom: 9.v), + ], + ), + child: CustomAppBar( + height: 54.v, + leadingWidth: 28.h, + leading: AppbarLeadingImage( + imagePath: ImageConstant.imgArrowLeft, + margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v), + onTap: () => GoRouter.of(context).pop(), + ), + title: Padding( + padding: EdgeInsets.only(left: 20.h), + child: Row( + children: [ + AppbarSubtitle( + text: "Рецепты", + margin: EdgeInsets.only(left: 80.h, top: 12.v, bottom: 9.v), + ), + Spacer(), + Padding( + padding: const EdgeInsets.only(right: 20.0), + child: GestureDetector( + onTap: () => GoRouter.of(context).goNamed('recipeCreateScreen'), + child: Icon(Icons.create_outlined), ), - Spacer(), - Padding( - padding: const EdgeInsets.only(right: 20.0), - child: GestureDetector( - onTap: () => Navigator.pushNamed( - context, - AppRoutes.recipeCreateScreen, - ), - child: Icon(Icons.create_outlined)), - ) - ], - ), + ) + ], ), ), ), - SizedBox(height: 20), - _buildRecipeList(context, state), - ], - ), + ), + SizedBox(height: 20), + _buildRecipeList(context, state), + ], ), ), - ), + ), ), - bottomNavigationBar: _buildBottomBar(context), ); } @@ -151,18 +149,17 @@ class RecipeSelectScreen extends StatelessWidget { style: CustomTextStyles.titleMediumSemiBold, ), subtitle: Text(post?['description'] ?? ''), - onTap: () => Navigator.pushNamed( - context, - AppRoutes.recipeScreen, - arguments: post?['id'].toString(), + onTap: () => GoRouter.of(context).goNamed( + 'recipeScreen', + extra: post?['id'].toString(), ), trailing: GestureDetector( - onTap: () => Navigator.pushNamed( - context, - AppRoutes.recipeEditScreen, - arguments: post?['id'].toString(), - ), - child: Icon(Icons.edit)), + onTap: () => GoRouter.of(context).goNamed( + 'recipeEditScreen', + extra: post?['id'].toString(), + ), + child: Icon(Icons.edit), + ), )); }, ); @@ -174,11 +171,7 @@ class RecipeSelectScreen extends StatelessWidget { }; } - _buildBottomBar(BuildContext context) { - return CustomBottomBar(onChanged: (BottomBarEnum type) {}); - } - onTapArrowLeft(BuildContext context) { - NavigatorService.goBack(); + GoRouter.of(context).pop(); } } diff --git a/lib/presentation/recipe/recipe_show_screen/bloc/recipe_show_bloc.dart b/lib/presentation/recipe/recipe_show_screen/bloc/recipe_show_bloc.dart index a65348e..a8b5e19 100644 --- a/lib/presentation/recipe/recipe_show_screen/bloc/recipe_show_bloc.dart +++ b/lib/presentation/recipe/recipe_show_screen/bloc/recipe_show_bloc.dart @@ -1,14 +1,20 @@ import 'dart:async'; +import 'package:eatvio_app/app_router.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:hive/hive.dart'; import 'package:http/http.dart' as http; +import 'package:intl/intl.dart'; import 'dart:convert'; import '/core/app_export.dart'; import 'package:eatvio_app/presentation/recipe/recipe_show_screen/models/recipe_show_model.dart'; +import 'package:eatvio_app/presentation/feed/blog_post_screen/models/comment_model.dart'; +import 'package:go_router/go_router.dart'; + part 'recipe_show_event.dart'; + part 'recipe_show_state.dart'; /// A bloc that manages the state of a Recipe according to the event that is dispatched to it. @@ -16,6 +22,7 @@ class RecipeBloc extends Bloc { RecipeBloc(RecipeState initialState) : super(initialState) { on(_onInitialize); on(_deleteEntry); + on(_createComment); } _onInitialize( @@ -26,9 +33,7 @@ class RecipeBloc extends Bloc { final box = await Hive.openBox('token'); final token = box.get('token'); if (token == null) { - NavigatorService.popAndPushNamed( - AppRoutes.authScreen, - ); + AppRouter.navigatorKey.currentContext?.goNamed('authScreen'); } final String? recipeID = state.recipeModelObj?.recipeID; final response = await http.get( @@ -44,9 +49,21 @@ class RecipeBloc extends Bloc { var currentState = state as RecipeLoadedState; emit(currentState.copyWith( recipeModelObj: currentState.recipeModelObj, - recipe: currentState.recipe)); + recipe: currentState.recipe, + token: currentState.token, + commentController: currentState.commentController, + comments: currentState.comments, + steps: currentState.steps, + author: currentState.author)); } else { - emit(RecipeLoadedState(recipe: data)); + emit(RecipeLoadedState( + token: token, + recipe: data, + steps: data['steps'], + author: await _fetchAuthor(data['id']), + comments: fillCommentList(await _fetchComments(data['id'])), + tags: await _fetchTags(data['id']), + commentController: TextEditingController())); } } else { // Один или несколько ключевых полей отсутствуют, обрабатываем ошибку @@ -62,14 +79,11 @@ class RecipeBloc extends Bloc { } } - FutureOr _deleteEntry( - DeleteEntry event, Emitter emit) async { + FutureOr _deleteEntry(DeleteEntry event, Emitter emit) async { final box = await Hive.openBox('token'); final token = box.get('token'); if (token == null) { - NavigatorService.popAndPushNamed( - AppRoutes.authScreen, - ); + AppRouter.navigatorKey.currentContext?.goNamed('authScreen'); } final int id = event.id; final response = await http.post( @@ -79,10 +93,109 @@ class RecipeBloc extends Bloc { }, ); if (response.statusCode == 200) { - NavigatorService.pushNamed(AppRoutes.recipeSelectScreen); + AppRouter.navigatorKey.currentContext?.goNamed('recipeSelectScreen'); } else { // Статус ответа не равен 200, обрабатываем ошибку emit(RecipeErrorState('Ошибка: ${response.statusCode}')); } } + + FutureOr _createComment(CreateComment event, Emitter emit) async { + if (state is RecipeLoadedState) { + var currentState = state as RecipeLoadedState; + // Отправляем данные по HTTP маршруту и ждем ответ + var response = await _sendCommentData(currentState.token, event.body, currentState.recipe?['id']); + if (response.statusCode == 200) { + var updatedComments = fillCommentList(await _fetchComments(currentState.recipe?['id'])); + var updatedState = currentState.copyWith( + comments: updatedComments, + commentController: TextEditingController(), // Clear the comment input field + ); + emit(updatedState); // Emit the updated state to trigger a screen update + } + } + } + + _fetchAuthor(int id) async { + final box = await Hive.openBox('token'); + final token = box.get('token'); + + if (token == null) { + AppRouter.navigatorKey.currentContext?.goNamed('authScreen'); + } + + final response = await http.get( + Uri.parse(dotenv.env['SERVER_ROOT']! + '/recipe/getAuthor/$id'), + headers: { + 'Authorization': 'Bearer $token', + }, + ); + final data = jsonDecode(response.body); + return data; + } + + Future _fetchComments(int id) async { + final box = await Hive.openBox('token'); + final token = box.get('token'); + if (token == null) { + AppRouter.navigatorKey.currentContext?.goNamed('authScreen'); + } + final response = await http.get( + Uri.parse(dotenv.env['SERVER_ROOT']! + '/recipe/getComments/$id'), + headers: { + 'Authorization': 'Bearer $token', + }, + ); + final data = jsonDecode(response.body); + return data; + } + + _fetchTags(int id) async { + final box = await Hive.openBox('token'); + final token = box.get('token'); + + if (token == null) { + AppRouter.navigatorKey.currentContext?.goNamed('authScreen'); + } + + final response = await http.get( + Uri.parse(dotenv.env['SERVER_ROOT']! + '/recipe/getTags/$id'), + headers: { + 'Authorization': 'Bearer $token', + }, + ); + final data = jsonDecode(response.body); + return data; + } + + List fillCommentList(List comments) { + List list = []; + for (int i = 0; i < comments.length; i++) { + list.add(CommentModel( + id: comments[i]['id'].toString(), + userImage: comments[i]['profile_photo_path'], + username: comments[i]['name'], + dateText: DateFormat('dd.MM').format(DateTime.parse(comments[i]['updated_at'])), + greetingText: comments[i]['body'])); + } + return list; + } + + Future _sendCommentData(String? token, String body, int id) async { + String url = dotenv.env['SERVER_ROOT']! + '/recipe/createComment/$id'; + final Map credentials = { + 'id': id, + 'body': body, + }; + + var response = await http.post(Uri.parse(url), + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $token', + }, + body: json.encode(credentials), + encoding: utf8); + + return response; + } } diff --git a/lib/presentation/recipe/recipe_show_screen/bloc/recipe_show_event.dart b/lib/presentation/recipe/recipe_show_screen/bloc/recipe_show_event.dart index 6257e89..7510c3a 100644 --- a/lib/presentation/recipe/recipe_show_screen/bloc/recipe_show_event.dart +++ b/lib/presentation/recipe/recipe_show_screen/bloc/recipe_show_event.dart @@ -21,3 +21,10 @@ class DeleteEntry extends RecipeEvent { @override List get props => [id]; } + +class CreateComment extends RecipeEvent { + CreateComment(this.body); + final String body; + @override + List get props => [body]; +} diff --git a/lib/presentation/recipe/recipe_show_screen/bloc/recipe_show_state.dart b/lib/presentation/recipe/recipe_show_screen/bloc/recipe_show_state.dart index e8c1807..fc3a9f4 100644 --- a/lib/presentation/recipe/recipe_show_screen/bloc/recipe_show_state.dart +++ b/lib/presentation/recipe/recipe_show_screen/bloc/recipe_show_state.dart @@ -22,17 +22,52 @@ class RecipeState extends Equatable { } class RecipeLoadedState extends RecipeState { - RecipeLoadedState({this.recipeModelObj, this.recipe}); + RecipeLoadedState( + {this.recipeModelObj, + this.recipe, + this.author, + this.comments, + this.token, + this.commentController, + this.tags, + this.steps}); RecipeModel? recipeModelObj; Map? recipe; + Map? author; + List? comments; + TextEditingController? commentController; + List? steps; + List? tags; + String? token; @override - List get props => [recipeModelObj, recipe]; + List get props => [ + recipeModelObj, + recipe, + author, + comments, + token, + commentController, + steps + ]; RecipeLoadedState copyWith( - {RecipeModel? recipeModelObj, Map? recipe}) { + {RecipeModel? recipeModelObj, + Map? recipe, + Map? author, + TextEditingController? commentController, + String? token, + List? tags, + List? steps, + List? comments}) { return RecipeLoadedState( recipeModelObj: recipeModelObj ?? this.recipeModelObj, recipe: recipe ?? this.recipe, + author: author ?? this.author, + tags: tags ?? this.tags, + token: token ?? this.token, + commentController: commentController ?? this.commentController, + steps: steps ?? this.steps, + comments: comments ?? this.comments, ); } } diff --git a/lib/presentation/recipe/recipe_show_screen/recipe_show_screen.dart b/lib/presentation/recipe/recipe_show_screen/recipe_show_screen.dart index bad35e5..f1835f8 100644 --- a/lib/presentation/recipe/recipe_show_screen/recipe_show_screen.dart +++ b/lib/presentation/recipe/recipe_show_screen/recipe_show_screen.dart @@ -1,19 +1,23 @@ +import 'package:go_router/go_router.dart'; + import 'bloc/recipe_show_bloc.dart'; import 'models/recipe_show_model.dart'; +import 'package:eatvio_app/presentation/feed/blog_post_screen/widgets/comment_widget.dart'; import 'package:eatvio_app/core/app_export.dart'; import 'package:flutter/material.dart'; -import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; +import 'package:intl/intl.dart'; +import 'package:eatvio_app/widgets/custom_edit_text_form_field.dart'; +import 'package:eatvio_app/widgets/custom_outlined_button.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; class RecipeScreen extends StatelessWidget { const RecipeScreen({Key? key}) : super(key: key); + static Widget builder(BuildContext context) { final recipeID = ModalRoute.of(context)!.settings.arguments; return BlocProvider( - create: (context) => RecipeBloc(RecipeState( - recipeModelObj: RecipeModel(recipeID: recipeID.toString()))) - ..add(RecipeInitialEvent()), + create: (context) => RecipeBloc(RecipeState(recipeModelObj: RecipeModel(recipeID: recipeID.toString())))..add(RecipeInitialEvent()), child: RecipeScreen()); } @@ -41,112 +45,414 @@ class RecipeScreen extends StatelessWidget { _buildRecipe(BuildContext context, RecipeLoadedState state) { mediaQueryData = MediaQuery.of(context); + List steps = []; + if (state.steps != null) { + for (int i = 0; i < state.steps!.length; i++) { + steps.add(_buildStep(context, state, i)); + } + } return Scaffold( - resizeToAvoidBottomInset: false, - body: SafeArea( + appBar: AppBar( + toolbarHeight: 0, + automaticallyImplyLeading: false, + ), + resizeToAvoidBottomInset: false, + body: Container( + width: double.maxFinite, + decoration: AppDecoration.fillGray, + child: ListView( + children: [ + SizedBox(height: 15.v), + _buildHeaderRow(context, state), + SizedBox(height: 15.v), + _buildRecipeData(context, state), + SizedBox(height: 15.v), + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: steps, + ), + SizedBox(height: 15.v), + _buildTextField(context, state), + SizedBox(height: 15.v), + _buildCommentSection(context, state), + ], + ), + )); + } + + _buildHeaderRow(BuildContext context, RecipeLoadedState state) { + final String name = state.author?['name']; + return Container( + margin: EdgeInsets.symmetric(horizontal: 16.h, vertical: 16.h), + padding: EdgeInsets.symmetric( + horizontal: 24.h, + vertical: 12.v, + ), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(12)), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), + ), + ], + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CustomImageView( + imagePath: ImageConstant.imgArrowLeft, + onTap: () => GoRouter.of(context).pop(), + ), + Padding( + padding: EdgeInsets.only(top: 1.v), + child: Row( + children: [ + Align( + alignment: Alignment.centerRight, + child: Text( + "$name", + style: CustomTextStyles.titleMediumBlack, + ), + ), + CachedNetworkImage( + imageUrl: dotenv.env['STORAGE_URL']! + (state.author?['profile_photo_path'] ?? ''), + imageBuilder: (context, imageProvider) => CircleAvatar( + backgroundImage: imageProvider, + radius: 25, + ), + placeholder: (context, url) => CircularProgressIndicator(), + errorWidget: (context, url, error) => CircleAvatar( + child: Icon(Icons.person), + backgroundColor: Colors.white, + radius: 25, + ), + ), + ], + ), + ), + ], + ), + ); + } + + _buildRecipeData(BuildContext context, RecipeLoadedState state) { + return Container( + margin: EdgeInsets.only( + left: 16.h, + right: 20.h, + bottom: 10.h, + ), + padding: EdgeInsets.all(12.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(12)), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), + ), + ], + ), + child: Column(mainAxisSize: MainAxisSize.min, children: [ + Row( + children: [ + Text( + state.recipe?['title'], + style: CustomTextStyles.titleMediumBlack, + ), + Spacer(), + PopupMenuButton( + onSelected: (value) => tapMenu(value, context, state), + itemBuilder: (BuildContext context) => >[ + PopupMenuItem( + value: 'edit', + child: Text('Редактировать'), + ), + PopupMenuItem( + value: 'delete', + child: Text('Удалить'), + ), + ], + ), + ], + ), + SizedBox(height: 8.0), + _buildImage(state), + SizedBox(height: 16.0), + Text( + state.recipe?['description'], + style: CustomTextStyles.bodyMediumBluegray800, + ), + SizedBox(height: 16.0), + Align( + alignment: Alignment.topLeft, + child: Text( + DateFormat('dd.MM.yyyy').format(DateTime.parse(state.recipe?['updated_at'])), + style: theme.textTheme.bodyMedium, + )), + SizedBox(height: 16.0), + _buildTags(state.tags), + SizedBox(height: 16.0), + Row(children: [ + Padding( + padding: EdgeInsets.only(left: 24), + child: Row(children: [ + Padding( + padding: EdgeInsets.only(right: 8), + child: Icon(Icons.favorite_outline), + ), + Text( + "Нравится", + style: CustomTextStyles.titleMediumSemiBold, + ) + ]), + ), + Spacer(), + Padding( + padding: EdgeInsets.only(right: 24), + child: Row(children: [ + Padding( + padding: const EdgeInsets.only(right: 8), + child: Icon(Icons.share_outlined), + ), + Text( + "Поделиться", + style: CustomTextStyles.titleMediumSemiBold, + ) + ])) + ]), + ]), + ); + } + + _buildTags(List? tags) { + return Wrap( + spacing: 8.0, + runSpacing: 4.0, + children: tags!.map((tag) { + return Chip( + label: Text(tag['name']), + ); + }).toList(), + ); + } + + _buildImage(RecipeLoadedState state) { + return CachedNetworkImage( + imageUrl: dotenv.env['STORAGE_URL']! + (state.recipe?['image'] ?? ''), + imageBuilder: (context, imageProvider) => Stack( + children: [ + ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(8.0)), + child: Image( + image: imageProvider, + ), + ), + Positioned( + bottom: 0, + left: 0, + right: 0, child: Container( - width: double.maxFinite, - decoration: AppDecoration.fillGray, - child: ListView( + padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - SizedBox(height: 30.v), - Container( - margin: EdgeInsets.only( - left: 16.h, - right: 20.h, - bottom: 10.h, - ), - padding: EdgeInsets.all(12.h), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: const BorderRadius.all(Radius.circular(12)), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 2, - blurRadius: 5, - offset: Offset(0, 3), + Padding( + padding: const EdgeInsets.only(left: 48.0), + child: Row( + children: [ + Icon( + Icons.remove_red_eye, + color: Colors.white, + ), + SizedBox(width: 4.0), + Text( + '24', + style: TextStyle(color: Colors.white), ), ], ), - child: Column(mainAxisSize: MainAxisSize.min, children: [ - CachedNetworkImage( - imageUrl: dotenv.env['STORAGE_URL']! + - (state.recipe?['image'] ?? ''), - imageBuilder: (context, imageProvider) => CircleAvatar( - backgroundImage: imageProvider, - radius: 25, - ), - placeholder: (context, url) => - CircularProgressIndicator(), - errorWidget: (context, url, error) => CircleAvatar( - child: Icon(Icons.person), - backgroundColor: Colors.white, - radius: 25, - ), + ), + Row( + children: [ + Icon( + Icons.favorite, + color: Colors.white, ), - SizedBox(height: 16.0), - Text(state.recipe?['title'], - style: CustomTextStyles.titleMediumBlack), - SizedBox(height: 8.0), + SizedBox(width: 4.0), Text( - state.recipe?['description'], + '24', + style: TextStyle(color: Colors.white), ), - SizedBox(height: 16.0), - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - ElevatedButton( - onPressed: () => Navigator.pushNamed( - context, AppRoutes.recipeEditScreen, - arguments: state.recipe?['id'].toString()), - child: Text('Редактировать'), - ), - ElevatedButton( - onPressed: () => context - .read() - .add(DeleteEntry(state.recipe?['id'])), - child: Text('Удалить'), - ), - ], - ) - ]), - ) + ], + ), + Padding( + padding: const EdgeInsets.only(right: 48.0), + child: Row( + children: [ + Icon( + Icons.comment_outlined, + color: Colors.white, + ), + SizedBox(width: 4.0), + Text( + '${state.comments?.length}', + style: TextStyle(color: Colors.white), + ), + ], + ), + ), ], ), ), - ), bottomNavigationBar: _buildBottomBar(context)); + ), + ], + ), + placeholder: (context, url) => CircularProgressIndicator(), + ); } - ///Handling route based on bottom click actions - String getCurrentRoute(BottomBarEnum type) { - switch (type) { - case BottomBarEnum.tf: - return AppRoutes.recipeScreen; - default: - return "/"; + void tapMenu(String value, BuildContext context, RecipeLoadedState state) { + if (value == 'edit') { + GoRouter.of(context).goNamed( + 'recipeEditScreen', + extra: state.recipe?['id'].toString(), + ); + } else if (value == 'delete') { + context.read().add(DeleteEntry(state.recipe?['id'])); } } - ///Handling page based on route - Widget getCurrentPage( - BuildContext context, - String currentRoute, - ) { - switch (currentRoute) { - case AppRoutes.recipeScreen: - return RecipeScreen.builder(context); - default: - return RecipeScreen(); - } + _buildTextField(BuildContext context, RecipeLoadedState state) { + return Container( + margin: EdgeInsets.only( + left: 16.h, + right: 20.h, + bottom: 10.h, + ), + padding: EdgeInsets.all(12.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(12)), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), + ), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Column( + children: [ + CustomTextFormField( + controller: state.commentController, + maxLines: 4, + ), + SizedBox(height: 15.h), + CustomOutlinedButton( + text: "Оставить отзыв", + margin: EdgeInsets.only(bottom: 5), + buttonStyle: CustomButtonStyles.outlinePrimaryTL15, + decoration: BoxDecoration( + color: Colors.transparent, + ), + onPressed: () => context.read().add(CreateComment(state.commentController!.text)), + // Установите прозрачный фон + ) + ], + ), + ], + ), + ); } - _buildBottomBar(BuildContext context) { - return CustomBottomBar(onChanged: (BottomBarEnum type) {}); + _buildCommentSection(BuildContext context, RecipeLoadedState state) { + if (state.comments != null && state.comments!.isNotEmpty) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + for (int i = 0; i < state.comments!.length; i++) + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + CommentWidget(state.comments![i]), + SizedBox(height: 10.0), // Separator + ], + ), + ], + ); + } else { + // Return a message or placeholder widget when there are no comments + return Column( + children: [ + Center( + child: Text('No comments available'), + ), + SizedBox( + height: 20, + ) + ], + ); + } } /// Navigates to the previous screen. onTapArrowleftone(BuildContext context) { - NavigatorService.goBack(); + GoRouter.of(context).pop(); + } + + Widget _buildStep(BuildContext context, RecipeLoadedState state, int i) { + return Container( + margin: EdgeInsets.only( + left: 16.h, + right: 16.h, + bottom: 10.h, + ), + padding: EdgeInsets.all(12.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(12)), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), + ), + ], + ), + child: Column(mainAxisSize: MainAxisSize.min, children: [ + Align( + alignment: Alignment.centerLeft, + child: Text( + "Шаг ${i + 1}", + style: CustomTextStyles.titleMediumSemiBold, + ), + ), + SizedBox(height: 16.0), + CachedNetworkImage( + imageUrl: dotenv.env['STORAGE_URL']! + (state.steps?[i]['image']), + width: double.infinity, + // Установите ширину равной ширине родительского элемента + height: 200.0, + // Задайте постоянную высоту элемента + fit: BoxFit.cover, + placeholder: (context, url) => CircularProgressIndicator(), + errorWidget: (context, url, error) => Icon(Icons.error), + ), + SizedBox(height: 16.0), + Text(state.steps?[i]['description']) + ]), + ); } } diff --git a/lib/presentation/recipe/recipe_show_screen/widgets/comment_widget.dart b/lib/presentation/recipe/recipe_show_screen/widgets/comment_widget.dart new file mode 100644 index 0000000..c224966 --- /dev/null +++ b/lib/presentation/recipe/recipe_show_screen/widgets/comment_widget.dart @@ -0,0 +1,107 @@ +// ignore_for_file: must_be_immutable + +import 'package:flutter/material.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:eatvio_app/core/app_export.dart'; +import 'package:eatvio_app/presentation/feed/blog_post_screen/models/comment_model.dart'; +import 'package:cached_network_image/cached_network_image.dart'; + +class CommentWidget extends StatelessWidget { + CommentWidget(this.commentModelObj, {Key? key}) + : super( + key: key, + ); + + CommentModel commentModelObj; + + @override + Widget build(BuildContext context) { + return Container( + margin: EdgeInsets.only( + left: 16.h, + right: 20.h, + bottom: 10.h, + ), + padding: EdgeInsets.all(12.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(12)), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), + ), + ], + ), + child: Row( + mainAxisAlignment: MainAxisAlignment + .spaceBetween, // Move the main axis alignment to space between + crossAxisAlignment: + CrossAxisAlignment.center, // Center the items vertically + children: [ + CachedNetworkImage( + imageUrl: dotenv.env['STORAGE_URL']! + commentModelObj.userImage!, + imageBuilder: (context, imageProvider) => CircleAvatar( + backgroundImage: imageProvider, + radius: 25, + ), + placeholder: (context, url) => CircularProgressIndicator(), + errorWidget: (context, url, error) => CircleAvatar( + child: Icon(Icons.person), + backgroundColor: Colors.white, + radius: 25, + ), + ), + Expanded( + child: Padding( + padding: EdgeInsets.only( + left: 12.h, + top: 2.v, + bottom: 11.v, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 227.h, + child: Row( + children: [ + Text( + commentModelObj.username!.length <= 15 + ? commentModelObj.username! + : commentModelObj.username!.substring(0, 15) + + '...', + style: theme.textTheme.titleMedium, + ), + Spacer(), + Padding( + padding: EdgeInsets.only(left: 6.h), + child: Text( + commentModelObj.dateText!, + style: theme.textTheme.bodyMedium, + ), + ) + ], + ), + ), + SizedBox(height: 6.v), + Text( + commentModelObj.greetingText!, + style: CustomTextStyles.bodyMediumBluegray800, + ), + ], + ), + ), + ), + CustomImageView( + imagePath: ImageConstant.imgSend, + height: 30.v, + width: 30.h, + margin: EdgeInsets.only(left: 5)), + ], + ), + ); + } +} diff --git a/lib/presentation/registration/additional_information_screen/additional_information_screen.dart b/lib/presentation/registration/additional_information_screen/additional_information_screen.dart index c1d7037..c855fb4 100644 --- a/lib/presentation/registration/additional_information_screen/additional_information_screen.dart +++ b/lib/presentation/registration/additional_information_screen/additional_information_screen.dart @@ -1,3 +1,5 @@ +import 'package:go_router/go_router.dart'; + import 'bloc/additional_information_bloc.dart'; import 'models/additional_information_model.dart'; import 'package:eatvio_app/core/app_export.dart'; @@ -16,85 +18,63 @@ import 'package:http/http.dart' as http; class AdditionalInformationScreen extends StatelessWidget { const AdditionalInformationScreen({Key? key}) : super(key: key); - static Widget builder(BuildContext context) { - final sheet = - ModalRoute.of(context)!.settings.arguments as UserRegistrationSheet; + static Widget builder(BuildContext context, sheet) { return BlocProvider( - create: (context) => AdditionalInformationBloc( - AdditionalInformationState( - additionalInformationModelObj: - AdditionalInformationModel(userRegistrationSheet: sheet))) - ..add(AdditionalInformationInitialEvent()), + create: (context) => + AdditionalInformationBloc(AdditionalInformationState(additionalInformationModelObj: AdditionalInformationModel(userRegistrationSheet: sheet))) + ..add(AdditionalInformationInitialEvent()), child: AdditionalInformationScreen()); } @override Widget build(BuildContext context) { - mediaQueryData = MediaQuery.of(context); - return SafeArea( - child: Scaffold( - resizeToAvoidBottomInset: false, - appBar: _buildAppBar(context), - body: Container( - width: double.maxFinite, - padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v), - child: Column(children: [ - SizedBox(height: 7.v), - Container( - height: 16.v, - width: 343.h, - decoration: BoxDecoration( - color: theme.colorScheme.onPrimary, - borderRadius: BorderRadius.circular(8.h), - border: Border.all( - color: theme.colorScheme.primary, width: 1.h)), - child: ClipRRect( - borderRadius: BorderRadius.circular(8.h), - child: LinearProgressIndicator( - value: 1, - backgroundColor: theme.colorScheme.onPrimary, - valueColor: AlwaysStoppedAnimation( - appTheme.blueA400)))), - SizedBox(height: 20.v), - SizedBox(height: 73.v), - _buildTextFormField(context), - Spacer(), - BlocBuilder(builder: (context, state) { - return NextStepButton( - text: "finish".tr.toUpperCase(), - margin: EdgeInsets.only( - left: 16.h, right: 16.h, bottom: 50.v), - buttonStyle: CustomButtonStyles.outlinePrimaryTL15, - onPressed: onTapFinish(state, context)); - }), - ])))); + return Scaffold( + resizeToAvoidBottomInset: false, + appBar: _buildAppBar(context), + body: Container( + width: double.maxFinite, + padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v), + child: Column(children: [ + SizedBox(height: 7.v), + Container( + height: 16.v, + width: 343.h, + decoration: BoxDecoration( + color: theme.colorScheme.onPrimary, + borderRadius: BorderRadius.circular(8.h), + border: Border.all(color: theme.colorScheme.primary, width: 1.h)), + child: ClipRRect( + borderRadius: BorderRadius.circular(8.h), + child: LinearProgressIndicator( + value: 1, backgroundColor: theme.colorScheme.onPrimary, valueColor: AlwaysStoppedAnimation(appTheme.blueA400)))), + SizedBox(height: 20.v), + SizedBox(height: 73.v), + _buildTextFormField(context), + Spacer(), + BlocBuilder(builder: (context, state) { + return NextStepButton( + text: "finish".tr.toUpperCase(), + margin: EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v), + buttonStyle: CustomButtonStyles.outlinePrimaryTL15, + onPressed: onTapFinish(state, context)); + }), + ]))); } onTapFinish(AdditionalInformationState state, BuildContext context) { return () async { - UserRegistrationSheet sheet = - state.additionalInformationModelObj!.userRegistrationSheet; + UserRegistrationSheet sheet = state.additionalInformationModelObj!.userRegistrationSheet; sheet.additionalInfo = state.editTextController!.text; - final response = await http.post( - Uri.parse(dotenv.env['SERVER_ROOT']! + '/register'), - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json' - }, - body: jsonEncode(sheet.toJson()), - encoding: utf8); + final response = await http.post(Uri.parse(dotenv.env['SERVER_ROOT']! + '/register'), + headers: {'Content-Type': 'application/json', 'Accept': 'application/json'}, body: jsonEncode(sheet.toJson()), encoding: utf8); var res = await jsonDecode(response.body); if (response.statusCode == 200) { final box = await Hive.openBox('token'); final token = res['token']; box.put('token', token); - Navigator.pushNamed( - context, - AppRoutes.myProfile, - ); + GoRouter.of(context).goNamed('myProfileScreen'); } else { showErrorMessage(context, res['message']); } @@ -105,10 +85,8 @@ class AdditionalInformationScreen extends StatelessWidget { _buildAppBar(BuildContext context) { return CustomAppBar( leadingWidth: 31.h, - leading: AppbarLeadingImage( - imagePath: ImageConstant.imgArrowLeft, - margin: EdgeInsets.only(left: 18.h, top: 15.v, bottom: 15.v), - onTap: onTapBack(context)), + leading: + AppbarLeadingImage(imagePath: ImageConstant.imgArrowLeft, margin: EdgeInsets.only(left: 18.h, top: 15.v, bottom: 15.v), onTap: onTapBack(context)), centerTitle: true, title: AppbarTitleImage(imagePath: ImageConstant.imgLogoEatvio)); } @@ -132,19 +110,14 @@ class AdditionalInformationScreen extends StatelessWidget { width: 294.h, margin: EdgeInsets.only(left: 24.h, right: 23.h), child: Text("enter_additional_information".tr, - maxLines: 2, - overflow: TextOverflow.ellipsis, - textAlign: TextAlign.center, - style: CustomTextStyles.titleMediumSegoeUI)), + maxLines: 2, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, style: CustomTextStyles.titleMediumSegoeUI)), SizedBox(height: 10.v), - BlocSelector( + BlocSelector( selector: (state) => state.editTextController, builder: (context, editTextController) { return CustomTextFormField( controller: editTextController, - hintText: - "У меня крепкое здоровье, но последнее время часто утомляюсь, однако, это мне не мешает радоваться жизни", + hintText: "У меня крепкое здоровье, но последнее время часто утомляюсь, однако, это мне не мешает радоваться жизни", textInputAction: TextInputAction.done, maxLines: 8); }) @@ -154,7 +127,7 @@ class AdditionalInformationScreen extends StatelessWidget { /// Navigates to the previous screen. onTapArrowLeft(BuildContext context) { - NavigatorService.goBack(); + GoRouter.of(context).pop(); } /// Displays an alert dialog when the action is triggered. @@ -165,9 +138,7 @@ class AdditionalInformationScreen extends StatelessWidget { builder: (_) => AlertDialog( title: const Text('Ошибка'), content: Text(text), - actions: [ - TextButton(onPressed: onTapOk(context), child: const Text('Ok')) - ], + actions: [TextButton(onPressed: onTapOk(context), child: const Text('Ok'))], )); } diff --git a/lib/presentation/registration/allergies_screen/allergies_screen.dart b/lib/presentation/registration/allergies_screen/allergies_screen.dart index 9f4c6b0..50d056b 100644 --- a/lib/presentation/registration/allergies_screen/allergies_screen.dart +++ b/lib/presentation/registration/allergies_screen/allergies_screen.dart @@ -1,3 +1,5 @@ +import 'package:go_router/go_router.dart'; + import 'bloc/allergies_bloc.dart'; import 'models/allergies_model.dart'; import 'package:eatvio_app/core/app_export.dart'; @@ -12,13 +14,9 @@ import 'package:flutter/material.dart'; class AllergiesScreen extends StatefulWidget { const AllergiesScreen({Key? key}) : super(key: key); - static Widget builder(BuildContext context) { - final sheet = - ModalRoute.of(context)!.settings.arguments as UserRegistrationSheet; + static Widget builder(BuildContext context, sheet) { return BlocProvider( - create: (context) => AllergiesBloc(AllergiesState( - allergiesModelObj: AllergiesModel(userRegistrationSheet: sheet))) - ..add(AllergiesInitialEvent()), + create: (context) => AllergiesBloc(AllergiesState(allergiesModelObj: AllergiesModel(userRegistrationSheet: sheet)))..add(AllergiesInitialEvent()), child: AllergiesScreen()); } @@ -29,78 +27,58 @@ class AllergiesScreen extends StatefulWidget { class _AllergiesScreenState extends State { @override Widget build(BuildContext context) { - mediaQueryData = MediaQuery.of(context); - return SafeArea( - child: Scaffold( - resizeToAvoidBottomInset: false, - appBar: _buildAppBar(context), - body: Container( - width: double.maxFinite, - padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v), - child: Column(children: [ - SizedBox(height: 7.v), - Container( - height: 16.v, - width: 343.h, - decoration: BoxDecoration( - color: theme.colorScheme.onPrimary, - borderRadius: BorderRadius.circular(8.h), - border: Border.all( - color: theme.colorScheme.primary, width: 1.h)), - child: ClipRRect( - borderRadius: BorderRadius.circular(8.h), - child: LinearProgressIndicator( - value: 0.76, - backgroundColor: theme.colorScheme.onPrimary, - valueColor: AlwaysStoppedAnimation( - appTheme.blueA400)))), - SizedBox(height: 22.v), - Text("allergies_presence".tr, - style: theme.textTheme.headlineSmall), - Spacer(flex: 36), - _buildTextFormField(context), - Spacer(flex: 54), - BlocBuilder( - builder: (context, state) { - return NextStepButton( - text: "continue".tr.toUpperCase(), - margin: EdgeInsets.only( - left: 16.h, right: 16.h, bottom: 50.v), - buttonStyle: CustomButtonStyles.outlinePrimaryTL15, - onPressed: onTapContinue(state, context)); - }), - BlocBuilder( - builder: (context, state) { - return GestureDetector( - onTap: onTapSkip(state, context), - child: Text("skip".tr, style: theme.textTheme.bodyLarge), - ); - }), - ])))); + return Scaffold( + resizeToAvoidBottomInset: false, + appBar: _buildAppBar(context), + body: Container( + width: double.maxFinite, + padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v), + child: Column(children: [ + SizedBox(height: 7.v), + Container( + height: 16.v, + width: 343.h, + decoration: BoxDecoration( + color: theme.colorScheme.onPrimary, + borderRadius: BorderRadius.circular(8.h), + border: Border.all(color: theme.colorScheme.primary, width: 1.h)), + child: ClipRRect( + borderRadius: BorderRadius.circular(8.h), + child: LinearProgressIndicator( + value: 0.76, backgroundColor: theme.colorScheme.onPrimary, valueColor: AlwaysStoppedAnimation(appTheme.blueA400)))), + SizedBox(height: 22.v), + Text("allergies_presence".tr, style: theme.textTheme.headlineSmall), + Spacer(flex: 36), + _buildTextFormField(context), + Spacer(flex: 54), + BlocBuilder(builder: (context, state) { + return NextStepButton( + text: "continue".tr.toUpperCase(), + margin: EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v), + buttonStyle: CustomButtonStyles.outlinePrimaryTL15, + onPressed: onTapContinue(state, context)); + }), + BlocBuilder(builder: (context, state) { + return GestureDetector( + onTap: onTapSkip(state, context), + child: Text("skip".tr, style: theme.textTheme.bodyLarge), + ); + }), + ]))); } onTapSkip(AllergiesState state, BuildContext context) { return () { - UserRegistrationSheet sheet = - state.allergiesModelObj!.userRegistrationSheet; - Navigator.pushNamed( - context, - AppRoutes.medicineIntakeScreen, - arguments: sheet, - ); + UserRegistrationSheet sheet = state.allergiesModelObj!.userRegistrationSheet; + GoRouter.of(context).goNamed('medicineIntakeScreen', extra: sheet); }; } onTapContinue(AllergiesState state, BuildContext context) { return () { - UserRegistrationSheet sheet = - state.allergiesModelObj!.userRegistrationSheet; + UserRegistrationSheet sheet = state.allergiesModelObj!.userRegistrationSheet; sheet.aboutAllergy = state.enterInformationController!.text; - Navigator.pushNamed( - context, - AppRoutes.medicineIntakeScreen, - arguments: sheet, - ); + GoRouter.of(context).goNamed('medicineIntakeScreen', extra: sheet); }; } @@ -108,10 +86,8 @@ class _AllergiesScreenState extends State { _buildAppBar(BuildContext context) { return CustomAppBar( leadingWidth: 31.h, - leading: AppbarLeadingImage( - imagePath: ImageConstant.imgArrowLeft, - margin: EdgeInsets.only(left: 18.h, top: 15.v, bottom: 15.v), - onTap: onTapBack(context)), + leading: + AppbarLeadingImage(imagePath: ImageConstant.imgArrowLeft, margin: EdgeInsets.only(left: 18.h, top: 15.v, bottom: 15.v), onTap: onTapBack(context)), centerTitle: true, title: AppbarTitleImage(imagePath: ImageConstant.imgLogoEatvio)); } @@ -125,23 +101,19 @@ class _AllergiesScreenState extends State { /// Section Widget _buildTextFormField(BuildContext context) { return Column(children: [ - Text("fill_in_information".tr, - style: CustomTextStyles.titleMediumSegoeUI), + Text("fill_in_information".tr, style: CustomTextStyles.titleMediumSegoeUI), SizedBox(height: 2.v), BlocSelector( selector: (state) => state.enterInformationController, builder: (context, enterInformationController) { return CustomTextFormField( - controller: enterInformationController, - hintText: "enter_information".tr, - textInputAction: TextInputAction.done, - maxLines: 8); + controller: enterInformationController, hintText: "enter_information".tr, textInputAction: TextInputAction.done, maxLines: 8); }) ]); } /// Navigates to the previous screen. onTapArrowLeft(BuildContext context) { - NavigatorService.goBack(); + GoRouter.of(context).pop(); } } diff --git a/lib/presentation/registration/app_navigation_screen/app_navigation_screen.dart b/lib/presentation/registration/app_navigation_screen/app_navigation_screen.dart index ec34e36..781552e 100644 --- a/lib/presentation/registration/app_navigation_screen/app_navigation_screen.dart +++ b/lib/presentation/registration/app_navigation_screen/app_navigation_screen.dart @@ -1,3 +1,5 @@ +import 'package:go_router/go_router.dart'; + import 'bloc/app_navigation_bloc.dart'; import 'models/app_navigation_model.dart'; import 'package:eatvio_app/core/app_export.dart'; @@ -49,92 +51,77 @@ class _AppNavigationScreenState extends State { _buildScreenTitle( context, screenTitle: "date_of_birth".tr, - onTapScreenTitle: () => - onTapScreenTitle(AppRoutes.dateOfBirthScreen), + onTapScreenTitle: () => GoRouter.of(context).goNamed('dateOfBirthScreen'), ), _buildScreenTitle( context, screenTitle: "city_of_living".tr, - onTapScreenTitle: () => onTapScreenTitle( - AppRoutes.cityOfLivingScreen), + onTapScreenTitle: () => GoRouter.of(context).goNamed('cityOfLivingScreen'), ), _buildScreenTitle( context, screenTitle: "body_circumferences".tr, - onTapScreenTitle: () => onTapScreenTitle( - AppRoutes.bodyCircumferencesScreen), + onTapScreenTitle: () => GoRouter.of(context).goNamed('bodyCircumferencesScreen'), ), _buildScreenTitle( context, screenTitle: "weight".tr, - onTapScreenTitle: () => - onTapScreenTitle(AppRoutes.weightScreen), + onTapScreenTitle: () => GoRouter.of(context).goNamed('weightScreen'), ), _buildScreenTitle( context, screenTitle: "allergies".tr, - onTapScreenTitle: () => - onTapScreenTitle(AppRoutes.allergiesScreen), + onTapScreenTitle: () => GoRouter.of(context).goNamed('allergiesScreen'), ), _buildScreenTitle( context, screenTitle: "medicine_intake".tr, - onTapScreenTitle: () => onTapScreenTitle( - AppRoutes.medicineIntakeScreen), + onTapScreenTitle: () => GoRouter.of(context).goNamed('medicineIntakeScreen'), ), _buildScreenTitle( context, screenTitle: "splash_screen".tr, - onTapScreenTitle: () => - onTapScreenTitle(AppRoutes.splashScreen), + onTapScreenTitle: () => GoRouter.of(context).goNamed('splashScreen'), ), _buildScreenTitle( context, screenTitle: "gender".tr, - onTapScreenTitle: () => - onTapScreenTitle(AppRoutes.genderScreen), + onTapScreenTitle: () => GoRouter.of(context).goNamed('genderScreen'), ), _buildScreenTitle( context, screenTitle: "blood_pressure".tr, - onTapScreenTitle: () => onTapScreenTitle( - AppRoutes.bloodPressureScreen), + onTapScreenTitle: () => GoRouter.of(context).goNamed('bloodPressureScreen'), ), _buildScreenTitle( context, screenTitle: "favorite_sports".tr, - onTapScreenTitle: () => onTapScreenTitle( - AppRoutes.favoriteSportsScreen), + onTapScreenTitle: () => GoRouter.of(context).goNamed('favoriteSportsScreen'), ), _buildScreenTitle( context, screenTitle: "favorite_foods".tr, - onTapScreenTitle: () => onTapScreenTitle( - AppRoutes.favoriteFoodsScreen), + onTapScreenTitle: () => GoRouter.of(context).goNamed('favoriteFoodsScreen'), ), _buildScreenTitle( context, screenTitle: "schedule".tr, - onTapScreenTitle: () => - onTapScreenTitle(AppRoutes.scheduleScreen), + onTapScreenTitle: () => GoRouter.of(context).goNamed('scheduleScreen'), ), _buildScreenTitle( context, screenTitle: "additional_information".tr, - onTapScreenTitle: () => onTapScreenTitle( - AppRoutes.additionalInformationScreen), + onTapScreenTitle: () => GoRouter.of(context).goNamed('additionalInformationScreen'), ), _buildScreenTitle( context, screenTitle: "language_select".tr, - onTapScreenTitle: () => onTapScreenTitle( - AppRoutes.languageSelectScreen), + onTapScreenTitle: () => GoRouter.of(context).goNamed('languageSelectScreen'), ), _buildScreenTitle( context, screenTitle: "auth".tr, - onTapScreenTitle: () => - onTapScreenTitle(AppRoutes.authScreen), + onTapScreenTitle: () => GoRouter.of(context).goNamed('authScreen'), ), ], ), @@ -181,8 +168,7 @@ class _AppNavigationScreenState extends State { child: Padding( padding: EdgeInsets.only(left: 20.h), child: Text( - "Check your app's UI from the below demo screens of your app." - .tr, + "Check your app's UI from the below demo screens of your app.".tr, textAlign: TextAlign.center, style: TextStyle( color: Color(0XFF888888), @@ -247,9 +233,4 @@ class _AppNavigationScreenState extends State { ), ); } - - /// Common click event - void onTapScreenTitle(String routeName) { - NavigatorService.pushNamed(routeName); - } } diff --git a/lib/presentation/registration/blood_pressure_screen/blood_pressure_screen.dart b/lib/presentation/registration/blood_pressure_screen/blood_pressure_screen.dart index f0c26a1..5d4d8cc 100644 --- a/lib/presentation/registration/blood_pressure_screen/blood_pressure_screen.dart +++ b/lib/presentation/registration/blood_pressure_screen/blood_pressure_screen.dart @@ -1,3 +1,6 @@ +import 'package:eatvio_app/app_router.dart'; +import 'package:go_router/go_router.dart'; + import 'bloc/blood_pressure_bloc.dart'; import 'package:flutter/services.dart'; import 'models/blood_pressure_model.dart'; @@ -14,14 +17,10 @@ import 'package:flutter/material.dart'; class BloodPressureScreen extends StatefulWidget { const BloodPressureScreen({Key? key}) : super(key: key); - static Widget builder(BuildContext context) { - final sheet = - ModalRoute.of(context)!.settings.arguments as UserRegistrationSheet; + static Widget builder(BuildContext context, sheet) { return BlocProvider( - create: (context) => BloodPressureBloc(BloodPressureState( - bloodPressureModelObj: - BloodPressureModel(userRegistrationSheet: sheet))) - ..add(BloodPressureInitialEvent()), + create: (context) => + BloodPressureBloc(BloodPressureState(bloodPressureModelObj: BloodPressureModel(userRegistrationSheet: sheet)))..add(BloodPressureInitialEvent()), child: BloodPressureScreen()); } @@ -33,184 +32,133 @@ class _BloodPressureScreenState extends State { @override Widget build(BuildContext context) { final digitsOnly = FilteringTextInputFormatter.digitsOnly; - mediaQueryData = MediaQuery.of(context); - return SafeArea( - child: Scaffold( - resizeToAvoidBottomInset: false, - appBar: _buildAppBar(context), - body: Container( - width: double.maxFinite, - padding: EdgeInsets.symmetric(horizontal: 12.h, vertical: 9.v), - child: Column(children: [ - SizedBox(height: 7.v), - Container( - height: 16.v, - width: 343.h, - padding: EdgeInsets.symmetric( - horizontal: - 4.h), // Apply padding directly to the Container - decoration: BoxDecoration( - color: theme.colorScheme.onPrimary, - borderRadius: BorderRadius.circular(8.h), - border: Border.all( - color: theme.colorScheme.primary, - width: 1.h, - ), - ), - child: ClipRRect( - borderRadius: BorderRadius.circular(8.h), - child: LinearProgressIndicator( - value: 0.62, - backgroundColor: theme.colorScheme.onPrimary, - valueColor: - AlwaysStoppedAnimation(appTheme.blueA400), - ), - ), + return Scaffold( + resizeToAvoidBottomInset: false, + appBar: _buildAppBar(context), + body: Container( + width: double.maxFinite, + padding: EdgeInsets.symmetric(horizontal: 12.h, vertical: 9.v), + child: Column(children: [ + SizedBox(height: 7.v), + Container( + height: 16.v, + width: 343.h, + padding: EdgeInsets.symmetric(horizontal: 4.h), + // Apply padding directly to the Container + decoration: BoxDecoration( + color: theme.colorScheme.onPrimary, + borderRadius: BorderRadius.circular(8.h), + border: Border.all( + color: theme.colorScheme.primary, + width: 1.h, ), - SizedBox(height: 21.v), - SizedBox( - width: 224.h, - child: Text("your_blood_pressure".tr, - maxLines: 2, - overflow: TextOverflow.ellipsis, - textAlign: TextAlign.center, - style: theme.textTheme.headlineSmall)), - SizedBox(height: 21.v), - Container( - margin: EdgeInsets.only(right: 1.h), - padding: EdgeInsets.symmetric(horizontal: 3.h), - child: Column(children: [ - Text("up_pressure".tr, - style: theme.textTheme.bodyLarge), - SizedBox(height: 2.v), - BlocSelector( - selector: (state) => state.upPressureController, - builder: (context, upPressureController) { - return CustomTextFormField( - inputFormatters: [digitsOnly], - controller: upPressureController, - hintText: "90".tr); - }) - ])), - SizedBox(height: 21.v), - Container( - margin: EdgeInsets.only(right: 1.h), - padding: EdgeInsets.symmetric(horizontal: 3.h), - child: Column(children: [ - Text("down_pressure".tr, - style: theme.textTheme.bodyLarge), - SizedBox(height: 2.v), - BlocSelector( - selector: (state) => state.downPressureController, - builder: (context, downPressureController) { - return CustomTextFormField( - inputFormatters: [digitsOnly], - controller: downPressureController, - hintText: "90".tr); - }) - ])), - SizedBox(height: 22.v), - Text("do_you_have_hypotension_tendency".tr, - style: theme.textTheme.bodyLarge), - SizedBox(height: 2.v), - Padding( - padding: EdgeInsets.symmetric(horizontal: 4.h), - child: BlocSelector( - selector: (state) => state.bloodPressureModelObj, - builder: (context, bloodPressureModelObj) { - return CustomDropDown( - icon: Container( - margin: EdgeInsets.fromLTRB( - 30.h, 11.v, 13.h, 11.v), - decoration: BoxDecoration( - borderRadius: - BorderRadius.circular(1.h)), - child: CustomImageView( - imagePath: ImageConstant.imgFavorite, - height: 12.adaptSize, - width: 12.adaptSize)), - hintText: "Не выбрано".tr, - items: - bloodPressureModelObj?.dropdownItemList ?? - [], - onChanged: onChangeHyper(context)); - })), - SizedBox(height: 22.v), - Text("do_you_have_hypertension_tendency".tr, - style: theme.textTheme.bodyLarge), - SizedBox(height: 2.v), - Padding( - padding: EdgeInsets.symmetric(horizontal: 4.h), - child: BlocSelector( - selector: (state) => state.bloodPressureModelObj, - builder: (context, bloodPressureModelObj) { - return CustomDropDown( - icon: Container( - margin: EdgeInsets.fromLTRB( - 30.h, 11.v, 13.h, 11.v), - decoration: BoxDecoration( - borderRadius: - BorderRadius.circular(1.h)), - child: CustomImageView( - imagePath: ImageConstant.imgFavorite, - height: 12.adaptSize, - width: 12.adaptSize)), - hintText: "Не выбрано".tr, - items: - bloodPressureModelObj?.dropdownItemList ?? - [], - onChanged: onChangeHypo(context)); - })), - Spacer(), - BlocBuilder( - builder: (context, state) { - return NextStepButton( - text: "continue".tr.toUpperCase(), - margin: EdgeInsets.only( - left: 16.h, right: 16.h, bottom: 50.v), - buttonStyle: CustomButtonStyles.outlinePrimaryTL15, - onPressed: onTapContinue(state, context)); - }), - BlocBuilder( - builder: (context, state) { - return GestureDetector( - onTap: onTapSkip(state, context), - child: Text("skip".tr, style: theme.textTheme.bodyLarge), - ); - }), - ])))); + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(8.h), + child: LinearProgressIndicator( + value: 0.62, + backgroundColor: theme.colorScheme.onPrimary, + valueColor: AlwaysStoppedAnimation(appTheme.blueA400), + ), + ), + ), + SizedBox(height: 21.v), + SizedBox( + width: 224.h, + child: Text("your_blood_pressure".tr, + maxLines: 2, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, style: theme.textTheme.headlineSmall)), + SizedBox(height: 21.v), + Container( + margin: EdgeInsets.only(right: 1.h), + padding: EdgeInsets.symmetric(horizontal: 3.h), + child: Column(children: [ + Text("up_pressure".tr, style: theme.textTheme.bodyLarge), + SizedBox(height: 2.v), + BlocSelector( + selector: (state) => state.upPressureController, + builder: (context, upPressureController) { + return CustomTextFormField(inputFormatters: [digitsOnly], controller: upPressureController, hintText: "90".tr); + }) + ])), + SizedBox(height: 21.v), + Container( + margin: EdgeInsets.only(right: 1.h), + padding: EdgeInsets.symmetric(horizontal: 3.h), + child: Column(children: [ + Text("down_pressure".tr, style: theme.textTheme.bodyLarge), + SizedBox(height: 2.v), + BlocSelector( + selector: (state) => state.downPressureController, + builder: (context, downPressureController) { + return CustomTextFormField(inputFormatters: [digitsOnly], controller: downPressureController, hintText: "90".tr); + }) + ])), + SizedBox(height: 22.v), + Text("do_you_have_hypotension_tendency".tr, style: theme.textTheme.bodyLarge), + SizedBox(height: 2.v), + Padding( + padding: EdgeInsets.symmetric(horizontal: 4.h), + child: BlocSelector( + selector: (state) => state.bloodPressureModelObj, + builder: (context, bloodPressureModelObj) { + return CustomDropDown( + icon: Container( + margin: EdgeInsets.fromLTRB(30.h, 11.v, 13.h, 11.v), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(1.h)), + child: CustomImageView(imagePath: ImageConstant.imgFavorite, height: 12.adaptSize, width: 12.adaptSize)), + hintText: "Не выбрано".tr, + items: bloodPressureModelObj?.dropdownItemList ?? [], + onChanged: onChangeHyper(context)); + })), + SizedBox(height: 22.v), + Text("do_you_have_hypertension_tendency".tr, style: theme.textTheme.bodyLarge), + SizedBox(height: 2.v), + Padding( + padding: EdgeInsets.symmetric(horizontal: 4.h), + child: BlocSelector( + selector: (state) => state.bloodPressureModelObj, + builder: (context, bloodPressureModelObj) { + return CustomDropDown( + icon: Container( + margin: EdgeInsets.fromLTRB(30.h, 11.v, 13.h, 11.v), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(1.h)), + child: CustomImageView(imagePath: ImageConstant.imgFavorite, height: 12.adaptSize, width: 12.adaptSize)), + hintText: "Не выбрано".tr, + items: bloodPressureModelObj?.dropdownItemList ?? [], + onChanged: onChangeHypo(context)); + })), + Spacer(), + BlocBuilder(builder: (context, state) { + return NextStepButton( + text: "continue".tr.toUpperCase(), + margin: EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v), + buttonStyle: CustomButtonStyles.outlinePrimaryTL15, + onPressed: onTapContinue(state, context)); + }), + BlocBuilder(builder: (context, state) { + return GestureDetector( + onTap: onTapSkip(state, context), + child: Text("skip".tr, style: theme.textTheme.bodyLarge), + ); + }), + ]))); } onTapSkip(BloodPressureState state, BuildContext context) { return () { - UserRegistrationSheet sheet = - state.bloodPressureModelObj!.userRegistrationSheet; - Navigator.pushNamed( - context, - AppRoutes.allergiesScreen, - arguments: sheet, - ); + UserRegistrationSheet sheet = state.bloodPressureModelObj!.userRegistrationSheet; + AppRouter.navigatorKey.currentContext?.goNamed('allergiesScreen', extra: sheet); }; } onTapContinue(BloodPressureState state, BuildContext context) { return () { - List errors = validateFields( - state.upPressureController!.text, state.downPressureController!.text); + List errors = validateFields(state.upPressureController!.text, state.downPressureController!.text); if (errors.isEmpty) { - UserRegistrationSheet sheet = - state.bloodPressureModelObj!.userRegistrationSheet; + UserRegistrationSheet sheet = state.bloodPressureModelObj!.userRegistrationSheet; sheet.bloodPressureUp = state.upPressureController!.text; sheet.bloodPressureDown = state.downPressureController!.text; - Navigator.pushNamed( - context, - AppRoutes.favoriteSportsScreen, - arguments: sheet, - ); + AppRouter.navigatorKey.currentContext?.goNamed('favoriteSportsScreen', extra: sheet); } else { String errorMessage = ''; @@ -243,9 +191,7 @@ class _BloodPressureScreenState extends State { onChangeHypo(BuildContext context) { return (value) { - context - .read() - .add(ChangeHyperDropDownEvent(value: value)); + context.read().add(ChangeHyperDropDownEvent(value: value)); }; } @@ -259,10 +205,8 @@ class _BloodPressureScreenState extends State { _buildAppBar(BuildContext context) { return CustomAppBar( leadingWidth: 29.h, - leading: AppbarLeadingImage( - imagePath: ImageConstant.imgArrowLeft, - margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v), - onTap: onTapBack(context)), + leading: + AppbarLeadingImage(imagePath: ImageConstant.imgArrowLeft, margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v), onTap: onTapBack(context)), centerTitle: true, title: AppbarTitleImage(imagePath: ImageConstant.imgLogoEatvio)); } @@ -275,7 +219,7 @@ class _BloodPressureScreenState extends State { /// Navigates to the previous screen. onTapArrowLeft(BuildContext context) { - NavigatorService.goBack(); + GoRouter.of(context).pop(); } List validateFields( @@ -288,8 +232,7 @@ class _BloodPressureScreenState extends State { errors.add('pressure_empty'.tr); } - if (![bloodPressureUp, bloodPressureDown] - .every((pressure) => int.tryParse(pressure) != null)) { + if (![bloodPressureUp, bloodPressureDown].every((pressure) => int.tryParse(pressure) != null)) { errors.add('pressure_is_not_a_number'.tr); } diff --git a/lib/presentation/registration/body_circumferences_screen/body_circumferences_screen.dart b/lib/presentation/registration/body_circumferences_screen/body_circumferences_screen.dart index 17ce3df..3610697 100644 --- a/lib/presentation/registration/body_circumferences_screen/body_circumferences_screen.dart +++ b/lib/presentation/registration/body_circumferences_screen/body_circumferences_screen.dart @@ -1,3 +1,5 @@ +import 'package:go_router/go_router.dart'; + import 'bloc/body_circumferences_bloc.dart'; import 'package:flutter/services.dart'; import 'models/body_circumferences_model.dart'; @@ -13,120 +15,87 @@ import 'package:flutter/material.dart'; class BodyCircumferencesScreen extends StatelessWidget { const BodyCircumferencesScreen({Key? key}) : super(key: key); - static Widget builder(BuildContext context) { - final sheet = - ModalRoute.of(context)!.settings.arguments as UserRegistrationSheet; + static Widget builder(BuildContext context, sheet) { return BlocProvider( - create: (context) => BodyCircumferencesBloc(BodyCircumferencesState( - bodyCircumferencesModelObj: - BodyCircumferencesModel(userRegistrationSheet: sheet))) - ..add(BodyCircumferencesInitialEvent()), - child: BodyCircumferencesScreen()); + create: (context) => BodyCircumferencesBloc(BodyCircumferencesState(bodyCircumferencesModelObj: BodyCircumferencesModel(userRegistrationSheet: sheet))) + ..add(BodyCircumferencesInitialEvent()), + child: BodyCircumferencesScreen(), + ); } @override Widget build(BuildContext context) { final digitsOnly = FilteringTextInputFormatter.digitsOnly; mediaQueryData = MediaQuery.of(context); - return SafeArea( - child: Scaffold( - resizeToAvoidBottomInset: false, - appBar: _buildAppBar(context), - body: Container( - width: double.maxFinite, - padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v), - child: Column(children: [ - SizedBox(height: 7.v), - Container( - height: 16.v, - width: 343.h, - decoration: BoxDecoration( - color: theme.colorScheme.onPrimary, - borderRadius: BorderRadius.circular(8.h), - border: Border.all( - color: theme.colorScheme.primary, width: 1.h)), - child: ClipRRect( - borderRadius: BorderRadius.circular(8.h), - child: LinearProgressIndicator( - value: 0.40, - backgroundColor: theme.colorScheme.onPrimary, - valueColor: AlwaysStoppedAnimation( - appTheme.blueA400)))), - SizedBox(height: 39.v), - Text("initial_measurements".tr, - style: theme.textTheme.headlineSmall), - SizedBox(height: 93.v), - Column(children: [ - Text("chest_circumference".tr, - style: theme.textTheme.bodyLarge), - SizedBox(height: 3.v), - BlocSelector( - selector: (state) => state.chestCircumferenceController, - builder: (context, chestCircumferenceController) { - return CustomTextFormField( - inputFormatters: [digitsOnly], - controller: chestCircumferenceController, - hintText: "90".tr); - }) - ]), - SizedBox(height: 19.v), - Text("waist_circumference".tr, - style: theme.textTheme.bodyLarge), - SizedBox(height: 5.v), - BlocSelector( - selector: (state) => state.waistCircumferenceController, - builder: (context, waistCircumferenceController) { - return CustomTextFormField( - inputFormatters: [digitsOnly], - controller: waistCircumferenceController, - hintText: "60".tr); - }), - SizedBox(height: 21.v), - Text("hip_circumference".tr, - style: theme.textTheme.bodyLarge), - SizedBox(height: 3.v), - BlocSelector( - selector: (state) => state.hipCircumferenceController, - builder: (context, hipCircumferenceController) { - return CustomTextFormField( - inputFormatters: [digitsOnly], - controller: hipCircumferenceController, - hintText: "90".tr, - textInputAction: TextInputAction.done); - }), - Spacer(), - BlocBuilder( - builder: (context, state) { - return NextStepButton( - text: "continue".tr.toUpperCase(), - margin: EdgeInsets.only( - left: 16.h, right: 16.h, bottom: 50.v), - buttonStyle: CustomButtonStyles.outlinePrimaryTL15, - onPressed: onTapContinue(state, context)); + return Scaffold( + resizeToAvoidBottomInset: false, + appBar: _buildAppBar(context), + body: Container( + width: double.maxFinite, + padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v), + child: Column(children: [ + SizedBox(height: 7.v), + Container( + height: 16.v, + width: 343.h, + decoration: BoxDecoration( + color: theme.colorScheme.onPrimary, + borderRadius: BorderRadius.circular(8.h), + border: Border.all(color: theme.colorScheme.primary, width: 1.h)), + child: ClipRRect( + borderRadius: BorderRadius.circular(8.h), + child: LinearProgressIndicator( + value: 0.40, backgroundColor: theme.colorScheme.onPrimary, valueColor: AlwaysStoppedAnimation(appTheme.blueA400)))), + SizedBox(height: 39.v), + Text("initial_measurements".tr, style: theme.textTheme.headlineSmall), + SizedBox(height: 93.v), + Column(children: [ + Text("chest_circumference".tr, style: theme.textTheme.bodyLarge), + SizedBox(height: 3.v), + BlocSelector( + selector: (state) => state.chestCircumferenceController, + builder: (context, chestCircumferenceController) { + return CustomTextFormField(inputFormatters: [digitsOnly], controller: chestCircumferenceController, hintText: "90".tr); + }) + ]), + SizedBox(height: 19.v), + Text("waist_circumference".tr, style: theme.textTheme.bodyLarge), + SizedBox(height: 5.v), + BlocSelector( + selector: (state) => state.waistCircumferenceController, + builder: (context, waistCircumferenceController) { + return CustomTextFormField(inputFormatters: [digitsOnly], controller: waistCircumferenceController, hintText: "60".tr); }), - ])))); + SizedBox(height: 21.v), + Text("hip_circumference".tr, style: theme.textTheme.bodyLarge), + SizedBox(height: 3.v), + BlocSelector( + selector: (state) => state.hipCircumferenceController, + builder: (context, hipCircumferenceController) { + return CustomTextFormField( + inputFormatters: [digitsOnly], controller: hipCircumferenceController, hintText: "90".tr, textInputAction: TextInputAction.done); + }), + Spacer(), + BlocBuilder(builder: (context, state) { + return NextStepButton( + text: "continue".tr.toUpperCase(), + margin: EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v), + buttonStyle: CustomButtonStyles.outlinePrimaryTL15, + onPressed: onTapContinue(state, context)); + }), + ]))); } onTapContinue(BodyCircumferencesState state, BuildContext context) { return () { - List errors = validateFields( - state.chestCircumferenceController!.text, - state.waistCircumferenceController!.text, - state.hipCircumferenceController!.text); + List errors = + validateFields(state.chestCircumferenceController!.text, state.waistCircumferenceController!.text, state.hipCircumferenceController!.text); if (errors.isEmpty) { - UserRegistrationSheet sheet = - state.bodyCircumferencesModelObj!.userRegistrationSheet; + UserRegistrationSheet sheet = state.bodyCircumferencesModelObj!.userRegistrationSheet; sheet.chestCircumference = state.chestCircumferenceController!.text; sheet.waistCircumference = state.waistCircumferenceController!.text; sheet.hipCircumference = state.hipCircumferenceController!.text; - Navigator.pushNamed( - context, - AppRoutes.weightScreen, - arguments: sheet, - ); + GoRouter.of(context).goNamed('weightScreen', extra: sheet); } else { String errorMessage = ''; @@ -161,10 +130,8 @@ class BodyCircumferencesScreen extends StatelessWidget { _buildAppBar(BuildContext context) { return CustomAppBar( leadingWidth: 29.h, - leading: AppbarLeadingImage( - imagePath: ImageConstant.imgArrowLeft, - margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v), - onTap: onTapBack(context)), + leading: + AppbarLeadingImage(imagePath: ImageConstant.imgArrowLeft, margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v), onTap: onTapBack(context)), centerTitle: true, title: AppbarTitleImage(imagePath: ImageConstant.imgLogoEatvio)); } @@ -177,15 +144,11 @@ class BodyCircumferencesScreen extends StatelessWidget { /// Navigates to the previous screen. onTapArrowLeft(BuildContext context) { - NavigatorService.goBack(); + GoRouter.of(context).pop(); } /// Navigates to the weightScreen when the action is triggered. - navigateToWeight(BuildContext context) { - NavigatorService.pushNamed( - AppRoutes.weightScreen, - ); - } + navigateToWeight(BuildContext context) => GoRouter.of(context).goNamed('weightScreen'); List validateFields( String chestCircumference, @@ -194,14 +157,11 @@ class BodyCircumferencesScreen extends StatelessWidget { ) { List errors = []; - if (chestCircumference.isEmpty | - waistCircumference.isEmpty | - hipCircumference.isEmpty) { + if (chestCircumference.isEmpty | waistCircumference.isEmpty | hipCircumference.isEmpty) { errors.add('circumference_empty'.tr); } - if (![chestCircumference, waistCircumference, hipCircumference] - .every((circumference) => int.tryParse(circumference) != null)) { + if (![chestCircumference, waistCircumference, hipCircumference].every((circumference) => int.tryParse(circumference) != null)) { errors.add('circumference_is_not_a_number'.tr); } diff --git a/lib/presentation/registration/city_of_living_screen/city_of_living_screen.dart b/lib/presentation/registration/city_of_living_screen/city_of_living_screen.dart index 20ab28e..5ce9150 100644 --- a/lib/presentation/registration/city_of_living_screen/city_of_living_screen.dart +++ b/lib/presentation/registration/city_of_living_screen/city_of_living_screen.dart @@ -1,3 +1,5 @@ +import 'package:go_router/go_router.dart'; + import 'bloc/city_of_living_bloc.dart'; import 'models/city_of_living_model.dart'; import 'package:eatvio_app/core/app_export.dart'; @@ -13,15 +15,12 @@ import 'package:flutter/material.dart'; class CityOfLivingScreen extends StatelessWidget { const CityOfLivingScreen({Key? key}) : super(key: key); - static Widget builder(BuildContext context) { - final sheet = - ModalRoute.of(context)!.settings.arguments as UserRegistrationSheet; + static Widget builder(BuildContext context, sheet) { return BlocProvider( - create: (context) => CityOfLivingBloc(CityOfLivingState( - cityOfLivingModelObj: - CityOfLivingModel(userRegistrationSheet: sheet))) - ..add(CityOfLivingInitialEvent()), - child: CityOfLivingScreen()); + create: (context) => + CityOfLivingBloc(CityOfLivingState(cityOfLivingModelObj: CityOfLivingModel(userRegistrationSheet: sheet)))..add(CityOfLivingInitialEvent()), + child: CityOfLivingScreen(), + ); } @override @@ -1145,132 +1144,106 @@ class CityOfLivingScreen extends StatelessWidget { "Ясный", "Яхрома" ]; - mediaQueryData = MediaQuery.of(context); - return SafeArea( - child: Scaffold( - appBar: _buildAppBar(context), - body: Container( - width: double.maxFinite, - padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v), - child: Column(children: [ - SizedBox(height: 7.v), - Container( - height: 16.v, - width: 343.h, - decoration: BoxDecoration( - color: theme.colorScheme.onPrimary, - borderRadius: BorderRadius.circular(8.h), - border: Border.all( - color: theme.colorScheme.primary, width: 1.h)), - child: ClipRRect( - borderRadius: BorderRadius.circular(8.h), - child: LinearProgressIndicator( - value: 0.32, - backgroundColor: theme.colorScheme.onPrimary, - valueColor: AlwaysStoppedAnimation( - appTheme.blueA400)))), - SizedBox(height: 39.v), - Text("place_of_living".tr, - style: theme.textTheme.headlineSmall), - Spacer(flex: 34), - Text("country".tr, style: theme.textTheme.bodyLarge), - SizedBox(height: 2.v), - BlocSelector( - selector: (state) => state.cityOfLivingModelObj, - builder: (context, cityOfLivingModelObj) { - return CustomDropDown( - icon: Container( - margin: - EdgeInsets.fromLTRB(30.h, 11.v, 17.h, 11.v), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(1.h)), - child: CustomImageView( - imagePath: ImageConstant.imgFavorite, - height: 12.adaptSize, - width: 12.adaptSize)), - hintText: "Не выбрано".tr, - items: cityOfLivingModelObj?.dropdownItemList ?? [], - onChanged: onDropDownChange(context)); - }), - SizedBox(height: 22.v), - Text("Населенный пункт", style: theme.textTheme.bodyLarge), - SizedBox(height: 2.v), - BlocBuilder( - builder: (context, state) { - return RawAutocomplete( - optionsBuilder: (TextEditingValue textEditingValue) => - _options.where((String option) { - return option - .toLowerCase() - .startsWith(textEditingValue.text.toLowerCase()); - }), - fieldViewBuilder: ( - BuildContext context, - TextEditingController textEditingController, - FocusNode focusNode, - VoidCallback onFieldSubmitted, - ) { - return CustomTextFormField( - controller: textEditingController, - focusNode: focusNode, - onFieldSubmitted: (String value) => - onFieldSubmitted(), - ); - }, - optionsViewBuilder: ( - BuildContext context, - AutocompleteOnSelected onSelected, - Iterable options, - ) { - return Align( - alignment: Alignment.topLeft, - child: Material( - elevation: 4.0, - child: SizedBox( - height: 200.0, - child: ListView.builder( - padding: const EdgeInsets.all(8.0), - itemCount: options.length, - itemBuilder: (BuildContext context, int index) { - final String option = - options.elementAt(index); - return GestureDetector( - onTap: () => onSelected(option), - child: ListTile( - title: Text(option), - ), - ); - }, - ), - ), - ), - ); - }, + return Scaffold( + appBar: _buildAppBar(context), + body: Container( + width: double.maxFinite, + padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v), + child: Column(children: [ + SizedBox(height: 7.v), + Container( + height: 16.v, + width: 343.h, + decoration: BoxDecoration( + color: theme.colorScheme.onPrimary, + borderRadius: BorderRadius.circular(8.h), + border: Border.all(color: theme.colorScheme.primary, width: 1.h)), + child: ClipRRect( + borderRadius: BorderRadius.circular(8.h), + child: LinearProgressIndicator( + value: 0.32, backgroundColor: theme.colorScheme.onPrimary, valueColor: AlwaysStoppedAnimation(appTheme.blueA400)))), + SizedBox(height: 39.v), + Text("place_of_living".tr, style: theme.textTheme.headlineSmall), + Spacer(flex: 34), + Text("country".tr, style: theme.textTheme.bodyLarge), + SizedBox(height: 2.v), + BlocSelector( + selector: (state) => state.cityOfLivingModelObj, + builder: (context, cityOfLivingModelObj) { + return CustomDropDown( + icon: Container( + margin: EdgeInsets.fromLTRB(30.h, 11.v, 17.h, 11.v), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(1.h)), + child: CustomImageView(imagePath: ImageConstant.imgFavorite, height: 12.adaptSize, width: 12.adaptSize)), + hintText: "Не выбрано".tr, + items: cityOfLivingModelObj?.dropdownItemList ?? [], + onChanged: onDropDownChange(context)); + }), + SizedBox(height: 22.v), + Text("Населенный пункт", style: theme.textTheme.bodyLarge), + SizedBox(height: 2.v), + BlocBuilder(builder: (context, state) { + return RawAutocomplete( + optionsBuilder: (TextEditingValue textEditingValue) => _options.where((String option) { + return option.toLowerCase().startsWith(textEditingValue.text.toLowerCase()); + }), + fieldViewBuilder: ( + BuildContext context, + TextEditingController textEditingController, + FocusNode focusNode, + VoidCallback onFieldSubmitted, + ) { + return CustomTextFormField( + controller: textEditingController, + focusNode: focusNode, + onFieldSubmitted: (String value) => onFieldSubmitted(), ); - }), - Spacer(flex: 65), - BlocBuilder( - builder: (context, state) { - return NextStepButton( - text: "continue".tr.toUpperCase(), - margin: EdgeInsets.only( - left: 16.h, right: 16.h, bottom: 50.v), - buttonStyle: CustomButtonStyles.outlinePrimaryTL15, - onPressed: onTapContinue(state, context)); - }), - ])))); + }, + optionsViewBuilder: ( + BuildContext context, + AutocompleteOnSelected onSelected, + Iterable options, + ) { + return Align( + alignment: Alignment.topLeft, + child: Material( + elevation: 4.0, + child: SizedBox( + height: 200.0, + child: ListView.builder( + padding: const EdgeInsets.all(8.0), + itemCount: options.length, + itemBuilder: (BuildContext context, int index) { + final String option = options.elementAt(index); + return GestureDetector( + onTap: () => onSelected(option), + child: ListTile( + title: Text(option), + ), + ); + }, + ), + ), + ), + ); + }, + ); + }), + Spacer(flex: 65), + BlocBuilder(builder: (context, state) { + return NextStepButton( + text: "continue".tr.toUpperCase(), + margin: EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v), + buttonStyle: CustomButtonStyles.outlinePrimaryTL15, + onPressed: onTapContinue(state, context)); + }), + ]))); } onTapContinue(CityOfLivingState state, BuildContext context) { return () { - UserRegistrationSheet sheet = - state.cityOfLivingModelObj!.userRegistrationSheet; - Navigator.pushNamed( - context, - AppRoutes.bodyCircumferencesScreen, - arguments: sheet, - ); + UserRegistrationSheet sheet = state.cityOfLivingModelObj!.userRegistrationSheet; + GoRouter.of(context).goNamed('bodyCircumferencesScreen', extra: sheet); }; } @@ -1284,10 +1257,8 @@ class CityOfLivingScreen extends StatelessWidget { _buildAppBar(BuildContext context) { return CustomAppBar( leadingWidth: 29.h, - leading: AppbarLeadingImage( - imagePath: ImageConstant.imgArrowLeft, - margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v), - onTap: onTapBack(context)), + leading: + AppbarLeadingImage(imagePath: ImageConstant.imgArrowLeft, margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v), onTap: onTapBack(context)), centerTitle: true, title: AppbarTitleImage(imagePath: ImageConstant.imgLogoEatvio)); } @@ -1300,12 +1271,11 @@ class CityOfLivingScreen extends StatelessWidget { /// Navigates to the previous screen. onTapArrowLeft(BuildContext context) { - NavigatorService.goBack(); + GoRouter.of(context).pop(); } } -selectProduct(AutocompleteOnSelected onSelected, String option, - CityOfLivingState state) { +selectProduct(AutocompleteOnSelected onSelected, String option, CityOfLivingState state) { state.cityController?.text = option; return () { onSelected(option); diff --git a/lib/presentation/registration/credentials_screen/credentials_screen.dart b/lib/presentation/registration/credentials_screen/credentials_screen.dart index 4897999..90723f6 100644 --- a/lib/presentation/registration/credentials_screen/credentials_screen.dart +++ b/lib/presentation/registration/credentials_screen/credentials_screen.dart @@ -1,3 +1,5 @@ +import 'package:go_router/go_router.dart'; + import 'bloc/credentials_bloc.dart'; import 'models/credentials_model.dart'; import 'package:eatvio_app/core/app_export.dart'; @@ -10,9 +12,7 @@ import 'package:eatvio_app/widgets/custom_text_form_field.dart'; import 'package:flutter/material.dart'; class CredentialsScreen extends StatelessWidget { - static Widget builder(BuildContext context) { - final sheet = - ModalRoute.of(context)!.settings.arguments as UserRegistrationSheet; + static Widget builder(BuildContext context, sheet) { return BlocProvider( create: (context) => CredentialsBloc( CredentialsState( @@ -40,45 +40,33 @@ class CredentialsScreen extends StatelessWidget { decoration: BoxDecoration( color: theme.colorScheme.onPrimary, borderRadius: BorderRadius.circular(8.h), - border: Border.all( - color: theme.colorScheme.primary, width: 1.h)), + border: Border.all(color: theme.colorScheme.primary, width: 1.h)), child: ClipRRect( borderRadius: BorderRadius.circular(8.h), child: LinearProgressIndicator( - value: 0.05, - backgroundColor: theme.colorScheme.onPrimary, - valueColor: AlwaysStoppedAnimation( - appTheme.blueA400)))), + value: 0.05, backgroundColor: theme.colorScheme.onPrimary, valueColor: AlwaysStoppedAnimation(appTheme.blueA400)))), SizedBox(height: 39.v), - Text("user_registration".tr, - style: theme.textTheme.headlineSmall), + Text("user_registration".tr, style: theme.textTheme.headlineSmall), SizedBox(height: 16.v), Text("email".tr, style: theme.textTheme.bodyLarge), SizedBox(height: 3.v), - BlocSelector( + BlocSelector( selector: (state) => state.emailController, builder: (context, emailController) { - return CustomTextFormField( - controller: emailController, - textInputAction: TextInputAction.done); + return CustomTextFormField(controller: emailController, textInputAction: TextInputAction.done); }), SizedBox(height: 22.v), Text("username".tr, style: theme.textTheme.bodyLarge), SizedBox(height: 3.v), - BlocSelector( + BlocSelector( selector: (state) => state.nameController, builder: (context, nameController) { - return CustomTextFormField( - controller: nameController, - textInputAction: TextInputAction.done); + return CustomTextFormField(controller: nameController, textInputAction: TextInputAction.done); }), SizedBox(height: 19.v), Text("password".tr, style: theme.textTheme.bodyLarge), SizedBox(height: 5.v), - BlocSelector( + BlocSelector( selector: (state) => state.passwordController, builder: (context, passwordController) { return CustomTextFormField( @@ -87,25 +75,18 @@ class CredentialsScreen extends StatelessWidget { ); }), SizedBox(height: 21.v), - Text("password_confirmation".tr, - style: theme.textTheme.bodyLarge), + Text("password_confirmation".tr, style: theme.textTheme.bodyLarge), SizedBox(height: 3.v), - BlocSelector( + BlocSelector( selector: (state) => state.passwordConfirmationController, builder: (context, passwordConfirmationController) { - return CustomTextFormField( - obscureText: true, - controller: passwordConfirmationController, - textInputAction: TextInputAction.done); + return CustomTextFormField(obscureText: true, controller: passwordConfirmationController, textInputAction: TextInputAction.done); }), Spacer(), - BlocBuilder( - builder: (context, state) { + BlocBuilder(builder: (context, state) { return NextStepButton( text: "continue".tr.toUpperCase(), - margin: - EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v), + margin: EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v), buttonStyle: CustomButtonStyles.outlinePrimaryTL15, onPressed: onTapContinue(state, context)); }), @@ -114,23 +95,15 @@ class CredentialsScreen extends StatelessWidget { onTapContinue(CredentialsState state, BuildContext context) { return () { - List errors = validateFields( - state.emailController!.text, - state.nameController!.text, - state.passwordController!.text, - state.passwordConfirmationController!.text); + List errors = + validateFields(state.emailController!.text, state.nameController!.text, state.passwordController!.text, state.passwordConfirmationController!.text); if (errors.isEmpty) { - UserRegistrationSheet sheet = - state.credentialsModelObj!.userRegistrationSheet; + UserRegistrationSheet sheet = state.credentialsModelObj!.userRegistrationSheet; sheet.email = state.emailController!.text; sheet.name = state.nameController!.text; sheet.password = state.passwordController!.text; sheet.passwordConfirmation = state.passwordConfirmationController!.text; - Navigator.pushNamed( - context, - AppRoutes.nameScreen, - arguments: sheet, - ); + GoRouter.of(context).goNamed('nameScreen', extra: sheet); } else { String errorMessage = ''; @@ -163,14 +136,10 @@ class CredentialsScreen extends StatelessWidget { /// Section Widget _buildAppBar(BuildContext context) { - return CustomAppBar( - leadingWidth: 29.h, - centerTitle: true, - title: AppbarTitleImage(imagePath: ImageConstant.imgLogoEatvio)); + return CustomAppBar(leadingWidth: 29.h, centerTitle: true, title: AppbarTitleImage(imagePath: ImageConstant.imgLogoEatvio)); } - List validateFields( - String email, String name, String password, String passwordConfirmation) { + List validateFields(String email, String name, String password, String passwordConfirmation) { List errors = []; if (!isEmail(email)) { diff --git a/lib/presentation/registration/date_of_birth_screen/date_of_birth_screen.dart b/lib/presentation/registration/date_of_birth_screen/date_of_birth_screen.dart index df85987..5f68a3d 100644 --- a/lib/presentation/registration/date_of_birth_screen/date_of_birth_screen.dart +++ b/lib/presentation/registration/date_of_birth_screen/date_of_birth_screen.dart @@ -1,3 +1,5 @@ +import 'package:go_router/go_router.dart'; + import 'bloc/date_of_birth_bloc.dart'; import 'models/date_of_birth_model.dart'; import 'package:eatvio_app/core/app_export.dart'; @@ -11,15 +13,17 @@ import 'package:flutter/material.dart'; class DateOfBirthScreen extends StatefulWidget { const DateOfBirthScreen({Key? key}) : super(key: key); - static Widget builder(BuildContext context) { - final sheet = - ModalRoute.of(context)!.settings.arguments as UserRegistrationSheet; + static Widget builder(BuildContext context, sheet) { return BlocProvider( - create: (context) => DateOfBirthBloc(DateOfBirthState( - dateOfBirthModelObj: - DateOfBirthModel(userRegistrationSheet: sheet))) - ..add(DateOfBirthInitialEvent()), - child: DateOfBirthScreen()); + create: (context) => DateOfBirthBloc( + DateOfBirthState( + dateOfBirthModelObj: DateOfBirthModel( + userRegistrationSheet: sheet, + ), + ), + )..add(DateOfBirthInitialEvent()), + child: DateOfBirthScreen(), + ); } @override @@ -28,63 +32,49 @@ class DateOfBirthScreen extends StatefulWidget { class _DateOfBirthScreenState extends State { DateTime selectedDate = DateTime.now(); + @override Widget build(BuildContext context) { mediaQueryData = MediaQuery.of(context); - return BlocBuilder( - builder: (context, state) { - return SafeArea( - child: Scaffold( - appBar: _buildAppBar(context), - body: Container( - width: double.maxFinite, - padding: - EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v), - child: Column(children: [ - Container( - height: 16.v, - width: 343.h, - decoration: BoxDecoration( - color: theme.colorScheme.onPrimary, - borderRadius: BorderRadius.circular(8.h), - border: Border.all( - color: theme.colorScheme.primary, width: 1.h)), - child: ClipRRect( - borderRadius: BorderRadius.circular(8.h), - child: LinearProgressIndicator( - value: 0.24, - backgroundColor: theme.colorScheme.onPrimary, - valueColor: AlwaysStoppedAnimation( - appTheme.blueA400)))), - SizedBox(height: 38.v), - Text("date_of_birth".tr, - style: theme.textTheme.headlineSmall), - Spacer(flex: 39), - _buildDateInput(context), - Spacer(flex: 60), - BlocBuilder( - builder: (context, state) { - return NextStepButton( - text: "continue".tr.toUpperCase(), - margin: EdgeInsets.only( - left: 16.h, right: 16.h, bottom: 50.v), - buttonStyle: CustomButtonStyles.outlinePrimaryTL15, - onPressed: onTapContinue(state, context)); - }), - ])))); + return BlocBuilder(builder: (context, state) { + return Scaffold( + appBar: _buildAppBar(context), + body: Container( + width: double.maxFinite, + padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v), + child: Column(children: [ + Container( + height: 16.v, + width: 343.h, + decoration: BoxDecoration( + color: theme.colorScheme.onPrimary, + borderRadius: BorderRadius.circular(8.h), + border: Border.all(color: theme.colorScheme.primary, width: 1.h)), + child: ClipRRect( + borderRadius: BorderRadius.circular(8.h), + child: LinearProgressIndicator( + value: 0.24, backgroundColor: theme.colorScheme.onPrimary, valueColor: AlwaysStoppedAnimation(appTheme.blueA400)))), + SizedBox(height: 38.v), + Text("date_of_birth".tr, style: theme.textTheme.headlineSmall), + Spacer(flex: 39), + _buildDateInput(context), + Spacer(flex: 60), + BlocBuilder(builder: (context, state) { + return NextStepButton( + text: "continue".tr.toUpperCase(), + margin: EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v), + buttonStyle: CustomButtonStyles.outlinePrimaryTL15, + onPressed: onTapContinue(state, context)); + }), + ]))); }); } onTapContinue(DateOfBirthState state, BuildContext context) { return () { - UserRegistrationSheet sheet = - state.dateOfBirthModelObj!.userRegistrationSheet; + UserRegistrationSheet sheet = state.dateOfBirthModelObj!.userRegistrationSheet; sheet.birthdate = selectedDate.toString(); - Navigator.pushNamed( - context, - AppRoutes.cityOfLivingScreen, - arguments: sheet, - ); + GoRouter.of(context).goNamed('cityOfLivingScreen', extra: sheet); }; } @@ -92,10 +82,8 @@ class _DateOfBirthScreenState extends State { _buildAppBar(BuildContext context) { return CustomAppBar( leadingWidth: 29.h, - leading: AppbarLeadingImage( - imagePath: ImageConstant.imgArrowLeft, - margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v), - onTap: onTapBack(context)), + leading: + AppbarLeadingImage(imagePath: ImageConstant.imgArrowLeft, margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v), onTap: onTapBack(context)), centerTitle: true, title: AppbarTitleImage(imagePath: ImageConstant.imgLogoEatvio)); } @@ -111,28 +99,17 @@ class _DateOfBirthScreenState extends State { return Container( width: 343.h, padding: EdgeInsets.symmetric(horizontal: 4.h, vertical: 1.v), - decoration: AppDecoration.outlineBlueA - .copyWith(borderRadius: BorderRadiusStyle.circleBorder8), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - CustomImageView( - imagePath: ImageConstant.imgFavorite, - height: 12.adaptSize, - width: 12.adaptSize, - radius: BorderRadius.circular(1.h)), - SizedBox(height: 6.v), - Align( - alignment: Alignment.center, - child: GestureDetector( - onTap: onTapCalendar(context), - child: Text( - '${selectedDate.day}.${selectedDate.month}.${selectedDate.year}', - style: theme.textTheme.titleMedium))), - SizedBox(height: 6.v) - ])); + decoration: AppDecoration.outlineBlueA.copyWith(borderRadius: BorderRadiusStyle.circleBorder8), + child: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ + CustomImageView(imagePath: ImageConstant.imgFavorite, height: 12.adaptSize, width: 12.adaptSize, radius: BorderRadius.circular(1.h)), + SizedBox(height: 6.v), + Align( + alignment: Alignment.center, + child: GestureDetector( + onTap: onTapCalendar(context), + child: Text('${selectedDate.day}.${selectedDate.month}.${selectedDate.year}', style: theme.textTheme.titleMedium))), + SizedBox(height: 6.v) + ])); } onTapCalendar(BuildContext context) { @@ -143,7 +120,7 @@ class _DateOfBirthScreenState extends State { /// Navigates to the previous screen. onTapArrowLeft(BuildContext context) { - NavigatorService.goBack(); + GoRouter.of(context).pop(); } /// Displays a date picker dialog to update the selected date diff --git a/lib/presentation/registration/favorite_foods_screen/favorite_foods_screen.dart b/lib/presentation/registration/favorite_foods_screen/favorite_foods_screen.dart index dac3ebd..94771a1 100644 --- a/lib/presentation/registration/favorite_foods_screen/favorite_foods_screen.dart +++ b/lib/presentation/registration/favorite_foods_screen/favorite_foods_screen.dart @@ -1,3 +1,5 @@ +import 'package:go_router/go_router.dart'; + import 'bloc/favorite_foods_bloc.dart'; import 'models/favorite_foods_model.dart'; import 'package:eatvio_app/core/app_export.dart'; @@ -12,14 +14,10 @@ import 'package:flutter/material.dart'; class FavoriteFoodsScreen extends StatefulWidget { const FavoriteFoodsScreen({Key? key}) : super(key: key); - static Widget builder(BuildContext context) { - final sheet = - ModalRoute.of(context)!.settings.arguments as UserRegistrationSheet; + static Widget builder(BuildContext context, sheet) { return BlocProvider( - create: (context) => FavoriteFoodsBloc(FavoriteFoodsState( - favoriteFoodsModelObj: - FavoriteFoodsModel(userRegistrationSheet: sheet))) - ..add(FavoriteFoodsInitialEvent()), + create: (context) => + FavoriteFoodsBloc(FavoriteFoodsState(favoriteFoodsModelObj: FavoriteFoodsModel(userRegistrationSheet: sheet)))..add(FavoriteFoodsInitialEvent()), child: FavoriteFoodsScreen()); } @@ -30,87 +28,65 @@ class FavoriteFoodsScreen extends StatefulWidget { class _FavoriteFoodsScreenState extends State { @override Widget build(BuildContext context) { - mediaQueryData = MediaQuery.of(context); - return SafeArea( - child: Scaffold( - resizeToAvoidBottomInset: false, - appBar: _buildAppBar(context), - body: Container( - width: double.maxFinite, - padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v), - child: Column(children: [ - SizedBox(height: 7.v), - Container( - height: 16.v, - width: 343.h, - decoration: BoxDecoration( - color: theme.colorScheme.onPrimary, - borderRadius: BorderRadius.circular(8.h), - border: Border.all( - color: theme.colorScheme.primary, width: 1.h)), - child: ClipRRect( - borderRadius: BorderRadius.circular(8.h), - child: LinearProgressIndicator( - value: 0.90, - backgroundColor: theme.colorScheme.onPrimary, - valueColor: AlwaysStoppedAnimation( - appTheme.blueA400)))), - SizedBox(height: 19.v), - Container( - width: 327.h, - margin: EdgeInsets.only(left: 8.h, right: 7.h), - child: Text("favorite_unfavorite_foods".tr, - maxLines: 2, - overflow: TextOverflow.ellipsis, - textAlign: TextAlign.center, - style: theme.textTheme.headlineSmall)), - SizedBox(height: 13.v), - _buildTextFormFieldFav(context), - SizedBox(height: 12.v), - _buildTextFormFieldUnfav(context), - Spacer(), - BlocBuilder( - builder: (context, state) { - return NextStepButton( - text: "continue".tr.toUpperCase(), - margin: EdgeInsets.only( - left: 16.h, right: 16.h, bottom: 40.v), - buttonStyle: CustomButtonStyles.outlinePrimaryTL15, - onPressed: onTapContinue(state, context)); - }), - BlocBuilder( - builder: (context, state) { - return GestureDetector( - onTap: onTapSkip(state, context), - child: Text("skip".tr, style: theme.textTheme.bodyLarge), - ); - }), - ])))); + return Scaffold( + resizeToAvoidBottomInset: false, + appBar: _buildAppBar(context), + body: Container( + width: double.maxFinite, + padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v), + child: Column(children: [ + SizedBox(height: 7.v), + Container( + height: 16.v, + width: 343.h, + decoration: BoxDecoration( + color: theme.colorScheme.onPrimary, + borderRadius: BorderRadius.circular(8.h), + border: Border.all(color: theme.colorScheme.primary, width: 1.h)), + child: ClipRRect( + borderRadius: BorderRadius.circular(8.h), + child: LinearProgressIndicator( + value: 0.90, backgroundColor: theme.colorScheme.onPrimary, valueColor: AlwaysStoppedAnimation(appTheme.blueA400)))), + SizedBox(height: 19.v), + Container( + width: 327.h, + margin: EdgeInsets.only(left: 8.h, right: 7.h), + child: Text("favorite_unfavorite_foods".tr, + maxLines: 2, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, style: theme.textTheme.headlineSmall)), + SizedBox(height: 13.v), + _buildTextFormFieldFav(context), + SizedBox(height: 12.v), + _buildTextFormFieldUnfav(context), + Spacer(), + BlocBuilder(builder: (context, state) { + return NextStepButton( + text: "continue".tr.toUpperCase(), + margin: EdgeInsets.only(left: 16.h, right: 16.h, bottom: 40.v), + buttonStyle: CustomButtonStyles.outlinePrimaryTL15, + onPressed: onTapContinue(state, context)); + }), + BlocBuilder(builder: (context, state) { + return GestureDetector( + onTap: onTapSkip(state, context), + child: Text("skip".tr, style: theme.textTheme.bodyLarge), + ); + }), + ]))); } onTapSkip(FavoriteFoodsState state, BuildContext context) { return () { - UserRegistrationSheet sheet = - state.favoriteFoodsModelObj!.userRegistrationSheet; - Navigator.pushNamed( - context, - AppRoutes.additionalInformationScreen, - arguments: sheet, - ); + UserRegistrationSheet sheet = state.favoriteFoodsModelObj!.userRegistrationSheet; + GoRouter.of(context).goNamed('additionalInformationScreen', extra: sheet); }; } onTapContinue(FavoriteFoodsState state, BuildContext context) { return () { - UserRegistrationSheet sheet = - state.favoriteFoodsModelObj!.userRegistrationSheet; + UserRegistrationSheet sheet = state.favoriteFoodsModelObj!.userRegistrationSheet; sheet.favoriteFoods = state.favoriteFoodsController!.text; sheet.unfavoriteFoods = state.unfavoriteFoodsController!.text; - Navigator.pushNamed( - context, - AppRoutes.additionalInformationScreen, - arguments: sheet, - ); + GoRouter.of(context).goNamed('additionalInformationScreen', extra: sheet); }; } @@ -118,10 +94,8 @@ class _FavoriteFoodsScreenState extends State { _buildAppBar(BuildContext context) { return CustomAppBar( leadingWidth: 31.h, - leading: AppbarLeadingImage( - imagePath: ImageConstant.imgArrowLeft, - margin: EdgeInsets.only(left: 18.h, top: 15.v, bottom: 15.v), - onTap: onTapBack(context)), + leading: + AppbarLeadingImage(imagePath: ImageConstant.imgArrowLeft, margin: EdgeInsets.only(left: 18.h, top: 15.v, bottom: 15.v), onTap: onTapBack(context)), centerTitle: true, title: AppbarTitleImage(imagePath: ImageConstant.imgLogoEatvio)); } @@ -137,14 +111,10 @@ class _FavoriteFoodsScreenState extends State { return Column(children: [ Text("favorite_foods".tr, style: CustomTextStyles.titleMediumSegoeUI), SizedBox(height: 2.v), - BlocSelector( + BlocSelector( selector: (state) => state.favoriteFoodsController, builder: (context, favoriteFoodsController) { - return CustomTextFormField( - controller: favoriteFoodsController, - hintText: "enter_information".tr, - maxLines: 7); + return CustomTextFormField(controller: favoriteFoodsController, hintText: "enter_information".tr, maxLines: 7); }) ]); } @@ -154,28 +124,22 @@ class _FavoriteFoodsScreenState extends State { return Column(children: [ Text("unfavorite_foods".tr, style: CustomTextStyles.titleMediumSegoeUI), SizedBox(height: 2.v), - BlocSelector( + BlocSelector( selector: (state) => state.unfavoriteFoodsController, builder: (context, unfavoriteFoodsController) { return CustomTextFormField( - controller: unfavoriteFoodsController, - hintText: "enter_information".tr, - textInputAction: TextInputAction.done, - maxLines: 7); + controller: unfavoriteFoodsController, hintText: "enter_information".tr, textInputAction: TextInputAction.done, maxLines: 7); }) ]); } /// Navigates to the previous screen. onTapArrowLeft(BuildContext context) { - NavigatorService.goBack(); + GoRouter.of(context).pop(); } /// Navigates to the scheduleScreen when the action is triggered. navigateToAdditionalInformationScreen(BuildContext context) { - NavigatorService.pushNamed( - AppRoutes.additionalInformationScreen, - ); + GoRouter.of(context).goNamed('additionalInformationScreen'); } } diff --git a/lib/presentation/registration/favorite_sports_screen/favorite_sports_screen.dart b/lib/presentation/registration/favorite_sports_screen/favorite_sports_screen.dart index e4c6f1c..66f98d2 100644 --- a/lib/presentation/registration/favorite_sports_screen/favorite_sports_screen.dart +++ b/lib/presentation/registration/favorite_sports_screen/favorite_sports_screen.dart @@ -1,3 +1,5 @@ +import 'package:go_router/go_router.dart'; + import 'bloc/favorite_sports_bloc.dart'; import 'models/favorite_sports_model.dart'; import 'package:eatvio_app/core/app_export.dart'; @@ -12,13 +14,9 @@ import 'package:flutter/material.dart'; class FavoriteSportsScreen extends StatefulWidget { const FavoriteSportsScreen({Key? key}) : super(key: key); - static Widget builder(BuildContext context) { - final sheet = - ModalRoute.of(context)!.settings.arguments as UserRegistrationSheet; + static Widget builder(BuildContext context, sheet) { return BlocProvider( - create: (context) => FavoriteSportsBloc(FavoriteSportsState( - favoriteSportsModelObj: - FavoriteSportsModel(userRegistrationSheet: sheet))) + create: (context) => FavoriteSportsBloc(FavoriteSportsState(favoriteSportsModelObj: FavoriteSportsModel(userRegistrationSheet: sheet))) ..add(FavoriteSportsInitialEvent()), child: FavoriteSportsScreen()); } @@ -30,92 +28,66 @@ class FavoriteSportsScreen extends StatefulWidget { class _FavoriteSportsScreenState extends State { @override Widget build(BuildContext context) { - mediaQueryData = MediaQuery.of(context); - return SafeArea( - child: Scaffold( - resizeToAvoidBottomInset: false, - appBar: _buildAppBar(context), - body: Container( - width: double.maxFinite, - padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 5.v), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SizedBox(height: 20.v), - Container( - height: 16.v, - width: 343.h, - decoration: BoxDecoration( - color: theme.colorScheme.onPrimary, - borderRadius: BorderRadius.circular(8.h), - border: Border.all( - color: theme.colorScheme.primary, - width: 1.h)), - child: ClipRRect( - borderRadius: BorderRadius.circular(8.h), - child: LinearProgressIndicator( - value: 0.83, - backgroundColor: theme.colorScheme.onPrimary, - valueColor: AlwaysStoppedAnimation( - appTheme.blueA400)))), - SizedBox(height: 38.v), - Text("your_favorite_sports".tr, - style: theme.textTheme.headlineSmall), - Spacer(flex: 32), - _buildTextFormFieldSection(context), - Spacer(flex: 67), - BlocBuilder( - builder: (context, state) { - return NextStepButton( - text: "continue".tr.toUpperCase(), - margin: EdgeInsets.only( - left: 16.h, right: 16.h, bottom: 50.v), - buttonStyle: CustomButtonStyles.outlinePrimaryTL15, - onPressed: onTapContinue(state, context)); - }), - BlocBuilder( - builder: (context, state) { - return GestureDetector( - onTap: onTapSkip(state, context), - child: - Text("skip".tr, style: theme.textTheme.bodyLarge), - ); - }), - ])))); + return Scaffold( + resizeToAvoidBottomInset: false, + appBar: _buildAppBar(context), + body: Container( + width: double.maxFinite, + padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 5.v), + child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ + SizedBox(height: 20.v), + Container( + height: 16.v, + width: 343.h, + decoration: BoxDecoration( + color: theme.colorScheme.onPrimary, + borderRadius: BorderRadius.circular(8.h), + border: Border.all(color: theme.colorScheme.primary, width: 1.h)), + child: ClipRRect( + borderRadius: BorderRadius.circular(8.h), + child: LinearProgressIndicator( + value: 0.83, backgroundColor: theme.colorScheme.onPrimary, valueColor: AlwaysStoppedAnimation(appTheme.blueA400)))), + SizedBox(height: 38.v), + Text("your_favorite_sports".tr, style: theme.textTheme.headlineSmall), + Spacer(flex: 32), + _buildTextFormFieldSection(context), + Spacer(flex: 67), + BlocBuilder(builder: (context, state) { + return NextStepButton( + text: "continue".tr.toUpperCase(), + margin: EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v), + buttonStyle: CustomButtonStyles.outlinePrimaryTL15, + onPressed: onTapContinue(state, context)); + }), + BlocBuilder(builder: (context, state) { + return GestureDetector( + onTap: onTapSkip(state, context), + child: Text("skip".tr, style: theme.textTheme.bodyLarge), + ); + }), + ]))); } onTapSkip(FavoriteSportsState state, BuildContext context) { return () { - UserRegistrationSheet sheet = - state.favoriteSportsModelObj!.userRegistrationSheet; - Navigator.pushNamed( - context, - AppRoutes.favoriteFoodsScreen, - arguments: sheet, - ); + UserRegistrationSheet sheet = state.favoriteSportsModelObj!.userRegistrationSheet; + GoRouter.of(context).goNamed('favoriteFoodsScreen', extra: sheet); }; } onTapContinue(FavoriteSportsState state, BuildContext context) { return () { - UserRegistrationSheet sheet = - state.favoriteSportsModelObj!.userRegistrationSheet; + UserRegistrationSheet sheet = state.favoriteSportsModelObj!.userRegistrationSheet; sheet.aboutSportHobbies = state.enterinformationController!.text; - Navigator.pushNamed( - context, - AppRoutes.favoriteFoodsScreen, - arguments: sheet, - ); + GoRouter.of(context).goNamed('favoriteFoodsScreen', extra: sheet); }; } _buildAppBar(BuildContext context) { return CustomAppBar( leadingWidth: 29.h, - leading: AppbarLeadingImage( - imagePath: ImageConstant.imgArrowLeft, - margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v), - onTap: onTapBack(context)), + leading: + AppbarLeadingImage(imagePath: ImageConstant.imgArrowLeft, margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v), onTap: onTapBack(context)), centerTitle: true, title: AppbarTitleImage(imagePath: ImageConstant.imgLogoEatvio)); } @@ -129,11 +101,9 @@ class _FavoriteSportsScreenState extends State { /// Section Widget _buildTextFormFieldSection(BuildContext context) { return Column(children: [ - Text("fill_in_information".tr, - style: CustomTextStyles.titleMediumSegoeUI), + Text("fill_in_information".tr, style: CustomTextStyles.titleMediumSegoeUI), SizedBox(height: 2.v), - BlocSelector( + BlocSelector( selector: (state) => state.enterinformationController, builder: (context, enterinformationController) { return CustomTextFormField( @@ -147,6 +117,6 @@ class _FavoriteSportsScreenState extends State { } onTapArrowLeft(BuildContext context) { - NavigatorService.goBack(); + GoRouter.of(context).pop(); } } diff --git a/lib/presentation/registration/gender_screen/gender_screen.dart b/lib/presentation/registration/gender_screen/gender_screen.dart index 1db99ac..68909d7 100644 --- a/lib/presentation/registration/gender_screen/gender_screen.dart +++ b/lib/presentation/registration/gender_screen/gender_screen.dart @@ -1,3 +1,5 @@ +import 'package:go_router/go_router.dart'; + import 'bloc/gender_bloc.dart'; import 'models/gender_model.dart'; import 'package:eatvio_app/data/models/userRegistrationModel/user_registration_model.dart'; @@ -11,9 +13,7 @@ import 'package:flutter/material.dart'; class GenderScreen extends StatefulWidget { const GenderScreen({Key? key}) : super(key: key); - static Widget builder(BuildContext context) { - final sheet = - ModalRoute.of(context)!.settings.arguments as UserRegistrationSheet; + static Widget builder(BuildContext context, sheet) { return BlocProvider( create: (context) => GenderBloc( GenderState( @@ -32,66 +32,57 @@ class _GenderScreenState extends State { Widget build(BuildContext context) { mediaQueryData = MediaQuery.of(context); return BlocBuilder(builder: (context, state) { - return SafeArea( - child: Scaffold( - appBar: _buildAppBar(context), - body: Container( - width: double.maxFinite, - padding: - EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v), - child: Column(children: [ - SizedBox(height: 7.v), - Container( - height: 16.v, - width: 343.h, - decoration: BoxDecoration( - color: theme.colorScheme.onPrimary, - borderRadius: BorderRadius.circular(8.h), - border: Border.all( - color: theme.colorScheme.primary, width: 1.h)), - child: ClipRRect( - borderRadius: BorderRadius.circular(8.h), - child: LinearProgressIndicator( - value: 0.16, - backgroundColor: theme.colorScheme.onPrimary, - valueColor: AlwaysStoppedAnimation( - appTheme.blueA400)))), - SizedBox(height: 36.v), - Text("your_gender".tr, - style: theme.textTheme.headlineSmall), - RadioListTile( - title: Text('Мужской'), - value: 'male', - groupValue: state.selectedRadioValue, - onChanged: onMale(context), - secondary: Image.asset( - 'assets/images/img_male.png', - width: 40, - height: 40, - ), - ), - RadioListTile( - title: Text('Женский'), - value: 'female', - groupValue: state.selectedRadioValue, - onChanged: onFemale(context), - secondary: Image.asset( - 'assets/images/img_female.png', - width: 40, - height: 40, - ), - ), - Spacer(flex: 66), - BlocBuilder( - builder: (context, state) { - return NextStepButton( - text: "continue".tr.toUpperCase(), - margin: EdgeInsets.only( - left: 16.h, right: 16.h, bottom: 50.v), - buttonStyle: CustomButtonStyles.outlinePrimaryTL15, - onPressed: onTapNext(state, context)); - }), - ])))); + return Scaffold( + appBar: _buildAppBar(context), + body: Container( + width: double.maxFinite, + padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v), + child: Column(children: [ + SizedBox(height: 7.v), + Container( + height: 16.v, + width: 343.h, + decoration: BoxDecoration( + color: theme.colorScheme.onPrimary, + borderRadius: BorderRadius.circular(8.h), + border: Border.all(color: theme.colorScheme.primary, width: 1.h)), + child: ClipRRect( + borderRadius: BorderRadius.circular(8.h), + child: LinearProgressIndicator( + value: 0.16, backgroundColor: theme.colorScheme.onPrimary, valueColor: AlwaysStoppedAnimation(appTheme.blueA400)))), + SizedBox(height: 36.v), + Text("your_gender".tr, style: theme.textTheme.headlineSmall), + RadioListTile( + title: Text('Мужской'), + value: 'male', + groupValue: state.selectedRadioValue, + onChanged: onMale(context), + secondary: Image.asset( + 'assets/images/img_male.png', + width: 40, + height: 40, + ), + ), + RadioListTile( + title: Text('Женский'), + value: 'female', + groupValue: state.selectedRadioValue, + onChanged: onFemale(context), + secondary: Image.asset( + 'assets/images/img_female.png', + width: 40, + height: 40, + ), + ), + Spacer(flex: 66), + BlocBuilder(builder: (context, state) { + return NextStepButton( + text: "continue".tr.toUpperCase(), + margin: EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v), + buttonStyle: CustomButtonStyles.outlinePrimaryTL15, + onPressed: onTapNext(state, context)); + }), + ]))); }); } @@ -99,50 +90,42 @@ class _GenderScreenState extends State { return () { UserRegistrationSheet sheet = state.genderModelObj!.userRegistrationSheet; sheet.gender = state.selectedRadioValue!; - Navigator.pushNamed( - context, - AppRoutes.dateOfBirthScreen, - arguments: sheet, - ); + GoRouter.of(context).goNamed('dateOfBirthScreen', extra: sheet); }; } onMale(BuildContext context) { return (value) { - BlocProvider.of(context) - .add(GenderSelectEvent(value: 'male')); + BlocProvider.of(context).add(GenderSelectEvent(value: 'male')); }; } onFemale(BuildContext context) { return (value) { - BlocProvider.of(context) - .add(GenderSelectEvent(value: 'female')); + BlocProvider.of(context).add(GenderSelectEvent(value: 'female')); }; } /// Section Widget _buildAppBar(BuildContext context) { return CustomAppBar( - leadingWidth: 29.h, - leading: AppbarLeadingImage( - imagePath: ImageConstant.imgArrowLeft, - margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v), - onTap: onTapBack()), - centerTitle: true, - title: AppbarTitleImage(imagePath: ImageConstant.imgLogoEatvio)); + leadingWidth: 29.h, + leading: AppbarLeadingImage(imagePath: ImageConstant.imgArrowLeft, margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v), onTap: onTapBack()), + centerTitle: true, + title: AppbarTitleImage( + imagePath: ImageConstant.imgLogoEatvio, + ), + ); } Function onTapBack() { return () { - NavigatorService.pushNamed( - AppRoutes.dateOfBirthScreen, - ); + GoRouter.of(context).pop(); }; } /// Navigates to the previous screen. onTapArrowLeft(BuildContext context) { - NavigatorService.goBack(); + GoRouter.of(context).pop(); } } diff --git a/lib/presentation/registration/height_screen/height_screen.dart b/lib/presentation/registration/height_screen/height_screen.dart index f5bafcb..071285f 100644 --- a/lib/presentation/registration/height_screen/height_screen.dart +++ b/lib/presentation/registration/height_screen/height_screen.dart @@ -1,3 +1,5 @@ +import 'package:go_router/go_router.dart'; + import 'bloc/height_bloc.dart'; import 'models/height_model.dart'; import 'package:eatvio_app/core/app_export.dart'; @@ -13,98 +15,91 @@ import 'package:flutter/material.dart'; class HeightScreen extends StatelessWidget { const HeightScreen({Key? key}) : super(key: key); - static Widget builder(BuildContext context) { - final sheet = - ModalRoute.of(context)!.settings.arguments as UserRegistrationSheet; + static Widget builder(BuildContext context, sheet) { return BlocProvider( - create: (context) => HeightBloc(HeightState( - heightModelObj: HeightModel(userRegistrationSheet: sheet))) - ..add(HeightInitialEvent()), + create: (context) => HeightBloc(HeightState(heightModelObj: HeightModel(userRegistrationSheet: sheet)))..add(HeightInitialEvent()), child: HeightScreen()); } @override Widget build(BuildContext context) { mediaQueryData = MediaQuery.of(context); - return SafeArea( - child: Scaffold( - appBar: _buildAppBar(context), - body: Container( - width: double.maxFinite, - padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v), - child: Column( - children: [ - SizedBox(height: 7.v), - Container( - height: 16.v, - width: 343.h, - decoration: BoxDecoration( - color: theme.colorScheme.onPrimary, - borderRadius: BorderRadius.circular(8.h), - border: Border.all( - color: theme.colorScheme.primary, - width: 1.h, - ), + return Scaffold( + appBar: _buildAppBar(context), + body: Container( + width: double.maxFinite, + padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v), + child: Column( + children: [ + SizedBox(height: 7.v), + Container( + height: 16.v, + width: 343.h, + decoration: BoxDecoration( + color: theme.colorScheme.onPrimary, + borderRadius: BorderRadius.circular(8.h), + border: Border.all( + color: theme.colorScheme.primary, + width: 1.h, ), - child: ClipRRect( - borderRadius: BorderRadius.circular(8.h), - child: LinearProgressIndicator( - value: 0.55, - backgroundColor: theme.colorScheme.onPrimary, - valueColor: AlwaysStoppedAnimation( - appTheme.blueA400, - ), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(8.h), + child: LinearProgressIndicator( + value: 0.55, + backgroundColor: theme.colorScheme.onPrimary, + valueColor: AlwaysStoppedAnimation( + appTheme.blueA400, ), ), ), - SizedBox(height: 39.v), - Column( - children: [ - Text( - "height_measurements".tr, - style: theme.textTheme.headlineSmall, - ), - SizedBox(height: 174.v), - Column( - children: [ - Text( - "your_height".tr, - style: theme.textTheme.bodyLarge, - ), - SizedBox(height: 4.v), - BlocBuilder( - builder: (context, state) { - return CustomTextFormField( - width: 150.h, - textInputAction: TextInputAction.done, - controller: state.heightController, - keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly, - ], - ); - }) - ], - ), - ], - ), - Spacer(), - BlocBuilder( - builder: (context, state) { - return NextStepButton( - text: "continue".tr.toUpperCase(), - margin: EdgeInsets.only( - left: 16.h, - right: 16.h, - bottom: 50.v, + ), + SizedBox(height: 39.v), + Column( + children: [ + Text( + "height_measurements".tr, + style: theme.textTheme.headlineSmall, + ), + SizedBox(height: 174.v), + Column( + children: [ + Text( + "your_height".tr, + style: theme.textTheme.bodyLarge, ), - buttonStyle: CustomButtonStyles.outlinePrimaryTL15, - onPressed: onTapNext(state, context), - ); - }, - ), - ], - ), + SizedBox(height: 4.v), + BlocBuilder(builder: (context, state) { + return CustomTextFormField( + width: 150.h, + textInputAction: TextInputAction.done, + controller: state.heightController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + ], + ); + }) + ], + ), + ], + ), + Spacer(), + BlocBuilder( + builder: (context, state) { + return NextStepButton( + text: "continue".tr.toUpperCase(), + margin: EdgeInsets.only( + left: 16.h, + right: 16.h, + bottom: 50.v, + ), + buttonStyle: CustomButtonStyles.outlinePrimaryTL15, + onPressed: onTapNext(state, context), + ); + }, + ), + ], ), ), ); @@ -113,11 +108,7 @@ class HeightScreen extends StatelessWidget { onTapNext(HeightState state, BuildContext context) { return () { UserRegistrationSheet sheet = state.heightModelObj!.userRegistrationSheet; - Navigator.pushNamed( - context, - AppRoutes.bloodPressureScreen, - arguments: sheet, - ); + GoRouter.of(context).goNamed('bloodPressureScreen', extra: sheet); }; } @@ -125,10 +116,8 @@ class HeightScreen extends StatelessWidget { _buildAppBar(BuildContext context) { return CustomAppBar( leadingWidth: 29.h, - leading: AppbarLeadingImage( - imagePath: ImageConstant.imgArrowLeft, - margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v), - onTap: onTapBack(context)), + leading: + AppbarLeadingImage(imagePath: ImageConstant.imgArrowLeft, margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v), onTap: onTapBack(context)), centerTitle: true, title: AppbarTitleImage(imagePath: ImageConstant.imgLogoEatvio)); } @@ -141,6 +130,6 @@ class HeightScreen extends StatelessWidget { /// Navigates to the previous screen. onTapArrowLeft(BuildContext context) { - NavigatorService.goBack(); + GoRouter.of(context).pop(); } } diff --git a/lib/presentation/registration/language_select_screen/language_select_screen.dart b/lib/presentation/registration/language_select_screen/language_select_screen.dart index 56e644a..75a7c1a 100644 --- a/lib/presentation/registration/language_select_screen/language_select_screen.dart +++ b/lib/presentation/registration/language_select_screen/language_select_screen.dart @@ -1,4 +1,5 @@ import 'package:eatvio_app/data/models/userRegistrationModel/user_registration_model.dart'; +import 'package:go_router/go_router.dart'; import 'bloc/language_select_bloc.dart'; import 'models/language_select_model.dart'; @@ -9,101 +10,107 @@ import 'package:flutter/material.dart'; class LanguageSelectScreen extends StatelessWidget { const LanguageSelectScreen({Key? key}) : super(key: key); + static Widget builder(BuildContext context) { return BlocProvider( - create: (context) => LanguageSelectBloc( - LanguageSelectState(languageSelectModelObj: LanguageSelectModel())) - ..add(LanguageSelectInitialEvent()), - child: LanguageSelectScreen()); + create: (context) => LanguageSelectBloc( + LanguageSelectState( + languageSelectModelObj: LanguageSelectModel(), + ), + )..add(LanguageSelectInitialEvent()), + child: LanguageSelectScreen(), + ); } @override Widget build(BuildContext context) { - mediaQueryData = MediaQuery.of(context); - return SafeArea( - child: Scaffold( - body: Container( - width: double.maxFinite, - padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 5.v), - child: Column(children: [ - CustomImageView( - imagePath: ImageConstant.imgLogoEatvio, - height: 50.adaptSize, - width: 50.adaptSize), - SizedBox(height: 20.v), - Container( - height: 16.v, - width: 343.h, + return Scaffold( + body: Container( + width: double.maxFinite, + padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 5.v), + child: Column( + children: [ + CustomImageView(imagePath: ImageConstant.imgLogoEatvio, height: 50.adaptSize, width: 50.adaptSize), + SizedBox(height: 20.v), + Container( + height: 16.v, + width: 343.h, + decoration: BoxDecoration( + color: theme.colorScheme.onPrimary, + borderRadius: BorderRadius.circular(8.h), + border: Border.all( + color: theme.colorScheme.primary, + width: 1.h, + ), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(8.h), + child: LinearProgressIndicator( + value: 0, + backgroundColor: theme.colorScheme.onPrimary, + valueColor: AlwaysStoppedAnimation(appTheme.blueA400), + ), + ), + ), + SizedBox(height: 20.v), + Container( + width: 262.h, + margin: EdgeInsets.only(left: 41.h, right: 39.h), + child: Text( + "preferred_language".tr, + maxLines: 2, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, + style: theme.textTheme.headlineSmall, + ), + ), + Spacer(flex: 36), + BlocSelector( + selector: (state) => state.languageSelectModelObj, + builder: ( + context, + languageSelectModelObj, + ) { + return CustomDropDown( + icon: Container( + margin: EdgeInsets.fromLTRB(30.h, 11.v, 17.h, 11.v), decoration: BoxDecoration( - color: theme.colorScheme.onPrimary, - borderRadius: BorderRadius.circular(8.h), - border: Border.all( - color: theme.colorScheme.primary, width: 1.h)), - child: ClipRRect( - borderRadius: BorderRadius.circular(8.h), - child: LinearProgressIndicator( - value: 0, - backgroundColor: theme.colorScheme.onPrimary, - valueColor: AlwaysStoppedAnimation( - appTheme.blueA400)))), - SizedBox(height: 20.v), - Container( - width: 262.h, - margin: EdgeInsets.only(left: 41.h, right: 39.h), - child: Text("preferred_language".tr, - maxLines: 2, - overflow: TextOverflow.ellipsis, - textAlign: TextAlign.center, - style: theme.textTheme.headlineSmall)), - Spacer(flex: 36), - BlocSelector( - selector: (state) => state.languageSelectModelObj, - builder: ( - context, - languageSelectModelObj, - ) { - return CustomDropDown( - icon: Container( - margin: EdgeInsets.fromLTRB(30.h, 11.v, 17.h, 11.v), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(1.h), - ), - child: CustomImageView( - imagePath: ImageConstant.imgFavorite, - height: 12.adaptSize, - width: 12.adaptSize, - ), - ), - hintText: "Не выбрано".tr, - items: languageSelectModelObj?.dropdownItemList ?? [], - onChanged: (value) => - _onDropDownChanged(context, value), - ); - }), - Spacer(flex: 63), - ])), - bottomNavigationBar: - BlocBuilder( - builder: (context, state) { - return NextStepButton( - text: "continue".tr.toUpperCase(), - margin: - EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v), - buttonStyle: CustomButtonStyles.outlinePrimaryTL15, - onPressed: _saveAndPush(state, context)); - }))); + borderRadius: BorderRadius.circular(1.h), + ), + child: CustomImageView( + imagePath: ImageConstant.imgFavorite, + height: 12.adaptSize, + width: 12.adaptSize, + ), + ), + hintText: "Не выбрано".tr, + items: languageSelectModelObj?.dropdownItemList ?? [], + onChanged: (value) => _onDropDownChanged(context, value), + ); + }), + Spacer(flex: 63), + ], + ), + ), + bottomNavigationBar: BlocBuilder( + builder: (context, state) { + return NextStepButton( + text: "continue".tr.toUpperCase(), + margin: EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v), + buttonStyle: CustomButtonStyles.outlinePrimaryTL15, + onPressed: _saveAndPush(state, context), + ); + }, + ), + ); } - void Function()? _saveAndPush( - LanguageSelectState state, BuildContext context) { + void Function()? _saveAndPush(LanguageSelectState state, BuildContext context) { return () { - UserRegistrationSheet sheet = - state.languageSelectModelObj!.userRegistrationSheet; - Navigator.pushNamed( - context, - AppRoutes.credentialsScreen, - arguments: sheet, + UserRegistrationSheet sheet = state.languageSelectModelObj!.userRegistrationSheet; + GoRouter.of(context).goNamed( + 'credentialsScreen', + extra: sheet, ); }; } diff --git a/lib/presentation/registration/medicine_intake_screen/medicine_intake_screen.dart b/lib/presentation/registration/medicine_intake_screen/medicine_intake_screen.dart index 51df64a..9f01f6f 100644 --- a/lib/presentation/registration/medicine_intake_screen/medicine_intake_screen.dart +++ b/lib/presentation/registration/medicine_intake_screen/medicine_intake_screen.dart @@ -1,3 +1,5 @@ +import 'package:go_router/go_router.dart'; + import 'bloc/medicine_intake_bloc.dart'; import 'models/medicine_intake_model.dart'; import 'package:eatvio_app/core/app_export.dart'; @@ -12,13 +14,9 @@ import 'package:flutter/material.dart'; class MedicineIntakeScreen extends StatefulWidget { const MedicineIntakeScreen({Key? key}) : super(key: key); - static Widget builder(BuildContext context) { - final sheet = - ModalRoute.of(context)!.settings.arguments as UserRegistrationSheet; + static Widget builder(BuildContext context, sheet) { return BlocProvider( - create: (context) => MedicineIntakeBloc(MedicineIntakeState( - medicineIntakeModelObj: - MedicineIntakeModel(userRegistrationSheet: sheet))) + create: (context) => MedicineIntakeBloc(MedicineIntakeState(medicineIntakeModelObj: MedicineIntakeModel(userRegistrationSheet: sheet))) ..add(MedicineIntakeInitialEvent()), child: MedicineIntakeScreen()); } @@ -31,82 +29,67 @@ class _MedicineIntakeScreenState extends State { @override Widget build(BuildContext context) { mediaQueryData = MediaQuery.of(context); - return SafeArea( - child: Scaffold( - resizeToAvoidBottomInset: false, - appBar: _buildAppBar(context), - body: Container( - width: double.maxFinite, - padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v), - child: Column(children: [ - SizedBox(height: 7.v), - Container( - height: 16.v, - width: 343.h, - decoration: BoxDecoration( - color: theme.colorScheme.onPrimary, - borderRadius: BorderRadius.circular(8.h), - border: Border.all( - color: theme.colorScheme.primary, width: 1.h)), - child: ClipRRect( - borderRadius: BorderRadius.circular(8.h), - child: LinearProgressIndicator( - value: 0.69, - backgroundColor: theme.colorScheme.onPrimary, - valueColor: AlwaysStoppedAnimation( - appTheme.blueA400)))), - SizedBox(height: 20.v), - Container( - width: 271.h, - margin: EdgeInsets.only(left: 35.h, right: 36.h), - child: Text("do_you_take_pills".tr, - maxLines: 2, - overflow: TextOverflow.ellipsis, - textAlign: TextAlign.center, - style: theme.textTheme.headlineSmall)), - SizedBox(height: 86.v), - _buildTextFormField(context), - Spacer(), - BlocBuilder( - builder: (context, state) { - return NextStepButton( - text: "continue".tr.toUpperCase(), - margin: EdgeInsets.only( - left: 16.h, right: 16.h, bottom: 50.v), - buttonStyle: CustomButtonStyles.outlinePrimaryTL15, - onPressed: onTapNext(state, context)); - }), - BlocBuilder( - builder: (context, state) { - return GestureDetector( - onTap: onTapSkip(state, context), - child: Text("skip".tr, style: theme.textTheme.bodyLarge), - ); - }), - ])))); + return Scaffold( + resizeToAvoidBottomInset: false, + appBar: _buildAppBar(context), + body: Container( + width: double.maxFinite, + padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v), + child: Column(children: [ + SizedBox(height: 7.v), + Container( + height: 16.v, + width: 343.h, + decoration: BoxDecoration( + color: theme.colorScheme.onPrimary, + borderRadius: BorderRadius.circular(8.h), + border: Border.all(color: theme.colorScheme.primary, width: 1.h)), + child: ClipRRect( + borderRadius: BorderRadius.circular(8.h), + child: LinearProgressIndicator( + value: 0.69, backgroundColor: theme.colorScheme.onPrimary, valueColor: AlwaysStoppedAnimation(appTheme.blueA400)))), + SizedBox(height: 20.v), + Container( + width: 271.h, + margin: EdgeInsets.only(left: 35.h, right: 36.h), + child: Text("do_you_take_pills".tr, + maxLines: 2, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, style: theme.textTheme.headlineSmall)), + SizedBox(height: 86.v), + _buildTextFormField(context), + Spacer(), + BlocBuilder(builder: (context, state) { + return NextStepButton( + text: "continue".tr.toUpperCase(), + margin: EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v), + buttonStyle: CustomButtonStyles.outlinePrimaryTL15, + onPressed: onTapNext(state, context)); + }), + BlocBuilder(builder: (context, state) { + return GestureDetector( + onTap: onTapSkip(state, context), + child: Text("skip".tr, style: theme.textTheme.bodyLarge), + ); + }), + ]))); } onTapSkip(MedicineIntakeState state, BuildContext context) { return () { - UserRegistrationSheet sheet = - state.medicineIntakeModelObj!.userRegistrationSheet; - Navigator.pushNamed( - context, - AppRoutes.bloodPressureScreen, - arguments: sheet, + UserRegistrationSheet sheet = state.medicineIntakeModelObj!.userRegistrationSheet; + GoRouter.of(context).goNamed( + 'bloodPressureScreen', + extra: sheet, ); }; } onTapNext(MedicineIntakeState state, BuildContext context) { return () { - UserRegistrationSheet sheet = - state.medicineIntakeModelObj!.userRegistrationSheet; + UserRegistrationSheet sheet = state.medicineIntakeModelObj!.userRegistrationSheet; sheet.aboutTakingPils = state.enterInformationController!.text; - Navigator.pushNamed( - context, - AppRoutes.bloodPressureScreen, - arguments: sheet, + GoRouter.of(context).goNamed( + 'bloodPressureScreen', + extra: sheet, ); }; } @@ -115,10 +98,8 @@ class _MedicineIntakeScreenState extends State { _buildAppBar(BuildContext context) { return CustomAppBar( leadingWidth: 31.h, - leading: AppbarLeadingImage( - imagePath: ImageConstant.imgArrowLeft, - margin: EdgeInsets.only(left: 18.h, top: 15.v, bottom: 15.v), - onTap: onTapBack(context)), + leading: + AppbarLeadingImage(imagePath: ImageConstant.imgArrowLeft, margin: EdgeInsets.only(left: 18.h, top: 15.v, bottom: 15.v), onTap: onTapBack(context)), centerTitle: true, title: AppbarTitleImage(imagePath: ImageConstant.imgLogoEatvio)); } @@ -132,24 +113,19 @@ class _MedicineIntakeScreenState extends State { /// Section Widget _buildTextFormField(BuildContext context) { return Column(children: [ - Text("fill_in_information".tr, - style: CustomTextStyles.titleMediumSegoeUI), + Text("fill_in_information".tr, style: CustomTextStyles.titleMediumSegoeUI), SizedBox(height: 2.v), - BlocSelector( + BlocSelector( selector: (state) => state.enterInformationController, builder: (context, enterInformationController) { return CustomTextFormField( - controller: enterInformationController, - hintText: "enter_information".tr, - textInputAction: TextInputAction.done, - maxLines: 8); + controller: enterInformationController, hintText: "enter_information".tr, textInputAction: TextInputAction.done, maxLines: 8); }) ]); } /// Navigates to the previous screen. onTapArrowLeft(BuildContext context) { - NavigatorService.goBack(); + GoRouter.of(context).pop(); } } diff --git a/lib/presentation/registration/name_screen/name_screen.dart b/lib/presentation/registration/name_screen/name_screen.dart index 23612d7..e1f0327 100644 --- a/lib/presentation/registration/name_screen/name_screen.dart +++ b/lib/presentation/registration/name_screen/name_screen.dart @@ -1,3 +1,6 @@ +import 'package:eatvio_app/app_router.dart'; +import 'package:go_router/go_router.dart'; + import 'bloc/name_bloc.dart'; import 'models/name_model.dart'; import 'package:eatvio_app/core/app_export.dart'; @@ -11,10 +14,8 @@ import 'package:flutter/material.dart'; class NameScreen extends StatelessWidget { const NameScreen({Key? key}) : super(key: key); - static Widget builder(BuildContext context) { - final sheet = - ModalRoute.of(context)!.settings.arguments as UserRegistrationSheet; + static Widget builder(BuildContext context, sheet) { return BlocProvider( create: (context) => NameBloc( NameState( @@ -27,65 +28,55 @@ class NameScreen extends StatelessWidget { @override Widget build(BuildContext context) { mediaQueryData = MediaQuery.of(context); - return SafeArea( - child: Scaffold( - resizeToAvoidBottomInset: false, - appBar: _buildAppBar(context), - body: Container( - width: double.maxFinite, - padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v), - child: Column(children: [ - SizedBox(height: 7.v), - Container( - height: 16.v, - width: 343.h, - decoration: BoxDecoration( - color: theme.colorScheme.onPrimary, - borderRadius: BorderRadius.circular(8.h), - border: Border.all( - color: theme.colorScheme.primary, width: 1.h)), - child: ClipRRect( - borderRadius: BorderRadius.circular(8.h), - child: LinearProgressIndicator( - value: 0.1, - backgroundColor: theme.colorScheme.onPrimary, - valueColor: AlwaysStoppedAnimation( - appTheme.blueA400)))), - SizedBox(height: 39.v), - Text("what_is_your_name".tr, - style: theme.textTheme.headlineSmall), - SizedBox(height: 93.v), - Text("first_name".tr, style: theme.textTheme.bodyLarge), - SizedBox(height: 3.v), - BlocSelector( - selector: (state) => state.firstNameController, - builder: (context, firstNameController) { - return CustomTextFormField( - controller: firstNameController, - textInputAction: TextInputAction.done); - }), - SizedBox(height: 19.v), - Text("last_name".tr, style: theme.textTheme.bodyLarge), - SizedBox(height: 5.v), - BlocSelector( - selector: (state) => state.lastNameController, - builder: (context, lastNameController) { - return CustomTextFormField( - controller: lastNameController, - ); - }), - Spacer(), - BlocBuilder(builder: (context, state) { - return NextStepButton( - text: "continue".tr.toUpperCase(), - margin: EdgeInsets.only( - left: 16.h, right: 16.h, bottom: 50.v), - buttonStyle: CustomButtonStyles.outlinePrimaryTL15, - onPressed: () => _onTapNext( - context, state), // Assign callback function here + return Scaffold( + resizeToAvoidBottomInset: false, + appBar: _buildAppBar(context), + body: Container( + width: double.maxFinite, + padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v), + child: Column(children: [ + SizedBox(height: 7.v), + Container( + height: 16.v, + width: 343.h, + decoration: BoxDecoration( + color: theme.colorScheme.onPrimary, + borderRadius: BorderRadius.circular(8.h), + border: Border.all(color: theme.colorScheme.primary, width: 1.h)), + child: ClipRRect( + borderRadius: BorderRadius.circular(8.h), + child: LinearProgressIndicator( + value: 0.1, backgroundColor: theme.colorScheme.onPrimary, valueColor: AlwaysStoppedAnimation(appTheme.blueA400)))), + SizedBox(height: 39.v), + Text("what_is_your_name".tr, style: theme.textTheme.headlineSmall), + SizedBox(height: 93.v), + Text("first_name".tr, style: theme.textTheme.bodyLarge), + SizedBox(height: 3.v), + BlocSelector( + selector: (state) => state.firstNameController, + builder: (context, firstNameController) { + return CustomTextFormField(controller: firstNameController, textInputAction: TextInputAction.done); + }), + SizedBox(height: 19.v), + Text("last_name".tr, style: theme.textTheme.bodyLarge), + SizedBox(height: 5.v), + BlocSelector( + selector: (state) => state.lastNameController, + builder: (context, lastNameController) { + return CustomTextFormField( + controller: lastNameController, ); }), - ])))); + Spacer(), + BlocBuilder(builder: (context, state) { + return NextStepButton( + text: "continue".tr.toUpperCase(), + margin: EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v), + buttonStyle: CustomButtonStyles.outlinePrimaryTL15, + onPressed: () => _onTapNext(context, state), // Assign callback function here + ); + }), + ]))); } /// Section Widget @@ -93,33 +84,25 @@ class NameScreen extends StatelessWidget { return CustomAppBar( leadingWidth: 29.h, leading: AppbarLeadingImage( - imagePath: ImageConstant.imgArrowLeft, - margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v), - onTap: () => onTapArrowLeft(context)), + imagePath: ImageConstant.imgArrowLeft, margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v), onTap: () => onTapArrowLeft(context)), centerTitle: true, title: AppbarTitleImage(imagePath: ImageConstant.imgLogoEatvio)); } /// Navigates to the previous screen. onTapArrowLeft(BuildContext context) { - NavigatorService.goBack(); + GoRouter.of(context).pop(); } _onTapNext(BuildContext context, NameState state) { if (state.firstNameController != null && state.lastNameController != null) { - List errors = validateFields( - state.firstNameController!.text, state.lastNameController!.text); + List errors = validateFields(state.firstNameController!.text, state.lastNameController!.text); if (errors.isEmpty) { UserRegistrationSheet sheet = state.nameModelObj!.userRegistrationSheet; sheet.firstName = state.firstNameController!.text; sheet.lastName = state.lastNameController!.text; - - Navigator.pushNamed( - context, - AppRoutes.genderScreen, - arguments: sheet, - ); + AppRouter.navigatorKey.currentContext?.goNamed('genderScreen', extra: sheet); } else { String errorMessage = ''; diff --git a/lib/presentation/registration/splash_screen/bloc/splash_bloc.dart b/lib/presentation/registration/splash_screen/bloc/splash_bloc.dart index 9491b45..bdd396f 100644 --- a/lib/presentation/registration/splash_screen/bloc/splash_bloc.dart +++ b/lib/presentation/registration/splash_screen/bloc/splash_bloc.dart @@ -1,8 +1,12 @@ +import 'package:eatvio_app/app_router.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; import '/core/app_export.dart'; import 'package:eatvio_app/presentation/registration/splash_screen/models/splash_model.dart'; + part 'splash_event.dart'; + part 'splash_state.dart'; /// A bloc that manages the state of a Splash according to the event that is dispatched to it. @@ -16,9 +20,7 @@ class SplashBloc extends Bloc { Emitter emit, ) async { Future.delayed(const Duration(milliseconds: 3000), () { - NavigatorService.popAndPushNamed( - AppRoutes.myProfile, - ); + AppRouter.navigatorKey.currentContext?.goNamed('myProfileScreen'); }); } } diff --git a/lib/presentation/registration/weight_screen/weight_screen.dart b/lib/presentation/registration/weight_screen/weight_screen.dart index c68ccb8..7bf28f1 100644 --- a/lib/presentation/registration/weight_screen/weight_screen.dart +++ b/lib/presentation/registration/weight_screen/weight_screen.dart @@ -1,3 +1,5 @@ +import 'package:go_router/go_router.dart'; + import 'bloc/weight_bloc.dart'; import 'models/weight_model.dart'; import 'package:eatvio_app/core/app_export.dart'; @@ -13,98 +15,90 @@ import 'package:flutter/material.dart'; class WeightScreen extends StatelessWidget { const WeightScreen({Key? key}) : super(key: key); - static Widget builder(BuildContext context) { - final sheet = - ModalRoute.of(context)!.settings.arguments as UserRegistrationSheet; + static Widget builder(BuildContext context, sheet) { return BlocProvider( - create: (context) => WeightBloc(WeightState( - weightModelObj: WeightModel(userRegistrationSheet: sheet))) - ..add(WeightInitialEvent()), + create: (context) => WeightBloc(WeightState(weightModelObj: WeightModel(userRegistrationSheet: sheet)))..add(WeightInitialEvent()), child: WeightScreen()); } @override Widget build(BuildContext context) { - mediaQueryData = MediaQuery.of(context); - return SafeArea( - child: Scaffold( - appBar: _buildAppBar(context), - body: Container( - width: double.maxFinite, - padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v), - child: Column( - children: [ - SizedBox(height: 7.v), - Container( - height: 16.v, - width: 343.h, - decoration: BoxDecoration( - color: theme.colorScheme.onPrimary, - borderRadius: BorderRadius.circular(8.h), - border: Border.all( - color: theme.colorScheme.primary, - width: 1.h, - ), + return Scaffold( + appBar: _buildAppBar(context), + body: Container( + width: double.maxFinite, + padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v), + child: Column( + children: [ + SizedBox(height: 7.v), + Container( + height: 16.v, + width: 343.h, + decoration: BoxDecoration( + color: theme.colorScheme.onPrimary, + borderRadius: BorderRadius.circular(8.h), + border: Border.all( + color: theme.colorScheme.primary, + width: 1.h, ), - child: ClipRRect( - borderRadius: BorderRadius.circular(8.h), - child: LinearProgressIndicator( - value: 0.55, - backgroundColor: theme.colorScheme.onPrimary, - valueColor: AlwaysStoppedAnimation( - appTheme.blueA400, - ), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(8.h), + child: LinearProgressIndicator( + value: 0.55, + backgroundColor: theme.colorScheme.onPrimary, + valueColor: AlwaysStoppedAnimation( + appTheme.blueA400, ), ), ), - SizedBox(height: 39.v), - Column( - children: [ - Text( - "weight_measurements".tr, - style: theme.textTheme.headlineSmall, - ), - SizedBox(height: 174.v), - Column( - children: [ - Text( - "your_weight".tr, - style: theme.textTheme.bodyLarge, - ), - SizedBox(height: 4.v), - BlocBuilder( - builder: (context, state) { - return CustomTextFormField( - width: 150.h, - textInputAction: TextInputAction.done, - controller: state.weightController, - keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly, - ], - ); - }) - ], - ), - ], - ), - Spacer(), - BlocBuilder( - builder: (context, state) { - return NextStepButton( - text: "continue".tr.toUpperCase(), - margin: EdgeInsets.only( - left: 16.h, - right: 16.h, - bottom: 50.v, + ), + SizedBox(height: 39.v), + Column( + children: [ + Text( + "weight_measurements".tr, + style: theme.textTheme.headlineSmall, + ), + SizedBox(height: 174.v), + Column( + children: [ + Text( + "your_weight".tr, + style: theme.textTheme.bodyLarge, ), - buttonStyle: CustomButtonStyles.outlinePrimaryTL15, - onPressed: onTapNext(state, context), - ); - }, - ), - ], - ), + SizedBox(height: 4.v), + BlocBuilder(builder: (context, state) { + return CustomTextFormField( + width: 150.h, + textInputAction: TextInputAction.done, + controller: state.weightController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + ], + ); + }) + ], + ), + ], + ), + Spacer(), + BlocBuilder( + builder: (context, state) { + return NextStepButton( + text: "continue".tr.toUpperCase(), + margin: EdgeInsets.only( + left: 16.h, + right: 16.h, + bottom: 50.v, + ), + buttonStyle: CustomButtonStyles.outlinePrimaryTL15, + onPressed: onTapNext(state, context), + ); + }, + ), + ], ), ), ); @@ -113,11 +107,7 @@ class WeightScreen extends StatelessWidget { onTapNext(WeightState state, BuildContext context) { return () { UserRegistrationSheet sheet = state.weightModelObj!.userRegistrationSheet; - Navigator.pushNamed( - context, - AppRoutes.heightScreen, - arguments: sheet, - ); + GoRouter.of(context).goNamed('heightScreen', extra: sheet); }; } @@ -125,10 +115,8 @@ class WeightScreen extends StatelessWidget { _buildAppBar(BuildContext context) { return CustomAppBar( leadingWidth: 29.h, - leading: AppbarLeadingImage( - imagePath: ImageConstant.imgArrowLeft, - margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v), - onTap: onTapBack(context)), + leading: + AppbarLeadingImage(imagePath: ImageConstant.imgArrowLeft, margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v), onTap: onTapBack(context)), centerTitle: true, title: AppbarTitleImage(imagePath: ImageConstant.imgLogoEatvio)); } @@ -141,6 +129,6 @@ class WeightScreen extends StatelessWidget { /// Navigates to the previous screen. onTapArrowLeft(BuildContext context) { - NavigatorService.goBack(); + GoRouter.of(context).pop(); } } diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart deleted file mode 100644 index 16235b1..0000000 --- a/lib/routes/app_routes.dart +++ /dev/null @@ -1,235 +0,0 @@ -import 'package:eatvio_app/presentation/diets/diet_select_screen/diet_select_screen.dart'; -import 'package:flutter/material.dart'; -import 'package:eatvio_app/presentation/registration/date_of_birth_screen/date_of_birth_screen.dart'; -import 'package:eatvio_app/presentation/registration/city_of_living_screen/city_of_living_screen.dart'; -import 'package:eatvio_app/presentation/registration/body_circumferences_screen/body_circumferences_screen.dart'; -import 'package:eatvio_app/presentation/registration/weight_screen/weight_screen.dart'; -import 'package:eatvio_app/presentation/registration/allergies_screen/allergies_screen.dart'; -import 'package:eatvio_app/presentation/registration/medicine_intake_screen/medicine_intake_screen.dart'; -import 'package:eatvio_app/presentation/registration/splash_screen/splash_screen.dart'; -import 'package:eatvio_app/presentation/registration/gender_screen/gender_screen.dart'; -import 'package:eatvio_app/presentation/registration/blood_pressure_screen/blood_pressure_screen.dart'; -import 'package:eatvio_app/presentation/registration/favorite_sports_screen/favorite_sports_screen.dart'; -import 'package:eatvio_app/presentation/registration/favorite_foods_screen/favorite_foods_screen.dart'; -import 'package:eatvio_app/presentation/registration/additional_information_screen/additional_information_screen.dart'; -import 'package:eatvio_app/presentation/registration/language_select_screen/language_select_screen.dart'; -import 'package:eatvio_app/presentation/registration/height_screen/height_screen.dart'; -import 'package:eatvio_app/presentation/registration/credentials_screen/credentials_screen.dart'; -import 'package:eatvio_app/presentation/registration/name_screen/name_screen.dart'; -import 'package:eatvio_app/presentation/registration/app_navigation_screen/app_navigation_screen.dart'; -import 'package:eatvio_app/presentation/auth/auth_screen/auth_screen.dart'; -import 'package:eatvio_app/presentation/profile/my_profile_screen/my_profile_screen.dart'; -import 'package:eatvio_app/presentation/profile/my_profile_screen/my_settings_screen.dart'; -import 'package:eatvio_app/presentation/diary/diary_edit_screen/diary_edit_screen.dart'; -import 'package:eatvio_app/presentation/chat/chat_screen/chat_screen.dart'; -import 'package:eatvio_app/presentation/chat/chat_select_screen/chat_select_screen.dart'; -import 'package:eatvio_app/presentation/feed/feed_screen/feed_screen.dart'; -import 'package:eatvio_app/presentation/recipe/recipe_select_screen/recipe_select_screen.dart'; -import 'package:eatvio_app/presentation/plans/plan_select_screen/plan_select_screen.dart'; -import 'package:eatvio_app/presentation/plans/plan_edit_screen/plan_edit_screen.dart'; -import 'package:eatvio_app/presentation/plans/plan_create_screen/plan_create_screen.dart'; -import 'package:eatvio_app/presentation/diets/diet_show_screen/diet_show_screen.dart'; -import 'package:eatvio_app/presentation/diets/diet_edit_screen/diet_edit_screen.dart'; -import 'package:eatvio_app/presentation/diets/diet_create_screen/diet_create_screen.dart'; -import 'package:eatvio_app/presentation/plans/plan_show_screen/plan_show_screen.dart'; -import 'package:eatvio_app/presentation/recipe/recipe_show_screen/recipe_show_screen.dart'; -import 'package:eatvio_app/presentation/recipe/recipe_create_screen/recipe_create_screen.dart'; -import 'package:eatvio_app/presentation/recipe/recipe_edit_screen/recipe_edit_screen.dart'; -import 'package:eatvio_app/presentation/feed/blog_post_screen/blog_post_screen.dart'; -import 'package:eatvio_app/presentation/feed/blog_post_edit_screen/blog_post_edit_screen.dart'; -import 'package:eatvio_app/presentation/feed/blog_post_create_screen/blog_post_create_screen.dart'; -import 'package:eatvio_app/presentation/glossary/glossary_screen/glossary_screen.dart'; - -class AppRoutes { - // REGISTRATION - - static const String dateOfBirthScreen = '/date_of_birth_screen'; - - static const String cityOfLivingScreen = '/city_of_living_screen'; - - static const String bodyCircumferencesScreen = '/body_circumferences_screen'; - - static const String weightScreen = '/weight_screen'; - - static const String heightScreen = '/height_screen'; - - static const String allergiesScreen = '/allergies_screen'; - - static const String medicineIntakeScreen = '/medicine_intake_screen'; - - static const String splashScreen = '/splash_screen'; - - static const String genderScreen = '/gender_screen'; - - static const String bloodPressureScreen = '/blood_pressure_screen'; - - static const String favoriteSportsScreen = '/favorite_sports_screen'; - - static const String favoriteFoodsScreen = '/favorite_foods_screen'; - - static const String scheduleScreen = '/schedule_screen'; - - static const String additionalInformationScreen = - '/additional_information_screen'; - - static const String languageSelectScreen = '/language_select_screen'; - - static const String nameScreen = '/name_screen'; - - static const String credentialsScreen = '/credentials_screen'; - - static const String appNavigationScreen = '/app_navigation_screen'; - - static const String authScreen = '/auth_screen'; - - static const String initialRoute = '/initialRoute'; - - //PROFILE - static const String myProfile = '/myProfile'; - - static const String mySettings = '/mySettings'; - - static const String diaryEdit = '/diaryEdit'; - - static const String chatScreen = '/chat'; - - static const String chatSelectScreen = '/chatSelect'; - - static const String feedScreen = '/feed'; - - static const String blogPostScreen = '/blogPost'; - - static const String blogPostEditScreen = '/blogPostEdit'; - - static const String blogPostCreateScreen = '/blogPostCreate'; - - static const String recipeSelectScreen = '/recipeSelect'; - - static const String recipeScreen = '/recipe'; - - static const String recipeEditScreen = '/recipeEdit'; - - static const String recipeCreateScreen = '/recipeCreate'; - - static const String dietSelectScreen = '/dietSelect'; - - static const String dietScreen = '/diet'; - - static const String dietEditScreen = '/dietEdit'; - - static const String dietCreateScreen = '/dietCreate'; - - static const String planSelectScreen = '/planSelect'; - - static const String planScreen = '/plan'; - - static const String planEditScreen = '/planEdit'; - - static const String planCreateScreen = '/planCreate'; - - static const String glossaryScreen = '/glossary'; - - static Map get routes => { - dateOfBirthScreen: (context) => - _buildFadeRoute(context, DateOfBirthScreen.builder), - cityOfLivingScreen: (context) => - _buildFadeRoute(context, CityOfLivingScreen.builder), - bodyCircumferencesScreen: (context) => - _buildFadeRoute(context, BodyCircumferencesScreen.builder), - weightScreen: (context) => - _buildFadeRoute(context, WeightScreen.builder), - heightScreen: (context) => - _buildFadeRoute(context, HeightScreen.builder), - nameScreen: (context) => _buildFadeRoute(context, NameScreen.builder), - credentialsScreen: (context) => - _buildFadeRoute(context, CredentialsScreen.builder), - allergiesScreen: (context) => - _buildFadeRoute(context, AllergiesScreen.builder), - medicineIntakeScreen: (context) => - _buildFadeRoute(context, MedicineIntakeScreen.builder), - splashScreen: (context) => - _buildFadeRoute(context, SplashScreen.builder), - authScreen: (context) => _buildFadeRoute(context, AuthScreen.builder), - genderScreen: (context) => - _buildFadeRoute(context, GenderScreen.builder), - bloodPressureScreen: (context) => - _buildFadeRoute(context, BloodPressureScreen.builder), - favoriteSportsScreen: (context) => - _buildFadeRoute(context, FavoriteSportsScreen.builder), - favoriteFoodsScreen: (context) => - _buildFadeRoute(context, FavoriteFoodsScreen.builder), - additionalInformationScreen: (context) => - _buildFadeRoute(context, AdditionalInformationScreen.builder), - languageSelectScreen: (context) => - _buildFadeRoute(context, LanguageSelectScreen.builder), - appNavigationScreen: (context) => - _buildFadeRoute(context, AppNavigationScreen.builder), - initialRoute: (context) => - _buildFadeRoute(context, SplashScreen.builder), - myProfile: (context) => - _buildFadeRoute(context, MyProfileScreen.builder), - mySettings: (context) => - _buildFadeRoute(context, MySettingsScreen.builder), - diaryEdit: (context) => - _buildFadeRoute(context, DiaryEditScreen.builder), - chatScreen: (context) => _buildFadeRoute(context, ChatScreen.builder), - chatSelectScreen: (context) => - _buildFadeRoute(context, ChatSelectScreen.builder), - recipeSelectScreen: (context) => - _buildFadeRoute(context, RecipeSelectScreen.builder), - recipeScreen: (context) => - _buildFadeRoute(context, RecipeScreen.builder), - recipeEditScreen: (context) => - _buildFadeRoute(context, RecipeEditScreen.builder), - recipeCreateScreen: (context) => - _buildFadeRoute(context, RecipeCreateScreen.builder), - dietSelectScreen: (context) => - _buildFadeRoute(context, DietSelectScreen.builder), - dietScreen: (context) => _buildFadeRoute(context, DietScreen.builder), - dietEditScreen: (context) => - _buildFadeRoute(context, DietEditScreen.builder), - dietCreateScreen: (context) => - _buildFadeRoute(context, DietCreateScreen.builder), - planSelectScreen: (context) => - _buildFadeRoute(context, PlanSelectScreen.builder), - planScreen: (context) => _buildFadeRoute(context, PlanScreen.builder), - planEditScreen: (context) => - _buildFadeRoute(context, PlanEditScreen.builder), - planCreateScreen: (context) => - _buildFadeRoute(context, PlanCreateScreen.builder), - feedScreen: (context) => _buildFadeRoute(context, FeedScreen.builder), - blogPostScreen: (context) => - _buildFadeRoute(context, BlogPostScreen.builder), - blogPostEditScreen: (context) => - _buildFadeRoute(context, BlogPostEditScreen.builder), - blogPostCreateScreen: (context) => - _buildFadeRoute(context, BlogPostCreateScreen.builder), - glossaryScreen: (context) => - _buildFadeRoute(context, GlossaryScreen.builder), - }; - - static Widget _buildFadeRoute(BuildContext context, WidgetBuilder builder) { - return FadeTransition( - opacity: AlwaysStoppedAnimation(1.0), - child: builder(context), - ); - } -} - -class FadeTransitionPage extends PageRouteBuilder { - final WidgetBuilder builder; - - FadeTransitionPage({required this.builder}) - : super( - pageBuilder: (BuildContext context, Animation animation, - Animation secondaryAnimation) { - return builder(context); - }, - transitionsBuilder: (BuildContext context, - Animation animation, - Animation secondaryAnimation, - Widget child) { - return FadeTransition(opacity: animation, child: child); - }, - ); -} diff --git a/lib/theme/custom_button_style.dart b/lib/theme/custom_button_style.dart index bdd84d9..60204f5 100644 --- a/lib/theme/custom_button_style.dart +++ b/lib/theme/custom_button_style.dart @@ -16,7 +16,7 @@ class CustomButtonStyles { ); // text button style static ButtonStyle get none => ButtonStyle( - backgroundColor: MaterialStateProperty.all(Colors.transparent), - elevation: MaterialStateProperty.all(0), + backgroundColor: WidgetStateProperty.all(Colors.transparent), + elevation: WidgetStateProperty.all(0), ); } diff --git a/lib/theme/custom_text_style.dart b/lib/theme/custom_text_style.dart index ff969f9..de3bee7 100644 --- a/lib/theme/custom_text_style.dart +++ b/lib/theme/custom_text_style.dart @@ -8,155 +8,171 @@ import '../core/app_export.dart'; class CustomTextStyles { // Headline style static get headlineSmallMulish => theme.textTheme.headlineSmall!.mulish; + // Title text style - static get titleMediumSegoeUI => - theme.textTheme.titleMedium!.segoeUI.copyWith( + static get titleMediumSegoeUI => theme.textTheme.titleMedium!.segoeUI.copyWith( fontWeight: FontWeight.w600, ); + static get bodyMediumBlack900 => theme.textTheme.bodyMedium!.copyWith( color: appTheme.black900, ); + static get bodyMediumBlack90013 => theme.textTheme.bodyMedium!.copyWith( color: appTheme.black900, fontSize: 13.fSize, ); + static get bodyMediumBluegray800 => theme.textTheme.bodyMedium!.copyWith( color: appTheme.blueGray800, ); + static get bodyMediumBluegray800_1 => theme.textTheme.bodyMedium!.copyWith( color: appTheme.blueGray800, ); + static get bodyMediumBluegray800_2 => theme.textTheme.bodyMedium!.copyWith( color: appTheme.blueGray800, ); + static get bodyMediumWhiteA700 => theme.textTheme.bodyMedium!.copyWith( color: appTheme.whiteA700, ); - static get bodySmallMulishBlack900 => - theme.textTheme.bodySmall!.mulish.copyWith( + + static get bodySmallMulishBlack900 => theme.textTheme.bodySmall!.mulish.copyWith( color: appTheme.black900, fontSize: 12.fSize, ); - static get bodySmallMulishBluegray800 => - theme.textTheme.bodySmall!.mulish.copyWith( + + static get bodySmallMulishBluegray800 => theme.textTheme.bodySmall!.mulish.copyWith( color: appTheme.blueGray800, fontSize: 12.fSize, ); - static get bodySmallMulishBluegray80012 => - theme.textTheme.bodySmall!.mulish.copyWith( + + static get bodySmallMulishBluegray80012 => theme.textTheme.bodySmall!.mulish.copyWith( color: appTheme.blueGray800, fontSize: 12.fSize, ); - static get bodySmallMulishBluegray80012_1 => - theme.textTheme.bodySmall!.mulish.copyWith( + + static get bodySmallMulishBluegray80012_1 => theme.textTheme.bodySmall!.mulish.copyWith( color: appTheme.blueGray800, fontSize: 12.fSize, ); - static get bodySmallMulishBluegray900 => - theme.textTheme.bodySmall!.mulish.copyWith( + + static get bodySmallMulishBluegray900 => theme.textTheme.bodySmall!.mulish.copyWith( color: appTheme.blueGray900, fontSize: 12.fSize, ); - static get bodySmallMulishGray600 => - theme.textTheme.bodySmall!.mulish.copyWith( + + static get bodySmallMulishGray600 => theme.textTheme.bodySmall!.mulish.copyWith( color: appTheme.gray600, ); - static get bodySmallMulishPrimary => - theme.textTheme.bodySmall!.mulish.copyWith( + + static get bodySmallMulishPrimary => theme.textTheme.bodySmall!.mulish.copyWith( color: theme.colorScheme.primary, fontSize: 12.fSize, ); - static get bodySmallSegoeUIBlack900 => - theme.textTheme.bodySmall!.segoeUI.copyWith( + + static get bodySmallSegoeUIBlack900 => theme.textTheme.bodySmall!.segoeUI.copyWith( color: appTheme.black900, fontSize: 12.fSize, ); + // Label text style static get labelLargeGray60003 => theme.textTheme.labelLarge!.copyWith( color: appTheme.gray60003, ); + static get labelLargeWhiteA700 => theme.textTheme.labelLarge!.copyWith( color: appTheme.whiteA700, ); + // Title text style static get titleMedium16 => theme.textTheme.titleMedium!.copyWith( fontSize: 16.fSize, ); + static get titleMediumBlack => theme.textTheme.titleMedium!.copyWith( fontSize: 17.fSize, fontWeight: FontWeight.w900, ); + static get titleMediumBlack900 => theme.textTheme.titleMedium!.copyWith( color: appTheme.black900, ); + static get titleMediumSemiBold => theme.textTheme.titleMedium!.copyWith( fontWeight: FontWeight.w600, ); + static get titleMediumWhiteA700 => theme.textTheme.titleMedium!.copyWith( color: appTheme.whiteA700, fontWeight: FontWeight.w600, ); + static get titleSmallBlue800 => theme.textTheme.titleSmall!.copyWith( color: appTheme.blue800, fontWeight: FontWeight.w800, ); + static get titleSmallBluegray800 => theme.textTheme.titleSmall!.copyWith( color: appTheme.blueGray800, ); + static get titleSmallBluegray800Bold => theme.textTheme.titleSmall!.copyWith( color: appTheme.blueGray800, fontWeight: FontWeight.w700, ); - static get titleSmallBluegray800ExtraBold => - theme.textTheme.titleSmall!.copyWith( + + static get titleSmallBluegray800ExtraBold => theme.textTheme.titleSmall!.copyWith( color: appTheme.blueGray800, fontWeight: FontWeight.w800, ); + static get titleSmallBold => theme.textTheme.titleSmall!.copyWith( fontSize: 15.fSize, fontWeight: FontWeight.w700, ); + static get titleSmallGreenA70001 => theme.textTheme.titleSmall!.copyWith( color: appTheme.greenA70001, ); - static get titleSmallGreenA70001ExtraBold => - theme.textTheme.titleSmall!.copyWith( + + static get titleSmallGreenA70001ExtraBold => theme.textTheme.titleSmall!.copyWith( color: appTheme.greenA70001, fontWeight: FontWeight.w800, ); + static get titleSmallLightgreen300 => theme.textTheme.titleSmall!.copyWith( color: appTheme.lightGreen300, fontWeight: FontWeight.w800, ); + static get titleSmallRed200 => theme.textTheme.titleSmall!.copyWith( color: appTheme.red200, fontWeight: FontWeight.w800, ); - static get titleSmallSecondaryContainer => - theme.textTheme.titleSmall!.copyWith( + + static get titleSmallSecondaryContainer => theme.textTheme.titleSmall!.copyWith( color: theme.colorScheme.secondaryContainer, fontWeight: FontWeight.w800, ); - static get titleSmallSecondaryContainer_1 => - theme.textTheme.titleSmall!.copyWith( + + static get titleSmallSecondaryContainer_1 => theme.textTheme.titleSmall!.copyWith( color: theme.colorScheme.secondaryContainer, ); - static get titleSmallSegoeUIOnPrimary => - theme.textTheme.titleSmall!.segoeUI.copyWith( + + static get titleSmallSegoeUIOnPrimary => theme.textTheme.titleSmall!.segoeUI.copyWith( color: theme.colorScheme.onPrimary, ); } extension on TextStyle { TextStyle get mulish { - return copyWith( - fontFamily: 'Mulish', - ); + return copyWith(); } TextStyle get segoeUI { - return copyWith( - fontFamily: 'Segoe UI', - ); + return copyWith(); } } diff --git a/lib/theme/theme_helper.dart b/lib/theme/theme_helper.dart index 699e282..7226e37 100644 --- a/lib/theme/theme_helper.dart +++ b/lib/theme/theme_helper.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import '../../core/app_export.dart'; /// Helper class for managing themes and colors. @@ -50,6 +51,13 @@ class ThemeHelper { colorScheme: colorScheme, textTheme: TextThemes.textTheme(colorScheme), scaffoldBackgroundColor: colorScheme.onPrimary, + appBarTheme: AppBarTheme( + systemOverlayStyle: SystemUiOverlayStyle( + statusBarIconBrightness: Brightness.dark, + statusBarBrightness: Brightness.light, + systemNavigationBarColor: Colors.white, + ), + ), outlinedButtonTheme: OutlinedButtonThemeData( style: OutlinedButton.styleFrom( backgroundColor: Colors.transparent, @@ -187,6 +195,8 @@ class PrimaryColors { // White Color get whiteA700 => Color(0XFFFFFFFF); + + Color get backgroundAvatar => Color(0xFFEBF4FF); } PrimaryColors get appTheme => ThemeHelper().themeColor(); diff --git a/lib/widgets/app_bar/custom_app_bar.dart b/lib/widgets/app_bar/custom_app_bar.dart index 7ffe17b..469e931 100644 --- a/lib/widgets/app_bar/custom_app_bar.dart +++ b/lib/widgets/app_bar/custom_app_bar.dart @@ -30,7 +30,6 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { @override Widget build(BuildContext context) { return AppBar( - elevation: 0, toolbarHeight: height ?? 56.v, automaticallyImplyLeading: false, backgroundColor: Colors.transparent, diff --git a/lib/widgets/bottom_navigation_bar_widget.dart b/lib/widgets/bottom_navigation_bar_widget.dart new file mode 100644 index 0000000..68197c1 --- /dev/null +++ b/lib/widgets/bottom_navigation_bar_widget.dart @@ -0,0 +1,160 @@ +import 'package:eatvio_app/core/app_export.dart'; +import 'package:flutter/material.dart'; +import 'package:eatvio_app/widgets/custom_navigation_menu.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:go_router/go_router.dart'; + +int currentBottomNavBarIndex = 0; + +class BottomNavigationBarWidget extends StatefulWidget { + final Widget child; + + const BottomNavigationBarWidget({ + super.key, + required this.child, + }); + + @override + State createState() => _BottomNavigationBarWidgetState(); +} + +class _BottomNavigationBarWidgetState extends State { + void _onItemTapped(int index) { + if (currentBottomNavBarIndex != index) { + setState(() { + currentBottomNavBarIndex = index; + }); + } + switch (currentBottomNavBarIndex) { + case 0: + GoRouter.of(context).replace('/my-profile-screen'); + break; + case 1: + GoRouter.of(context).replace('/feed-screen'); + break; + case 2: + GoRouter.of(context).replace('/diary-screen'); + break; + case 3: + GoRouter.of(context).replace('/chat-select-screen'); + break; + case 4: + break; + case 5: + final RenderBox overlay = Overlay.of(context).context.findRenderObject() as RenderBox; + final size = overlay.size; + showMenu( + context: context, + color: Colors.white, + shadowColor: Colors.white, + surfaceTintColor: Colors.white, + items: [ + PopupMenuItem( + child: CustomNavigationMenu(), + ) + ], + position: RelativeRect.fromLTRB(size.width, size.height, 0, 0), + ); + break; + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: widget.child, + bottomNavigationBar: BottomNavigationBar( + type: BottomNavigationBarType.fixed, + onTap: _onItemTapped, + selectedFontSize: 12, + unselectedFontSize: 12, + selectedItemColor: appTheme.blueA400, + currentIndex: currentBottomNavBarIndex, + items: [ + BottomNavigationBarItem( + label: '', + icon: SvgPicture.asset( + ImageConstant.imgLogo, + ), + ), + BottomNavigationBarItem( + label: 'Лента', + icon: SvgPicture.asset( + width: 24, + height: 24, + ImageConstant.imgFeed, + ), + activeIcon: SvgPicture.asset( + ImageConstant.imgFeed, + width: 24, + height: 24, + colorFilter: ColorFilter.mode( + appTheme.blueA400, + BlendMode.srcIn, + ), + ), + ), + BottomNavigationBarItem( + label: "diary".tr, + icon: SvgPicture.asset( + ImageConstant.imgDiary, + width: 24, + height: 24, + ), + activeIcon: SvgPicture.asset( + ImageConstant.imgDiary, + width: 24, + height: 24, + colorFilter: ColorFilter.mode( + appTheme.blueA400, + BlendMode.srcIn, + ), + ), + ), + BottomNavigationBarItem( + label: "chat".tr, + icon: SvgPicture.asset( + ImageConstant.imgChat, + width: 24, + height: 24, + ), + activeIcon: SvgPicture.asset( + ImageConstant.imgChat, + width: 24, + height: 24, + colorFilter: ColorFilter.mode( + appTheme.blueA400, + BlendMode.srcIn, + ), + ), + ), + BottomNavigationBarItem( + label: '', + icon: SvgPicture.asset( + ImageConstant.imgBell, + width: 24, + height: 24, + ), + activeIcon: SvgPicture.asset( + ImageConstant.imgBell, + width: 24, + height: 24, + colorFilter: ColorFilter.mode( + appTheme.blueA400, + BlendMode.srcIn, + ), + ), + ), + BottomNavigationBarItem( + label: '', + icon: Image.asset( + ImageConstant.imgEllipse1, + width: 32, + height: 32, + ), + ), + ], + ), + ); + } +} diff --git a/lib/widgets/custom_bottom_bar.dart b/lib/widgets/custom_bottom_bar.dart deleted file mode 100644 index 3dd396d..0000000 --- a/lib/widgets/custom_bottom_bar.dart +++ /dev/null @@ -1,218 +0,0 @@ -import 'package:eatvio_app/core/app_export.dart'; -import 'package:flutter/material.dart'; -import 'package:eatvio_app/widgets/app_bar/appbar_title_circleimage.dart'; -import 'package:eatvio_app/widgets/custom_navigation_menu.dart'; - -// ignore: must_be_immutable -class CustomBottomBar extends StatefulWidget { - CustomBottomBar({this.onChanged}); - - Function(BottomBarEnum)? onChanged; - - @override - CustomBottomBarState createState() => CustomBottomBarState(); -} - -class CustomBottomBarState extends State { - int selectedIndex = 2; - - List bottomMenuList = [ - BottomMenuModel( - icon: ImageConstant.imgLogo, - activeIcon: ImageConstant.imgLogo, - type: BottomBarEnum.tf, - image: false), - BottomMenuModel( - icon: ImageConstant.imgFeed, - activeIcon: ImageConstant.imgFeed, - title: "Лента", - type: BottomBarEnum.tf, - image: false), - BottomMenuModel( - icon: ImageConstant.imgDiary, - activeIcon: ImageConstant.imgDiary, - title: "diary".tr, - type: BottomBarEnum.tf, - image: false), - BottomMenuModel( - icon: ImageConstant.imgChat, - activeIcon: ImageConstant.imgChat, - title: "chat".tr, - type: BottomBarEnum.tf, - image: false), - BottomMenuModel( - icon: ImageConstant.imgBell, - activeIcon: ImageConstant.imgBell, - type: BottomBarEnum.tf, - image: false), - BottomMenuModel( - icon: ImageConstant.imgEllipse1, - activeIcon: ImageConstant.imgEllipse1, - type: BottomBarEnum.tf, - image: true, - ), - ]; - - @override - Widget build(BuildContext context) { - return Container( - decoration: BoxDecoration( - border: Border.all( - color: appTheme.whiteA700, - width: 1.h, - ), - ), - child: BottomNavigationBar( - backgroundColor: Colors.transparent, - showSelectedLabels: false, - showUnselectedLabels: false, - selectedFontSize: 0, - elevation: 0, - currentIndex: selectedIndex, - type: BottomNavigationBarType.fixed, - items: List.generate(bottomMenuList.length, bottomMenu), - onTap: changeMeny, - ), - ); - } - - void changeMeny(index) { - setState(() { - selectedIndex = index; - }); - - switch (index) { - case 0: - Navigator.pushNamed(context, AppRoutes.myProfile); - break; - case 1: - Navigator.pushNamed(context, AppRoutes.feedScreen); - break; - case 2: - Navigator.pushNamed(context, AppRoutes.diaryEdit); - break; - case 3: - Navigator.pushNamed(context, AppRoutes.chatSelectScreen); - break; - } - } - - BottomNavigationBarItem bottomMenu(index) { - bool isSelected = selectedIndex == index; - - double iconHeight; - double iconWidth; - - if (index == 0) { - iconHeight = 50.v; - iconWidth = 50.h; - } else if (index == bottomMenuList.length - 2) { - iconHeight = 20.v; - iconWidth = 20.h; - } else if (index == bottomMenuList.length - 1) { - iconHeight = 40.v; - iconWidth = 40.h; - } else { - iconHeight = 24.v; - iconWidth = 19.h; - } - - return BottomNavigationBarItem( - icon: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - if (!bottomMenuList[index].image) - CustomImageView( - imagePath: bottomMenuList[index].icon, - height: iconHeight, - width: iconWidth, - color: - isSelected ? theme.colorScheme.primary : appTheme.blueGray400, - ), - if (bottomMenuList[index].image) - GestureDetector( - onTap: () => showMenu( - context: context, - color: Colors.white, - shadowColor: Colors.white, - surfaceTintColor: Colors.white, - position: RelativeRect.fromLTRB( - MediaQuery.of(context).size.width - 50, - kToolbarHeight + 340, - 0, // Не смещаем по горизонтали - 0, // Не смещаем по вертикали - ), - items: [ - PopupMenuItem( - child: - CustomNavigationMenu(), // Используйте ваше меню здесь - ) - ], - ), - child: AppbarTitleCircleimage( - imagePath: ImageConstant.imgEllipse1, - ), - ), - if (bottomMenuList[index].title != - null) // Проверяем, существует ли title - Padding( - padding: EdgeInsets.only(top: 8.v), - child: Text( - bottomMenuList[index].title!, - style: CustomTextStyles.bodySmallMulishBluegray80012.copyWith( - color: isSelected - ? theme.colorScheme.primary - : appTheme.blueGray800, - ), - ), - ), - ], - ), - activeIcon: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - CustomImageView( - imagePath: bottomMenuList[index].activeIcon, - height: iconHeight, // Используем тот же размер для активной иконки - width: iconWidth, // Используем тот же размер для активной иконки - color: theme.colorScheme.primary, - ), - Padding( - padding: EdgeInsets.only(top: 9.v), - child: Text( - bottomMenuList[index].title ?? "", - style: CustomTextStyles.bodySmallMulishPrimary.copyWith( - color: theme.colorScheme.primary, - ), - ), - ), - ], - ), - label: '', - ); - } -} - -enum BottomBarEnum { - tf, -} - -class BottomMenuModel { - BottomMenuModel({ - required this.icon, - required this.activeIcon, - this.title, - required this.type, - required this.image, - }); - - String icon; - - String activeIcon; - - String? title; - - BottomBarEnum type; - - bool image; -} diff --git a/lib/widgets/custom_expandable_list.dart b/lib/widgets/custom_expandable_list.dart index 00d66ce..c3d552b 100644 --- a/lib/widgets/custom_expandable_list.dart +++ b/lib/widgets/custom_expandable_list.dart @@ -3,6 +3,7 @@ import 'package:eatvio_app/core/app_export.dart'; import 'package:eatvio_app/presentation/profile/my_profile_screen/bloc/my_profile_bloc.dart'; import 'package:eatvio_app/widgets/custom_hyper_switch.dart'; import 'package:eatvio_app/widgets/custom_hypo_switch.dart'; +import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; import 'package:eatvio_app/widgets/next_step_button.dart'; import 'dart:convert'; @@ -395,10 +396,7 @@ class _ExpandableListState extends State { } void _navigateToProfilePage() { - Navigator.pushNamed( - context, - AppRoutes.myProfile, - ); + GoRouter.of(context).go('/my-profile-screen'); } String _getErrorMessage(http.Response response) { diff --git a/lib/widgets/custom_navigation_menu.dart b/lib/widgets/custom_navigation_menu.dart index 4bbdc75..03d11a9 100644 --- a/lib/widgets/custom_navigation_menu.dart +++ b/lib/widgets/custom_navigation_menu.dart @@ -1,6 +1,7 @@ import 'package:eatvio_app/core/app_export.dart'; import 'package:flutter/material.dart'; import 'package:eatvio_app/widgets/custom_navigation_menu_item.dart'; +import 'package:go_router/go_router.dart'; import 'package:hive/hive.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:http/http.dart' as http; @@ -16,33 +17,30 @@ class CustomNavigationMenu extends StatelessWidget { MenuItem( icon: ImageConstant.imgSettings, text: 'Настройки профиля', - onTap: () => Navigator.pushNamed(context, AppRoutes.mySettings), + onTap: () => GoRouter.of(context).push('/my-settings-screen'), ), MenuItem( icon: ImageConstant.imgRecipes, text: 'Мои рецепты', - onTap: () => - Navigator.pushNamed(context, AppRoutes.recipeSelectScreen), + onTap: () => GoRouter.of(context).push('/recipe-select-screen'), // Действие при нажатии на "Мои рецепты" ), MenuItem( icon: ImageConstant.imgDiets, text: 'Мои диеты', - onTap: () => - Navigator.pushNamed(context, AppRoutes.dietSelectScreen), + onTap: () => GoRouter.of(context).push('/diet-select-screen'), // Действие при нажатии на "Мои рецепты" ), MenuItem( icon: ImageConstant.imgPlans, text: 'Мои планы', - onTap: () => - Navigator.pushNamed(context, AppRoutes.planSelectScreen), + onTap: () => GoRouter.of(context).push('/plan-select-screen'), // Действие при нажатии на "Мои рецепты" ), MenuItem( icon: ImageConstant.imgFinance, text: 'Глоссарий', - onTap: () => Navigator.pushNamed(context, AppRoutes.glossaryScreen), + onTap: () => GoRouter.of(context).push('/glossary-screen'), ), MenuItem( icon: ImageConstant.imgSupport, @@ -77,10 +75,7 @@ _onTapLogout(BuildContext context) { if (response.statusCode == 204) { print(response.statusCode); box.delete('token'); // Удаляем токен из Hive - Navigator.pushNamed( - context, - AppRoutes.authScreen, - ); + GoRouter.of(context).go('/auth-screen'); } }; } diff --git a/lib/widgets/custom_text_form_field.dart b/lib/widgets/custom_text_form_field.dart index 7b76f70..e70b2b5 100644 --- a/lib/widgets/custom_text_form_field.dart +++ b/lib/widgets/custom_text_form_field.dart @@ -30,6 +30,8 @@ class CustomTextFormField extends StatelessWidget { this.keyboardType, this.onFieldSubmitted, this.onSaved, + this.initialValue, + this.onChanged, }) : super( key: key, ); @@ -57,8 +59,10 @@ class CustomTextFormField extends StatelessWidget { final bool? filled; final TextInputType? keyboardType; final FormFieldValidator? validator; + final String? initialValue; final Function(String)? onFieldSubmitted; final void Function(String?)? onSaved; + final void Function(String?)? onChanged; @override Widget build(BuildContext context) { @@ -69,13 +73,14 @@ class CustomTextFormField extends StatelessWidget { width: width ?? double.maxFinite, child: TextFormField( controller: controller, - focusNode: focusNode ?? FocusNode(), + focusNode: focusNode ?? null, autofocus: autofocus!, style: textStyle ?? theme.textTheme.titleMedium, obscureText: obscureText!, textInputAction: textInputAction, maxLines: maxLines ?? 1, onFieldSubmitted: onFieldSubmitted, + onChanged: onChanged, decoration: InputDecoration( hintText: hintText ?? "", hintStyle: hintStyle ?? theme.textTheme.bodyLarge, @@ -116,6 +121,7 @@ class CustomTextFormField extends StatelessWidget { validator: validator, onSaved: onSaved, inputFormatters: inputFormatters, + initialValue: initialValue, ), ), ) @@ -124,11 +130,12 @@ class CustomTextFormField extends StatelessWidget { child: TextFormField( controller: controller, focusNode: focusNode ?? null, - autofocus: autofocus ?? false, + autofocus: autofocus!, style: textStyle ?? theme.textTheme.titleMedium, obscureText: obscureText!, textInputAction: textInputAction, maxLines: maxLines ?? 1, + initialValue: initialValue, decoration: InputDecoration( hintText: hintText ?? "", hintStyle: hintStyle ?? theme.textTheme.bodyLarge, @@ -211,4 +218,4 @@ class CustomTextFormField extends StatelessWidget { ), ); } -}*/ \ No newline at end of file +}*/ diff --git a/pubspec.yaml b/pubspec.yaml index 8a3d81e..6000794 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,17 +1,21 @@ name: eatvio_app description: A new Flutter project. version: 0.0.0+1 -environment: - sdk: '>=2.12.0 <3.0.0' -dependencies: - flutter: + +environment: + sdk: '>=3.3.4 <4.0.0' + +dependencies: + flutter: sdk: flutter - flutter_localizations: + + flutter_localizations: sdk: flutter + flutter_bloc: ^8.0.0 connectivity_plus: ^5.0.2 shared_preferences: ^2.0.15 - cached_network_image: ^3.2.1 + cached_network_image: ^3.3.1 flutter_svg: ^2.0.9 equatable: ^2.0.0 validators: ^3.0.0 @@ -27,29 +31,48 @@ dependencies: image_picker: ^1.0.7 flutter_typeahead: ^5.2.0 bloc_test: ^9.1.6 + # navigation + go_router: ^14.1.4 keyboard_dismisser: ^3.0.0 -dev_dependencies: + freezed_annotation: ^2.4.1 + json_annotation: ^4.9.0 + retrofit: ^4.1.0 + get_it: ^7.7.0 + dio: ^5.4.3+1 + + # photo viewer + photo_view: ^0.15.0 + #url launcher + url_launcher: ^6.3.0 + +dev_dependencies: dart_code_metrics: ^5.7.6 dart_code_metrics_presets: ^1.8.0 - flutter_test: + build_runner: ^2.4.11 + freezed: ^2.5.2 + json_serializable: ^6.8.0 + retrofit_generator: ^8.1.0 + + flutter_test: sdk: flutter -flutter: + +flutter: uses-material-design: true - assets: + assets: - assets/ - assets/images/ - .env - fonts: + fonts: - family: Segoe UI - fonts: + fonts: - asset: assets/fonts/SegoeUISemibold.ttf weight: 600 - family: Mulish - fonts: + fonts: - asset: assets/fonts/MulishBold.ttf weight: 700 - asset: assets/fonts/MulishRegular.ttf weight: 400 - asset: assets/fonts/MulishSemiBold.ttf - weight: 600 + weight: 600 \ No newline at end of file diff --git a/test/diary_test.dart b/test/diary_test.dart index 224b05a..c7570d3 100644 --- a/test/diary_test.dart +++ b/test/diary_test.dart @@ -16,10 +16,8 @@ void main() { ); });*/ - testWidgets('renders _buildDiaryEditScreen when in loaded state', - (WidgetTester tester) async { - final diaryEditBloc = - DiaryEditBloc(DiaryEditState(diaryEditModelObj: DiaryEditModel())); + testWidgets('renders _buildDiaryEditScreen when in loaded state', (WidgetTester tester) async { + final diaryEditBloc = DiaryEditBloc(DiaryEditState(diaryEditModelObj: DiaryEditModel())); final loadedState = DiaryEditLoadedState( diaryId: 1, selectedSatiety: 4, @@ -36,9 +34,7 @@ void main() { MaterialApp( home: BlocProvider.value( value: diaryEditBloc, - child: DiaryEditScreen( - onInitialize: () {}, - ), + child: DiaryEditScreen(), ), ), );