Merge pull request 'sync from eatvio main to maxxkim' (#33) from eatvio/eatvio_app:main into main

Reviewed-on: maxxkim/eatvio_app#33
This commit is contained in:
apoloz
2024-06-19 13:13:32 +00:00
139 changed files with 7516 additions and 4871 deletions

View File

@@ -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"

View File

@@ -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"

View File

@@ -1,4 +1,6 @@
analyzer:
errors:
invalid_annotation_target: ignore
plugins:
- dart_code_metrics

View File

@@ -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"
}

View File

@@ -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()

View File

@@ -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

View File

@@ -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"

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1 +1,2 @@
extensions:
- provider: true

View File

@@ -156,7 +156,7 @@
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1430;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

255
lib/app_router.dart Normal file
View File

@@ -0,0 +1,255 @@
import 'package:eatvio_app/core/token_storage.dart';
import 'package:eatvio_app/features/chat/presentation/pages/chat_screen.dart';
import 'package:eatvio_app/features/chat/presentation/pages/chat_select_screen/chat_select_screen.dart';
import 'package:eatvio_app/presentation/auth/auth_screen/auth_screen.dart';
import 'package:eatvio_app/presentation/diary/diary_edit_screen/diary_edit_screen.dart';
import 'package:eatvio_app/presentation/diets/diet_create_screen/diet_create_screen.dart';
import 'package:eatvio_app/presentation/diets/diet_select_screen/diet_select_screen.dart';
import 'package:eatvio_app/presentation/feed/blog_post_create_screen/blog_post_create_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_screen/blog_post_screen.dart';
import 'package:eatvio_app/presentation/feed/feed_screen/feed_screen.dart';
import 'package:eatvio_app/presentation/glossary/glossary_screen/glossary_screen.dart';
import 'package:eatvio_app/presentation/plans/plan_select_screen/plan_select_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/recipe/recipe_select_screen/recipe_select_screen.dart';
import 'package:eatvio_app/presentation/registration/additional_information_screen/additional_information_screen.dart';
import 'package:eatvio_app/presentation/registration/allergies_screen/allergies_screen.dart';
import 'package:eatvio_app/presentation/registration/blood_pressure_screen/blood_pressure_screen.dart';
import 'package:eatvio_app/presentation/registration/body_circumferences_screen/body_circumferences_screen.dart';
import 'package:eatvio_app/presentation/registration/city_of_living_screen/city_of_living_screen.dart';
import 'package:eatvio_app/presentation/registration/credentials_screen/credentials_screen.dart';
import 'package:eatvio_app/presentation/registration/date_of_birth_screen/date_of_birth_screen.dart';
import 'package:eatvio_app/presentation/registration/favorite_foods_screen/favorite_foods_screen.dart';
import 'package:eatvio_app/presentation/registration/favorite_sports_screen/favorite_sports_screen.dart';
import 'package:eatvio_app/presentation/registration/gender_screen/gender_screen.dart';
import 'package:eatvio_app/presentation/registration/height_screen/height_screen.dart';
import 'package:eatvio_app/presentation/registration/language_select_screen/language_select_screen.dart';
import 'package:eatvio_app/presentation/registration/medicine_intake_screen/medicine_intake_screen.dart';
import 'package:eatvio_app/presentation/registration/name_screen/name_screen.dart';
import 'package:eatvio_app/presentation/registration/weight_screen/weight_screen.dart';
import 'package:eatvio_app/widgets/bottom_navigation_bar_widget.dart';
import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
class AppRouter {
static GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
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<String>,
index: int.parse(state.pathParameters['index']!),
),
),
],
);
}

View File

@@ -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';

View File

@@ -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<Dio>().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');
}

View File

@@ -1,28 +0,0 @@
import 'package:flutter/material.dart';
class NavigatorService {
static GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
static Future<dynamic> pushNamed(String routeName,
{dynamic arguments}) async {
return navigatorKey.currentState
?.pushNamed(routeName, arguments: arguments);
}
static void goBack() {
return navigatorKey.currentState?.pop();
}
static Future<dynamic> pushNamedAndRemoveUntil(String routeName,
{bool routePredicate = false, dynamic arguments}) async {
return navigatorKey.currentState?.pushNamedAndRemoveUntil(
routeName, (route) => routePredicate,
arguments: arguments);
}
static Future<dynamic> popAndPushNamed(String routeName,
{dynamic arguments}) async {
return navigatorKey.currentState
?.popAndPushNamed(routeName, arguments: arguments);
}
}

View File

@@ -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;
}
}

View File

@@ -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)

View File

@@ -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<dynamic> mediaData,
@JsonKey(name: 'author_id') required int authorId,
@JsonKey(name: 'updated_at') required String updatedAt,
}) = _ChatMessageModel;
factory ChatMessageModel.fromJson(Map<String, Object?> json) => _$ChatMessageModelFromJson(json);
}

View File

@@ -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>(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<String, dynamic> json) {
return _ChatMessageModel.fromJson(json);
}
/// @nodoc
mixin _$ChatMessageModel {
int get id => throw _privateConstructorUsedError;
String get data => throw _privateConstructorUsedError;
@JsonKey(name: 'media_data')
List<dynamic> get mediaData => throw _privateConstructorUsedError;
@JsonKey(name: 'author_id')
int get authorId => throw _privateConstructorUsedError;
@JsonKey(name: 'updated_at')
String get updatedAt => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$ChatMessageModelCopyWith<ChatMessageModel> 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<dynamic> 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<dynamic>,
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<dynamic> 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<dynamic>,
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<dynamic> mediaData,
@JsonKey(name: 'author_id') required this.authorId,
@JsonKey(name: 'updated_at') required this.updatedAt})
: _mediaData = mediaData;
factory _$ChatMessageModelImpl.fromJson(Map<String, dynamic> json) =>
_$$ChatMessageModelImplFromJson(json);
@override
final int id;
@override
final String data;
final List<dynamic> _mediaData;
@override
@JsonKey(name: 'media_data')
List<dynamic> 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<String, dynamic> 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<dynamic> mediaData,
@JsonKey(name: 'author_id') required final int authorId,
@JsonKey(name: 'updated_at') required final String updatedAt}) =
_$ChatMessageModelImpl;
factory _ChatMessageModel.fromJson(Map<String, dynamic> json) =
_$ChatMessageModelImpl.fromJson;
@override
int get id;
@override
String get data;
@override
@JsonKey(name: 'media_data')
List<dynamic> 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;
}

View File

@@ -0,0 +1,27 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'chat_message_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$ChatMessageModelImpl _$$ChatMessageModelImplFromJson(
Map<String, dynamic> json) =>
_$ChatMessageModelImpl(
id: (json['id'] as num).toInt(),
data: json['data'] as String,
mediaData: json['media_data'] as List<dynamic>,
authorId: (json['author_id'] as num).toInt(),
updatedAt: json['updated_at'] as String,
);
Map<String, dynamic> _$$ChatMessageModelImplToJson(
_$ChatMessageModelImpl instance) =>
<String, dynamic>{
'id': instance.id,
'data': instance.data,
'media_data': instance.mediaData,
'author_id': instance.authorId,
'updated_at': instance.updatedAt,
};

View File

@@ -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<ChatMessageModel> messages,
required Map<String, ChatUserModel> users,
}) = _ChatRoomModel;
factory ChatRoomModel.fromJson(Map<String, Object?> json) => _$ChatRoomModelFromJson(json);
}

View File

@@ -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>(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<String, dynamic> json) {
return _ChatRoomModel.fromJson(json);
}
/// @nodoc
mixin _$ChatRoomModel {
String get id => throw _privateConstructorUsedError;
@JsonKey(name: 'chat_room_setting')
ChatRoomSettingsModel get chatRoomSettings =>
throw _privateConstructorUsedError;
List<ChatMessageModel> get messages => throw _privateConstructorUsedError;
Map<String, ChatUserModel> get users => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$ChatRoomModelCopyWith<ChatRoomModel> 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<ChatMessageModel> messages,
Map<String, ChatUserModel> 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<ChatMessageModel>,
users: null == users
? _value.users
: users // ignore: cast_nullable_to_non_nullable
as Map<String, ChatUserModel>,
) 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<ChatMessageModel> messages,
Map<String, ChatUserModel> 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<ChatMessageModel>,
users: null == users
? _value._users
: users // ignore: cast_nullable_to_non_nullable
as Map<String, ChatUserModel>,
));
}
}
/// @nodoc
@JsonSerializable()
class _$ChatRoomModelImpl implements _ChatRoomModel {
const _$ChatRoomModelImpl(
{required this.id,
@JsonKey(name: 'chat_room_setting') required this.chatRoomSettings,
required final List<ChatMessageModel> messages,
required final Map<String, ChatUserModel> users})
: _messages = messages,
_users = users;
factory _$ChatRoomModelImpl.fromJson(Map<String, dynamic> json) =>
_$$ChatRoomModelImplFromJson(json);
@override
final String id;
@override
@JsonKey(name: 'chat_room_setting')
final ChatRoomSettingsModel chatRoomSettings;
final List<ChatMessageModel> _messages;
@override
List<ChatMessageModel> get messages {
if (_messages is EqualUnmodifiableListView) return _messages;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_messages);
}
final Map<String, ChatUserModel> _users;
@override
Map<String, ChatUserModel> 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<String, dynamic> 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<ChatMessageModel> messages,
required final Map<String, ChatUserModel> users}) = _$ChatRoomModelImpl;
factory _ChatRoomModel.fromJson(Map<String, dynamic> json) =
_$ChatRoomModelImpl.fromJson;
@override
String get id;
@override
@JsonKey(name: 'chat_room_setting')
ChatRoomSettingsModel get chatRoomSettings;
@override
List<ChatMessageModel> get messages;
@override
Map<String, ChatUserModel> get users;
@override
@JsonKey(ignore: true)
_$$ChatRoomModelImplCopyWith<_$ChatRoomModelImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@@ -0,0 +1,29 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'chat_room_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$ChatRoomModelImpl _$$ChatRoomModelImplFromJson(Map<String, dynamic> json) =>
_$ChatRoomModelImpl(
id: json['id'] as String,
chatRoomSettings: ChatRoomSettingsModel.fromJson(
json['chat_room_setting'] as Map<String, dynamic>),
messages: (json['messages'] as List<dynamic>)
.map((e) => ChatMessageModel.fromJson(e as Map<String, dynamic>))
.toList(),
users: (json['users'] as Map<String, dynamic>).map(
(k, e) =>
MapEntry(k, ChatUserModel.fromJson(e as Map<String, dynamic>)),
),
);
Map<String, dynamic> _$$ChatRoomModelImplToJson(_$ChatRoomModelImpl instance) =>
<String, dynamic>{
'id': instance.id,
'chat_room_setting': instance.chatRoomSettings,
'messages': instance.messages,
'users': instance.users,
};

View File

@@ -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<String, Object?> json) => _$ChatRoomSettingsModelFromJson(json);
}

View File

@@ -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>(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<String, dynamic> 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<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$ChatRoomSettingsModelCopyWith<ChatRoomSettingsModel> 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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;
}

View File

@@ -0,0 +1,27 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'chat_room_settings_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$ChatRoomSettingsModelImpl _$$ChatRoomSettingsModelImplFromJson(
Map<String, dynamic> 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<String, dynamic> _$$ChatRoomSettingsModelImplToJson(
_$ChatRoomSettingsModelImpl instance) =>
<String, dynamic>{
'id': instance.id,
'room_name': instance.roomName,
'state': instance.state,
'access_level': instance.accessLevel,
'updated_at': instance.updatedAt,
};

View File

@@ -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<String, Object?> json) => _$ChatUserModelFromJson(json);
}

View File

@@ -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>(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<String, dynamic> 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<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$ChatUserModelCopyWith<ChatUserModel> 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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;
}

View File

@@ -0,0 +1,23 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'chat_user_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$ChatUserModelImpl _$$ChatUserModelImplFromJson(Map<String, dynamic> 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<String, dynamic> _$$ChatUserModelImplToJson(_$ChatUserModelImpl instance) =>
<String, dynamic>{
'id': instance.id,
'name': instance.name,
'profile_photo_path': instance.profilePhotoPath,
'profile_photo_url': instance.profilePhotoUrl,
};

View File

@@ -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<String, Object?> json) => _$WebsocketInfoModelFromJson(json);
}

View File

@@ -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>(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<String, dynamic> json) {
return _WebsocketInfoModel.fromJson(json);
}
/// @nodoc
mixin _$WebsocketInfoModel {
Map<String, dynamic> 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<String, dynamic> 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<String, dynamic> toJson() {
return _$$WebsocketInfoModelImplToJson(
this,
);
}
}
abstract class _WebsocketInfoModel implements WebsocketInfoModel {
const factory _WebsocketInfoModel() = _$WebsocketInfoModelImpl;
factory _WebsocketInfoModel.fromJson(Map<String, dynamic> json) =
_$WebsocketInfoModelImpl.fromJson;
}

View File

@@ -0,0 +1,15 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'websocket_info_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$WebsocketInfoModelImpl _$$WebsocketInfoModelImplFromJson(
Map<String, dynamic> json) =>
_$WebsocketInfoModelImpl();
Map<String, dynamic> _$$WebsocketInfoModelImplToJson(
_$WebsocketInfoModelImpl instance) =>
<String, dynamic>{};

View File

@@ -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<ChatRoomState> {
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: 'Ошибка получеия данных'));
}
}
}

View File

@@ -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;
}

View File

@@ -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>(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 extends Object?>(
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 extends Object?>(
TResult? Function()? $default, {
TResult? Function()? loading,
TResult? Function(ChatRoomModel chatRoomModel)? loaded,
TResult? Function(String errorMessage)? error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>(
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 extends Object?>(
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 extends Object?>(
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 extends Object?>(
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 extends Object?>(
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 extends Object?>(
TResult? Function()? $default, {
TResult? Function()? loading,
TResult? Function(ChatRoomModel chatRoomModel)? loaded,
TResult? Function(String errorMessage)? error,
}) {
return $default?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>(
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 extends Object?>(
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 extends Object?>(
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 extends Object?>(
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 extends Object?>(
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 extends Object?>(
TResult? Function()? $default, {
TResult? Function()? loading,
TResult? Function(ChatRoomModel chatRoomModel)? loaded,
TResult? Function(String errorMessage)? error,
}) {
return loading?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>(
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 extends Object?>(
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 extends Object?>(
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 extends Object?>(
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 extends Object?>(
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 extends Object?>(
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 extends Object?>(
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 extends Object?>(
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 extends Object?>(
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 extends Object?>(
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 extends Object?>(
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 extends Object?>(
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 extends Object?>(
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 extends Object?>(
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 extends Object?>(
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 extends Object?>(
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;
}

View File

@@ -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<ChatRoomCubit>(
create: (BuildContext context) => sl()..initChat(chatId),
child: BlocBuilder<ChatRoomCubit, ChatRoomState>(
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),
],
),
),
],
),
);
}
}

View File

@@ -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<ChatSelectEvent, ChatSelectState> {
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'),

View File

@@ -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 ??

View File

@@ -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<ChatSelectBloc>(
create: (context) => ChatSelectBloc(
ChatSelectState(
chatSelectModelObj: ChatSelectModel(),
),
)..add(
ChatSelectInitialEvent(),
),
child: ChatSelectScreen(),
);
}
Widget build(BuildContext context) {
return BlocBuilder<ChatSelectBloc, ChatSelectState>(
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,
);
},
);
}

View File

@@ -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.

View File

@@ -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,
),
],
),
)
],
),
),
);
}
}

View File

@@ -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<String> imageUrls;
final int index;
const PhotoViewScreen({
super.key,
required this.imageUrls,
required this.index,
});
@override
State<PhotoViewScreen> createState() => _PhotoViewScreenState();
}
class _PhotoViewScreenState extends State<PhotoViewScreen> {
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;
});
},
),
),
),
);
}
}

View File

@@ -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<dynamic>? 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),
),
),
],
),
),
),
),
);
}
}

View File

@@ -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<Failure, Result> for error handling
Future<ChatRoomModel> getChatRoom(String chatId) async {
try {
return (await chatService.getChatRoomModel(chatId))['chatRoom']!;
} catch (e) {
print(e);
throw (e);
}
}
/// TODO use "dartz" package because need Either<Failure, Result> for error handling
Future<WebsocketInfoModel> getWebsocket() async {
try {
return (await chatService.getWebsocket());
} catch (e) {
print(e);
throw (e);
}
}
}

View File

@@ -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<Map<String, ChatRoomModel>> getChatRoomModel(@Path() String id);
@GET('/profile/ws/params')
Future<WebsocketInfoModel> getWebsocket();
}

View File

@@ -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<Map<String, ChatRoomModel>> getChatRoomModel(String id) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
const Map<String, dynamic>? _data = null;
final _result = await _dio.fetch<Map<String, dynamic>>(
_setStreamType<Map<String, ChatRoomModel>>(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<String, dynamic>)));
return value;
}
@override
Future<WebsocketInfoModel> getWebsocket() async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
const Map<String, dynamic>? _data = null;
final _result = await _dio
.fetch<Map<String, dynamic>>(_setStreamType<WebsocketInfoModel>(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<T>(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();
}
}

View File

@@ -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<AppLocalization>(context, AppLocalization)!;
} else {

28
lib/locator_service.dart Normal file
View File

@@ -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<void> init() async {
// BLoC
sl.registerFactory<ChatRoomCubit>(() => ChatRoomCubit(chatRepository: sl()));
// Repository
sl.registerLazySingleton<ChatRepository>(() => ChatRepository(chatService: sl()));
// Services
sl.registerLazySingleton<ChatService>(() => ChatService(sl()));
// External
sl.registerLazySingleton(
() => Dio(
BaseOptions(
baseUrl: dotenv.env['SERVER_ROOT']!,
),
),
);
}

View File

@@ -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<ScaffoldMessengerState>();
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<ThemeBloc>(
create: (context) => ThemeBloc(
ThemeState(themeType: PrefUtils().getThemeData()),
),
),
),
BlocProvider<MyProfileBloc>(
create: (context) => MyProfileBloc(
MyProfileState(
myProfileModelObj: MyProfileModel(),
),
)..add(MyProfileInitialEvent()),
),
BlocProvider<FeedBloc>(
create: (context) => FeedBloc(FeedState(myProfileModelObj: FeedModel()))
..add(
FeedInitialEvent(),
),
),
BlocProvider<DiaryEditBloc>(
create: (context) => DiaryEditBloc(DiaryEditState(diaryEditModelObj: DiaryEditModel()))
..add(
DiaryEditInitialEvent(),
),
),
BlocProvider<ChatSelectBloc>(
create: (context) => ChatSelectBloc(ChatSelectState(chatSelectModelObj: ChatSelectModel()))
..add(
ChatSelectInitialEvent(),
),
),
],
child: BlocBuilder<ThemeBloc, ThemeState>(
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',
'',
),
],
),
);
},
),
);

View File

@@ -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<AuthBloc, AuthState>(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<AuthBloc, AuthState, TextEditingController?>(
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<AuthBloc, AuthState, TextEditingController?>(
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<AuthBloc, AuthState>(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<String> errors = validateFields(
state.emailController!.text, state.passwordController!.text);
List<String> errors = validateFields(state.emailController!.text, state.passwordController!.text);
if (errors.isEmpty) {
final Map<String, dynamic> 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<String> validateFields(String email, String password) {

View File

@@ -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<AuthEvent, AuthState> {
AuthInitialEvent event,
Emitter<AuthState> emit,
) async {
emit(state.copyWith(
emit(
state.copyWith(
emailController: TextEditingController(),
passwordController: TextEditingController()));
passwordController: TextEditingController(),
),
);
}
}

View File

@@ -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<ChatEvent, ChatState> {
ChatBloc(ChatState initialState) : super(initialState) {
on<ChatInitialEvent>(_onInitialize);
}
_onInitialize(
ChatInitialEvent event,
Emitter<ChatState> 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<String, dynamic> &&
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<ChatMessageModel> fillMessages(List<dynamic> messagesJSON) {
List<ChatMessageModel> 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;
}
}

View File

@@ -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<Object?> get props => [];
}

View File

@@ -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<Object?> 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<ChatMessageModel>? messages;
@override
List<Object?> get props => [chatModelObj, messages];
ChatLoadedState copyWith(
{ChatModel? chatModelObj, List<ChatMessageModel>? messages}) {
return ChatLoadedState(
chatModelObj: chatModelObj ?? this.chatModelObj,
messages: messages ?? this.messages,
);
}
}
class ChatErrorState extends ChatState {
final String errorMessage;
ChatErrorState(this.errorMessage);
@override
List<Object> get props => [errorMessage];
}
class ChatInitialState extends ChatState {
ChatInitialState({required ChatModel chatModelObj})
: super(chatModelObj: chatModelObj);
}

View File

@@ -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<ChatBloc>(
create: (context) => ChatBloc(
ChatState(chatModelObj: ChatModel(chatID: chatID.toString())))
..add(ChatInitialEvent()),
child: ChatScreen());
}
Widget build(BuildContext context) {
return BlocBuilder<ChatBloc, ChatState>(
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<ChatBloc, ChatState, TextEditingController?>(
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();
}
}

View File

@@ -1,36 +0,0 @@
import 'package:equatable/equatable.dart';
class ChatMessageModel extends Equatable {
final int id;
final String data;
final List<dynamic> 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<dynamic>? 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<Object?> get props => [id, data, mediaData, authorId, updatedAt];
}

View File

@@ -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<Object?> get props => [chatID];
}

View File

@@ -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<ChatSelectBloc>(
create: (context) => ChatSelectBloc(
ChatSelectState(chatSelectModelObj: ChatSelectModel()))
..add(ChatSelectInitialEvent()),
child: ChatSelectScreen());
}
Widget build(BuildContext context) {
return BlocBuilder<ChatSelectBloc, ChatSelectState>(
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) {});
}

View File

@@ -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,
)
],
),
),
)
]),
),
);
}
}

View File

@@ -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<DiaryEditBloc>(
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<String> _options =
state.autocompleteList!.map((dynamic item) => item as String).toList();
List<String> _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<DiaryEditBloc, DiaryEditState,
DiaryEditModel?>(
child: BlocSelector<DiaryEditBloc, DiaryEditState, DiaryEditModel?>(
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<DiaryEditBloc, DiaryEditState,
DiaryEditModel?>(
child: BlocSelector<DiaryEditBloc, DiaryEditState, DiaryEditModel?>(
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<DiaryEditBloc>().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<String> onSelected, String option,
DiaryEditLoadedState state) {
selectProduct(AutocompleteOnSelected<String> 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<DiaryEditBloc>()
.add(ChangeMeal(meals: state.meals, value: value));
context.read<DiaryEditBloc>().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<Widget>((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<String, dynamic> photo) {
return () {
context
.read<DiaryEditBloc>()
.add(RemoveImageEvent(photo['id'].toString(), photo['path']));
context.read<DiaryEditBloc>().add(RemoveImageEvent(photo['id'].toString(), photo['path']));
};
}
_buildNutritionChart(BuildContext context, DiaryEditLoadedState state) {
final List<int> recommendedValues = state
.recommendedChartValues; // Рекомендуемые значения: калории, жиры, белки, углеводы
final List<int> recommendedValues = state.recommendedChartValues; // Рекомендуемые значения: калории, жиры, белки, углеводы
final List<int> 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<DiaryEditBloc>().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<void> _selectWakeupTime(
BuildContext context, DiaryEditLoadedState state) async {
Future<void> _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<void> _selectRetireTime(
BuildContext context, DiaryEditLoadedState state) async {
Future<void> _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<void> _selectMealTime(
BuildContext context, DiaryEditLoadedState state) async {
Future<void> _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<int> recommendedValues, List<int> consumedValues) {
bool checkConsumedValues(List<int> recommendedValues, List<int> consumedValues) {
for (int i = 0; i < recommendedValues.length; i++) {
if (consumedValues[i] > recommendedValues[i]) {
// Если хотя бы одно потребленное значение превышает рекомендованное

View File

@@ -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<DietCreateEvent, DietCreateState> {
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<DietCreateEvent, DietCreateState> {
);
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}');

View File

@@ -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<DietCreateBloc>(
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<DietCreateBloc>().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<DietCreateBloc>().add(CreateDiet()),
child: Text('Создать'),
)
]),
),
), bottomNavigationBar: _buildBottomBar(context));
)
]),
),
),
);
}
}
_buildBottomBar(BuildContext context) {
return CustomBottomBar(onChanged: (BottomBarEnum type) {});
}

View File

@@ -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<DietEditEvent, DietEditState> {
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<DietEditEvent, DietEditState> {
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}');
}
}
}

View File

@@ -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<DietEditBloc>(
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<DietEditBloc>().add(EditDiet()),
onPressed: () => context.read<DietEditBloc>().add(EditDiet()),
child: Text('Сохранить'),
)
]),
)
]),
),
), bottomNavigationBar: _buildBottomBar(context));
],
),
)
],
),
),
),
);
}
}
_buildBottomBar(BuildContext context) {
return CustomBottomBar(onChanged: (BottomBarEnum type) {});
}

View File

@@ -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<DietSelectEvent, DietSelectState> {
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'),

View File

@@ -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<DietSelectBloc>(
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();
}
}

View File

@@ -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<DietEvent, DietState> {
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<DietEvent, DietState> {
if (data is Map<String, dynamic>) {
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<DietEvent, DietState> {
}
}
FutureOr<void> _deleteEntry(
DeleteEntry event, Emitter<DietState> emit) async {
FutureOr<void> _deleteEntry(DeleteEntry event, Emitter<DietState> 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<DietEvent, DietState> {
},
);
if (response.statusCode == 200) {
NavigatorService.pushNamed(AppRoutes.dietSelectScreen);
AppRouter.navigatorKey.currentContext?.goNamed('dietSelectScreen');
} else {
// Статус ответа не равен 200, обрабатываем ошибку
emit(DietErrorState('Ошибка: ${response.statusCode}'));

View File

@@ -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<DietBloc>(
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<DietBloc>().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<DietBloc>()
.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();
}
}

View File

@@ -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}');

View File

@@ -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<BlogPostCreateBloc>(
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: <Widget>[
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: <Widget>[
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) {});
}

View File

@@ -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<BlogPostEditEvent, BlogPostEditState> {
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<BlogPostEditEvent, BlogPostEditState> {
}
} else {
// Один или несколько ключевых полей отсутствуют, обрабатываем ошибку
emit(BlogPostEditErrorState(
'Некоторые необходимые данные отсутствуют.'));
emit(BlogPostEditErrorState('Некоторые необходимые данные отсутствуют.'));
}
} else {
// Статус ответа не равен 200, обрабатываем ошибку
@@ -70,8 +71,7 @@ class BlogPostEditBloc extends Bloc<BlogPostEditEvent, BlogPostEditState> {
}
}
FutureOr<void> _editBlogPost(
EditBlogPost event, Emitter<BlogPostEditState> emit) async {
FutureOr<void> _editBlogPost(EditBlogPost event, Emitter<BlogPostEditState> emit) async {
if (state is BlogPostEditLoadedState) {
var currentState = state as BlogPostEditLoadedState;
@@ -83,11 +83,9 @@ class BlogPostEditBloc extends Bloc<BlogPostEditEvent, BlogPostEditState> {
);
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}');
}
}
}

View File

@@ -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<BlogPostEditBloc>(
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<BlogPostEditBloc, BlogPostEditState>(
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<BlogPostEditBloc>().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<BlogPostEditBloc>().add(EditBlogPost()),
child: Text('Сохранить'),
)
],
),
)
],
),
),
);
}
}
_buildBottomBar(BuildContext context) {
return CustomBottomBar(onChanged: (BottomBarEnum type) {});
}

View File

@@ -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<BlogPostEvent, BlogPostState> {
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<BlogPostEvent, BlogPostState> {
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<BlogPostEvent, BlogPostState> {
},
);
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<BlogPostEvent, BlogPostState> {
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<BlogPostEvent, BlogPostState> {
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'),

View File

@@ -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<BlogPostBloc>(
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<BlogPostBloc, BlogPostState>(
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<BlogPostBloc>().add(DeleteEntry(state.blog_post?['id']));
@@ -341,9 +354,7 @@ class BlogPostScreen extends StatelessWidget {
decoration: BoxDecoration(
color: Colors.transparent,
),
onPressed: () => context
.read<BlogPostBloc>()
.add(CreateComment(state.commentController!.text)),
onPressed: () => context.read<BlogPostBloc>().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();
}
}

View File

@@ -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<FeedEvent, FeedState> {
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'),

View File

@@ -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<FeedBloc>(
create: (context) => FeedBloc(FeedState(myProfileModelObj: FeedModel()))
..add(FeedInitialEvent()),
child: FeedScreen());
return BlocProvider<FeedBloc>(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<String>(
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) {

View File

@@ -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<GlossaryEvent, GlossaryState> {
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'),

View File

@@ -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<GlossaryBloc>(
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<GlossaryBloc>().add(ChangeProductGroup(value.id));

View File

@@ -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<PlanCreateEvent, PlanCreateState> {
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<PlanCreateEvent, PlanCreateState> {
);
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}');

View File

@@ -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<PlanCreateBloc>(
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<PlanCreateBloc>().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<PlanCreateBloc>().add(CreatePlan()),
child: Text('Создать'),
)
]),
)
],
),
),
),
);
}
}
_buildBottomBar(BuildContext context) {
return CustomBottomBar(onChanged: (BottomBarEnum type) {});
}

View File

@@ -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<PlanEditEvent, PlanEditState> {
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<PlanEditEvent, PlanEditState> {
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<PlanEditEvent, PlanEditState> {
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}');
}
}
}

View File

@@ -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<PlanEditBloc>(
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<PlanEditBloc>().add(EditPlan()),
onPressed: () => context.read<PlanEditBloc>().add(EditPlan()),
child: Text('Сохранить'),
)
]),
)
]),
),
), bottomNavigationBar: _buildBottomBar(context));
],
),
)
],
),
),
),
);
}
}
_buildBottomBar(BuildContext context) {
return CustomBottomBar(onChanged: (BottomBarEnum type) {});
}

View File

@@ -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<PlanSelectEvent, PlanSelectState> {
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'),

View File

@@ -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<PlanSelectBloc>(
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();
}
}

View File

@@ -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<PlanEvent, PlanState> {
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<PlanEvent, PlanState> {
if (data is Map<String, dynamic>) {
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<PlanEvent, PlanState> {
}
}
FutureOr<void> _deleteEntry(
DeleteEntry event, Emitter<PlanState> emit) async {
FutureOr<void> _deleteEntry(DeleteEntry event, Emitter<PlanState> 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<PlanEvent, PlanState> {
},
);
if (response.statusCode == 200) {
NavigatorService.pushNamed(AppRoutes.planSelectScreen);
AppRouter.navigatorKey.currentContext?.goNamed('planSelectScreen');
} else {
// Статус ответа не равен 200, обрабатываем ошибку
emit(PlanErrorState('Ошибка: ${response.statusCode}'));

View File

@@ -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<PlanBloc>(
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<PlanBloc>().add(DeleteEntry(state.plan?['id'])),
child: Text('Удалить'),
),
SizedBox(height: 16.0),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
ElevatedButton(
onPressed: () {
// Действие для редактирования
},
child: Text('Редактировать'),
),
ElevatedButton(
onPressed: () => context
.read<PlanBloc>()
.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();
}
}

View File

@@ -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<MyProfileEvent, MyProfileState> {
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";

View File

@@ -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<MyProfileBloc>(
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<int>((e) => e['weight'])
.toList()
.map((i) => i.toDouble())
.toList() ??
[0.0, 0.0, 0.0, 0.0],
state.chartPoints?.values.map<int>((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<int>((e) => e['weight'])
.toList()
.map((i) => i.toDouble())
.toList() ??
[0.0, 0.0, 0.0, 0.0],
_bodyMassIndex(state.chartPoints?.values.map<int>((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<double> chartPoints, List<String>? dates) {
_buildChart(BuildContext context, List<double> chartPoints, List<String>? dates) {
List<FlSpot> 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<String>? dates) {
Widget bottomTitleWidgets(double value, TitleMeta meta, double chartWidth, List<String>? 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<String>? dates) {
_buildCircumferenceChart(BuildContext context, chartPoints, List<String>? dates) {
// Определение осей графика
final chestCircumferences =
chartPoints.values.map((e) => e['chest_circumference']).toList();
final chestCircumferences = chartPoints.values.map((e) => e['chest_circumference']).toList();
List<FlSpot> 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<FlSpot> 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<FlSpot> 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<String>? dates) {
Widget bottomTitleWidgets(double value, TitleMeta meta, double chartWidth, List<String>? 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<dynamic>? plans,
List<dynamic>? imagePaths, List<dynamic>? states) {
_buildPlans(BuildContext context, List<dynamic>? plans, List<dynamic>? imagePaths, List<dynamic>? 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: <TextInputFormatter>[
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: <TextInputFormatter>[
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: <TextInputFormatter>[
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) {
// Обработка ошибок при отправке запроса

View File

@@ -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<MyProfileBloc>(
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();
}
}

View File

@@ -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<RecipeCreateEvent, RecipeCreateState> {
RecipeCreateBloc(RecipeCreateState initialState) : super(initialState) {
on<RecipeCreateInitialEvent>(_onInitialize);
on<CreateRecipe>(_createRecipe);
on<AddStep>(_addStep);
on<RemoveStep>(_removeStep);
on<ReloadScreen>(_reloadScreen);
}
_onInitialize(
@@ -25,31 +30,72 @@ class RecipeCreateBloc extends Bloc<RecipeCreateEvent, RecipeCreateState> {
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<String, dynamic> step = {
'description': '',
'image': '',
};
emit(RecipeCreateLoadedState(
token: token,
title: TextEditingController(),
description: TextEditingController(),
video_url: TextEditingController(),
tags: TextEditingController(),
stepDescriptions: [TextEditingController()],
steps: [step]));
}
}
FutureOr<void> _addStep(
AddStep event, Emitter<RecipeCreateState> emit) async {
if (state is RecipeCreateLoadedState) {
var currentState = state as RecipeCreateLoadedState;
List<dynamic>? updatedSteps = currentState.steps;
List<TextEditingController>? updatedStepDescriptions =
currentState.stepDescriptions;
updatedSteps!.add({
'description': '',
'image': '',
});
updatedStepDescriptions!.add(TextEditingController());
emit(currentState.copyWith(
steps: updatedSteps, stepDescriptions: updatedStepDescriptions));
await _reloadScreen(event, emit);
}
}
FutureOr<void> _removeStep(
RemoveStep event, Emitter<RecipeCreateState> emit) async {
if (state is RecipeCreateLoadedState) {
var currentState = state as RecipeCreateLoadedState;
List<dynamic>? updatedSteps = currentState.steps;
List<TextEditingController>? updatedStepDescriptions =
currentState.stepDescriptions;
updatedSteps!.removeAt(event.id);
updatedStepDescriptions!.removeAt(event.id);
emit(currentState.copyWith(
steps: updatedSteps, stepDescriptions: updatedStepDescriptions));
await _reloadScreen(event, emit);
}
}
FutureOr<void> _createRecipe(
CreateRecipe event, Emitter<RecipeCreateState> emit) async {
if (state is RecipeCreateLoadedState) {
@@ -59,10 +105,11 @@ class RecipeCreateBloc extends Bloc<RecipeCreateEvent, RecipeCreateState> {
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<RecipeCreateEvent, RecipeCreateState> {
}
}
FutureOr<http.Response> sendRecipe(
String? title,
String? video_url,
String? description,
) async {
FutureOr<http.Response> sendRecipe(String? title, String? video_url,
String? description, List<dynamic>? steps) async {
String url = '${dotenv.env['SERVER_ROOT']!}/recipe/create';
final Map<String, dynamic> credentials = {
'title': title,
'description': description,
'video_url': video_url,
'steps': steps,
'status': 'active',
};
@@ -95,4 +139,26 @@ class RecipeCreateBloc extends Bloc<RecipeCreateEvent, RecipeCreateState> {
return response;
}
FutureOr<void> _reloadScreen(
RecipeCreateEvent? event, Emitter<RecipeCreateState> 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));
}
}
}

View File

@@ -19,3 +19,22 @@ class CreateRecipe extends RecipeCreateEvent {
@override
List<Object?> get props => [];
}
class AddStep extends RecipeCreateEvent {
AddStep();
@override
List<Object?> get props => [];
}
class RemoveStep extends RecipeCreateEvent {
RemoveStep(this.id);
final int id;
@override
List<Object?> get props => [id];
}
class ReloadScreen extends RecipeCreateEvent {
ReloadScreen();
@override
List<Object?> get props => [];
}

View File

@@ -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<dynamic>? steps;
List<TextEditingController>? stepDescriptions;
@override
List<Object?> get props =>
[recipeModelObj, title, description, video_url, steps];
List<Object?> 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<dynamic>? steps,
List<TextEditingController>? 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);
}
}

View File

@@ -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<RecipeCreateBloc>(
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<RecipeCreateBloc>().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<RecipeCreateBloc>().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<RecipeCreateBloc>().add(AddStep()),
text: "Добавить шаг",
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
),
),
SizedBox(width: 8), // Add some spacing between the buttons
Flexible(
flex: 1,
child: CustomOutlinedButton(
text: "х",
onPressed: () => context.read<RecipeCreateBloc>().add(RemoveStep(i)),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
),
),
],
),
],
),
if (!isLastStep && !isFirstStep)
CustomOutlinedButton(
onPressed: () => context.read<RecipeCreateBloc>().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();
}
}

View File

@@ -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<RecipeEditEvent, RecipeEditState> {
RecipeEditBloc(RecipeEditState initialState) : super(initialState) {
on<RecipeEditInitialEvent>(_onInitialize);
on<EditRecipe>(_editRecipe);
on<AddStep>(_addStep);
on<RemoveStep>(_removeStep);
on<ReloadScreen>(_reloadScreen);
}
_onInitialize(
@@ -26,9 +33,7 @@ class RecipeEditBloc extends Bloc<RecipeEditEvent, RecipeEditState> {
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<RecipeEditEvent, RecipeEditState> {
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<String, dynamic> defaultStep = {
'description': '',
'image': '',
};
List<dynamic> steps = data['steps'];
List<TextEditingController> 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<RecipeEditEvent, RecipeEditState> {
}
}
FutureOr<void> _editRecipe(
EditRecipe event, Emitter<RecipeEditState> emit) async {
FutureOr<void> _editRecipe(EditRecipe event, Emitter<RecipeEditState> emit) async {
if (state is RecipeEditLoadedState) {
var currentState = state as RecipeEditLoadedState;
@@ -83,15 +113,47 @@ class RecipeEditBloc extends Bloc<RecipeEditEvent, RecipeEditState> {
);
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<void> _addStep(AddStep event, Emitter<RecipeEditState> emit) async {
if (state is RecipeEditLoadedState) {
var currentState = state as RecipeEditLoadedState;
List<dynamic>? updatedSteps = currentState.steps;
List<TextEditingController>? updatedStepDescriptions = currentState.stepDescriptions;
updatedSteps!.add({
'description': '',
'image': '',
});
updatedStepDescriptions!.add(TextEditingController());
emit(currentState.copyWith(steps: updatedSteps, stepDescriptions: updatedStepDescriptions));
await _reloadScreen(event, emit);
}
}
FutureOr<void> _removeStep(RemoveStep event, Emitter<RecipeEditState> emit) async {
if (state is RecipeEditLoadedState) {
var currentState = state as RecipeEditLoadedState;
List<dynamic>? updatedSteps = currentState.steps;
List<TextEditingController>? updatedStepDescriptions = currentState.stepDescriptions;
updatedSteps!.removeAt(event.id);
updatedStepDescriptions!.removeAt(event.id);
emit(currentState.copyWith(steps: updatedSteps, stepDescriptions: updatedStepDescriptions));
await _reloadScreen(event, emit);
}
}
FutureOr<http.Response> sendRecipe(
Map<String, dynamic>? recipe,
String? title,
@@ -123,4 +185,44 @@ class RecipeEditBloc extends Bloc<RecipeEditEvent, RecipeEditState> {
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<void> _reloadScreen(RecipeEditEvent? event, Emitter<RecipeEditState> 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));
}
}
}

Some files were not shown because too many files have changed in this diff Show More