Merge remote-tracking branch 'upstream/main'

This commit is contained in:
Username1548
2024-11-20 15:28:52 +07:00
22 changed files with 900 additions and 321 deletions

View File

@@ -1,10 +1,10 @@
import 'dart:async';
import 'dart:io';
import 'package:eatvio_app/old/feed/blog_post_create_screen/bloc/blog_post_create_bloc.dart';
import 'package:eatvio_app/old/feed/blog_post_create_screen/models/blog_post_create_model.dart';
import 'package:eatvio_app/theme/app_decoration.dart';
import 'package:eatvio_app/theme/theme_helper.dart';
import 'package:eatvio_app/widgets/custom_text_form_field.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
@@ -13,22 +13,13 @@ import 'package:image_picker/image_picker.dart';
class BlogPostCreateScreen extends StatefulWidget {
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())))
..add(BlogPostCreateInitialEvent()),
child: BlogPostCreateScreen(),
);
}
@override
State<BlogPostCreateScreen> createState() => _BlogPostCreateScreenState();
}
class _BlogPostCreateScreenState extends State<BlogPostCreateScreen> {
final blogPostCreateBloc = BlogPostCreateBloc(BlogPostCreateState())..add(BlogPostCreateInitialEvent());
bool shrinkImage = false;
late StreamSubscription<bool> keyboardSubscription;
@override
@@ -49,24 +40,72 @@ class _BlogPostCreateScreenState extends State<BlogPostCreateScreen> {
}
Widget build(BuildContext context) {
return BlocBuilder<BlogPostCreateBloc, BlogPostCreateState>(
builder: (context, state) {
if (state is BlogPostCreateInitialState) {
// Это начальное состояние, данные ещё не загружены.
return Container(
color: Colors.white,
);
} else if (state is BlogPostCreateLoadedState) {
// Данные загружены, можно строить экран.
return _buildBlogPostCreate(context, state);
} else if (state is BlogPostCreateErrorState) {
// Произошла ошибка при загрузке данных.
return Center(child: Text(state.errorMessage));
} else {
// Другие состояния, если они есть.
return Container(); // Пустой контейнер для необработанных состояний.
}
},
return BlocProvider<BlogPostCreateBloc>(
create: (BuildContext context) => blogPostCreateBloc,
child: BlocBuilder<BlogPostCreateBloc, BlogPostCreateState>(
builder: (context, state) {
if (state is BlogPostCreateInitialState) {
// Это начальное состояние, данные ещё не загружены.
return Container(
color: Colors.white,
);
} else if (state is BlogPostCreateLoadedState) {
// Данные загружены, можно строить экран.
return _buildBlogPostCreate(context, state);
} else if (state is BlogPostCreateErrorState) {
// Произошла ошибка при загрузке данных.
return Center(child: Text(state.errorMessage));
} else {
// Другие состояния, если они есть.
return Container(); // Пустой контейнер для необработанных состояний.
}
},
),
);
}
_showImagePicker() {
showCupertinoModalPopup<void>(
context: context,
builder: (context) => CupertinoActionSheet(
actions: [
CupertinoActionSheetAction(
onPressed: () async {
final picker = ImagePicker();
XFile? image = await picker.pickImage(source: ImageSource.camera);
if (image != null) blogPostCreateBloc.add(UploadImages(image));
Navigator.of(context).pop();
},
child: const Row(
children: const [
Icon(Icons.photo_camera),
SizedBox(width: 8),
Text('Фото'),
],
),
),
CupertinoActionSheetAction(
onPressed: () async {
final picker = ImagePicker();
XFile? image = await picker.pickImage(source: ImageSource.gallery);
if (image != null) blogPostCreateBloc.add(UploadImages(image));
Navigator.of(context).pop();
},
child: const Row(
children: const [
Icon(Icons.photo),
SizedBox(width: 8),
Text('Галерея'),
],
),
),
],
cancelButton: CupertinoActionSheetAction(
onPressed: () => Navigator.of(context).pop(),
isDestructiveAction: true,
child: const Text('Отмена'),
),
),
);
}
@@ -164,15 +203,15 @@ class _BlogPostCreateScreenState extends State<BlogPostCreateScreen> {
),
),
if (state.image != null)
AnimatedContainer(
Container(
width: shrinkImage ? 52 : MediaQuery.of(context).size.width - 32,
curve: Curves.easeIn,
duration: Duration(milliseconds: 150),
child: ClipRRect(
borderRadius: BorderRadius.circular(16),
child: Image(
image: FileImage(
File(state.image!.path),
File(
state.image!.path,
),
),
),
),
@@ -181,6 +220,14 @@ class _BlogPostCreateScreenState extends State<BlogPostCreateScreen> {
),
);
}
addImage(BuildContext context, bool isLoading) {
if (!isLoading) {
return () {
_showImagePicker();
};
}
}
}
_submitForm(
@@ -191,15 +238,3 @@ _submitForm(
) async {
context.read<BlogPostCreateBloc>().add(CreateBlogPost());
}
addImage(BuildContext context, bool isLoading) {
if (!isLoading) {
return () async {
final picker = ImagePicker();
XFile? image = await picker.pickImage(source: ImageSource.gallery);
if (image != null) {
context.read<BlogPostCreateBloc>().add(UploadImages(image));
}
};
}
}

View File

@@ -62,157 +62,154 @@ class _BottomNavigationBarWidgetState extends State<BottomNavigationBarWidget> {
final s = S.of(context);
return Scaffold(
body: widget.child,
bottomNavigationBar: SizedBox(
height: 58,
child: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
onTap: _onItemTapped,
selectedFontSize: 10,
unselectedFontSize: 10,
selectedItemColor: appTheme.blueA400,
currentIndex: currentBottomNavBarIndex,
items: [
BottomNavigationBarItem(
label: '',
icon: BlocProvider<ProfileAvatarCubit>(
create: (context) => sl<ProfileAvatarCubit>()..getMyProfile(),
child: BlocBuilder<ProfileAvatarCubit, ProfileAvatarState>(
builder: (BuildContext context, ProfileAvatarState state) {
if (state is ProfileAvatarStateLoaded) {
return CircleAvatar(
backgroundImage: NetworkImage(state.profile.profilePhotoUrl),
radius: 16,
);
}
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
onTap: _onItemTapped,
selectedFontSize: 10,
unselectedFontSize: 10,
selectedItemColor: appTheme.blueA400,
currentIndex: currentBottomNavBarIndex,
items: [
BottomNavigationBarItem(
label: '',
icon: BlocProvider<ProfileAvatarCubit>(
create: (context) => sl<ProfileAvatarCubit>()..getMyProfile(),
child: BlocBuilder<ProfileAvatarCubit, ProfileAvatarState>(
builder: (BuildContext context, ProfileAvatarState state) {
if (state is ProfileAvatarStateLoaded) {
return CircleAvatar(
backgroundImage: NetworkImage(state.profile.profilePhotoUrl),
radius: 16,
backgroundColor: Colors.black12,
);
},
),
),
),
BottomNavigationBarItem(
label: 'Лента',
icon: Padding(
padding: const EdgeInsets.only(bottom: 2),
child: SvgPicture.asset(
colorFilter: ColorFilter.mode(Colors.grey, BlendMode.srcIn),
width: 20,
height: 20,
ImageConstant.icnFeed,
),
),
activeIcon: Padding(
padding: const EdgeInsets.only(bottom: 2),
child: SvgPicture.asset(
ImageConstant.icnFeed,
width: 20,
height: 20,
colorFilter: ColorFilter.mode(
appTheme.blueA400,
BlendMode.srcIn,
),
),
),
),
BottomNavigationBarItem(
label: s.diary,
icon: Padding(
padding: const EdgeInsets.only(bottom: 2),
child: SvgPicture.asset(
colorFilter: ColorFilter.mode(Colors.grey, BlendMode.srcIn),
ImageConstant.icnDiary,
width: 20,
height: 20,
),
),
activeIcon: Padding(
padding: const EdgeInsets.only(bottom: 2),
child: SvgPicture.asset(
ImageConstant.icnDiary,
width: 20,
height: 20,
colorFilter: ColorFilter.mode(
appTheme.blueA400,
BlendMode.srcIn,
),
),
),
),
BottomNavigationBarItem(
label: s.chat,
icon: BlocBuilder<NewMessagesCubit, NewMessagesState>(
builder: (BuildContext context, NewMessagesState state) {
return badges.Badge(
position: badges.BadgePosition.topEnd(),
badgeAnimation: badges.BadgeAnimation.fade(),
badgeContent: Text(
(state is NewMessagesStateLoaded) ? state.messagesCount.toString() : '0',
style: TextStyle(
color: Colors.white,
fontSize: 8,
),
),
child: Padding(
padding: const EdgeInsets.only(bottom: 2),
child: SvgPicture.asset(
ImageConstant.icnChat,
width: 20,
height: 20,
colorFilter: currentBottomNavBarIndex == 3
? ColorFilter.mode(
appTheme.blueA400,
BlendMode.srcIn,
)
: ColorFilter.mode(Colors.grey, BlendMode.srcIn),
),
),
}
return CircleAvatar(
radius: 16,
backgroundColor: Colors.black12,
);
},
),
),
BottomNavigationBarItem(
label: 'Уведомления',
icon: BlocBuilder<NotificationsCubit, NotificationsState>(
builder: (BuildContext context, NotificationsState state) {
return badges.Badge(
position: badges.BadgePosition.topEnd(),
badgeAnimation: badges.BadgeAnimation.fade(),
badgeContent: Text(
(state is NotificationsStateLoaded) ? state.notificationsCount.toString() : '0',
style: TextStyle(
color: Colors.white,
fontSize: 8,
),
),
child: Padding(
padding: const EdgeInsets.only(bottom: 2),
child: SvgPicture.asset(
ImageConstant.icnBell,
colorFilter: currentBottomNavBarIndex == 4
? ColorFilter.mode(
appTheme.blueA400,
BlendMode.srcIn,
)
: ColorFilter.mode(Colors.grey, BlendMode.srcIn),
width: 20,
height: 20,
),
),
);
},
),
BottomNavigationBarItem(
label: 'Лента',
icon: Padding(
padding: const EdgeInsets.only(bottom: 2),
child: SvgPicture.asset(
colorFilter: ColorFilter.mode(Colors.grey, BlendMode.srcIn),
width: 20,
height: 20,
ImageConstant.icnFeed,
),
),
BottomNavigationBarItem(
label: '',
icon: SvgPicture.asset(
ImageConstant.imgLogo,
height: 32,
activeIcon: Padding(
padding: const EdgeInsets.only(bottom: 2),
child: SvgPicture.asset(
ImageConstant.icnFeed,
width: 20,
height: 20,
colorFilter: ColorFilter.mode(
appTheme.blueA400,
BlendMode.srcIn,
),
),
),
],
),
),
BottomNavigationBarItem(
label: s.diary,
icon: Padding(
padding: const EdgeInsets.only(bottom: 2),
child: SvgPicture.asset(
colorFilter: ColorFilter.mode(Colors.grey, BlendMode.srcIn),
ImageConstant.icnDiary,
width: 20,
height: 20,
),
),
activeIcon: Padding(
padding: const EdgeInsets.only(bottom: 2),
child: SvgPicture.asset(
ImageConstant.icnDiary,
width: 20,
height: 20,
colorFilter: ColorFilter.mode(
appTheme.blueA400,
BlendMode.srcIn,
),
),
),
),
BottomNavigationBarItem(
label: s.chat,
icon: BlocBuilder<NewMessagesCubit, NewMessagesState>(
builder: (BuildContext context, NewMessagesState state) {
return badges.Badge(
position: badges.BadgePosition.topEnd(),
badgeAnimation: badges.BadgeAnimation.fade(),
badgeContent: Text(
(state is NewMessagesStateLoaded) ? state.messagesCount.toString() : '0',
style: TextStyle(
color: Colors.white,
fontSize: 8,
),
),
child: Padding(
padding: const EdgeInsets.only(bottom: 2),
child: SvgPicture.asset(
ImageConstant.icnChat,
width: 20,
height: 20,
colorFilter: currentBottomNavBarIndex == 3
? ColorFilter.mode(
appTheme.blueA400,
BlendMode.srcIn,
)
: ColorFilter.mode(Colors.grey, BlendMode.srcIn),
),
),
);
},
),
),
BottomNavigationBarItem(
label: 'Уведомления',
icon: BlocBuilder<NotificationsCubit, NotificationsState>(
builder: (BuildContext context, NotificationsState state) {
return badges.Badge(
position: badges.BadgePosition.topEnd(),
badgeAnimation: badges.BadgeAnimation.fade(),
badgeContent: Text(
(state is NotificationsStateLoaded) ? state.notificationsCount.toString() : '0',
style: TextStyle(
color: Colors.white,
fontSize: 8,
),
),
child: Padding(
padding: const EdgeInsets.only(bottom: 2),
child: SvgPicture.asset(
ImageConstant.icnBell,
colorFilter: currentBottomNavBarIndex == 4
? ColorFilter.mode(
appTheme.blueA400,
BlendMode.srcIn,
)
: ColorFilter.mode(Colors.grey, BlendMode.srcIn),
width: 20,
height: 20,
),
),
);
},
),
),
BottomNavigationBarItem(
label: '',
icon: SvgPicture.asset(
ImageConstant.imgLogo,
height: 32,
),
),
],
),
);
}

View File

@@ -0,0 +1,21 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'product_moderation_model.freezed.dart';
part 'product_moderation_model.g.dart';
@freezed
class ProductModerationModel with _$ProductModerationModel {
const factory ProductModerationModel({
required String name,
@JsonKey(name: 'energy_on_100gr')
required int energy,
@JsonKey(name: 'proteins_on_100gr')
required int proteins,
@JsonKey(name: 'fats_on_100gr')
required int fats,
@JsonKey(name: 'carbohydrates_on_100gr')
required int carbohydrates,
}) = _ProductModerationModel;
factory ProductModerationModel.fromJson(Map<String, Object?> json) => _$ProductModerationModelFromJson(json);
}

View File

@@ -0,0 +1,270 @@
// 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 'product_moderation_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');
ProductModerationModel _$ProductModerationModelFromJson(
Map<String, dynamic> json) {
return _ProductModerationModel.fromJson(json);
}
/// @nodoc
mixin _$ProductModerationModel {
String get name => throw _privateConstructorUsedError;
@JsonKey(name: 'energy_on_100gr')
int get energy => throw _privateConstructorUsedError;
@JsonKey(name: 'proteins_on_100gr')
int get proteins => throw _privateConstructorUsedError;
@JsonKey(name: 'fats_on_100gr')
int get fats => throw _privateConstructorUsedError;
@JsonKey(name: 'carbohydrates_on_100gr')
int get carbohydrates => throw _privateConstructorUsedError;
/// Serializes this ProductModerationModel to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of ProductModerationModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$ProductModerationModelCopyWith<ProductModerationModel> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ProductModerationModelCopyWith<$Res> {
factory $ProductModerationModelCopyWith(ProductModerationModel value,
$Res Function(ProductModerationModel) then) =
_$ProductModerationModelCopyWithImpl<$Res, ProductModerationModel>;
@useResult
$Res call(
{String name,
@JsonKey(name: 'energy_on_100gr') int energy,
@JsonKey(name: 'proteins_on_100gr') int proteins,
@JsonKey(name: 'fats_on_100gr') int fats,
@JsonKey(name: 'carbohydrates_on_100gr') int carbohydrates});
}
/// @nodoc
class _$ProductModerationModelCopyWithImpl<$Res,
$Val extends ProductModerationModel>
implements $ProductModerationModelCopyWith<$Res> {
_$ProductModerationModelCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of ProductModerationModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? name = null,
Object? energy = null,
Object? proteins = null,
Object? fats = null,
Object? carbohydrates = null,
}) {
return _then(_value.copyWith(
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
energy: null == energy
? _value.energy
: energy // ignore: cast_nullable_to_non_nullable
as int,
proteins: null == proteins
? _value.proteins
: proteins // ignore: cast_nullable_to_non_nullable
as int,
fats: null == fats
? _value.fats
: fats // ignore: cast_nullable_to_non_nullable
as int,
carbohydrates: null == carbohydrates
? _value.carbohydrates
: carbohydrates // ignore: cast_nullable_to_non_nullable
as int,
) as $Val);
}
}
/// @nodoc
abstract class _$$ProductModerationModelImplCopyWith<$Res>
implements $ProductModerationModelCopyWith<$Res> {
factory _$$ProductModerationModelImplCopyWith(
_$ProductModerationModelImpl value,
$Res Function(_$ProductModerationModelImpl) then) =
__$$ProductModerationModelImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{String name,
@JsonKey(name: 'energy_on_100gr') int energy,
@JsonKey(name: 'proteins_on_100gr') int proteins,
@JsonKey(name: 'fats_on_100gr') int fats,
@JsonKey(name: 'carbohydrates_on_100gr') int carbohydrates});
}
/// @nodoc
class __$$ProductModerationModelImplCopyWithImpl<$Res>
extends _$ProductModerationModelCopyWithImpl<$Res,
_$ProductModerationModelImpl>
implements _$$ProductModerationModelImplCopyWith<$Res> {
__$$ProductModerationModelImplCopyWithImpl(
_$ProductModerationModelImpl _value,
$Res Function(_$ProductModerationModelImpl) _then)
: super(_value, _then);
/// Create a copy of ProductModerationModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? name = null,
Object? energy = null,
Object? proteins = null,
Object? fats = null,
Object? carbohydrates = null,
}) {
return _then(_$ProductModerationModelImpl(
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
energy: null == energy
? _value.energy
: energy // ignore: cast_nullable_to_non_nullable
as int,
proteins: null == proteins
? _value.proteins
: proteins // ignore: cast_nullable_to_non_nullable
as int,
fats: null == fats
? _value.fats
: fats // ignore: cast_nullable_to_non_nullable
as int,
carbohydrates: null == carbohydrates
? _value.carbohydrates
: carbohydrates // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// @nodoc
@JsonSerializable()
class _$ProductModerationModelImpl implements _ProductModerationModel {
const _$ProductModerationModelImpl(
{required this.name,
@JsonKey(name: 'energy_on_100gr') required this.energy,
@JsonKey(name: 'proteins_on_100gr') required this.proteins,
@JsonKey(name: 'fats_on_100gr') required this.fats,
@JsonKey(name: 'carbohydrates_on_100gr') required this.carbohydrates});
factory _$ProductModerationModelImpl.fromJson(Map<String, dynamic> json) =>
_$$ProductModerationModelImplFromJson(json);
@override
final String name;
@override
@JsonKey(name: 'energy_on_100gr')
final int energy;
@override
@JsonKey(name: 'proteins_on_100gr')
final int proteins;
@override
@JsonKey(name: 'fats_on_100gr')
final int fats;
@override
@JsonKey(name: 'carbohydrates_on_100gr')
final int carbohydrates;
@override
String toString() {
return 'ProductModerationModel(name: $name, energy: $energy, proteins: $proteins, fats: $fats, carbohydrates: $carbohydrates)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ProductModerationModelImpl &&
(identical(other.name, name) || other.name == name) &&
(identical(other.energy, energy) || other.energy == energy) &&
(identical(other.proteins, proteins) ||
other.proteins == proteins) &&
(identical(other.fats, fats) || other.fats == fats) &&
(identical(other.carbohydrates, carbohydrates) ||
other.carbohydrates == carbohydrates));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode =>
Object.hash(runtimeType, name, energy, proteins, fats, carbohydrates);
/// Create a copy of ProductModerationModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$ProductModerationModelImplCopyWith<_$ProductModerationModelImpl>
get copyWith => __$$ProductModerationModelImplCopyWithImpl<
_$ProductModerationModelImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$ProductModerationModelImplToJson(
this,
);
}
}
abstract class _ProductModerationModel implements ProductModerationModel {
const factory _ProductModerationModel(
{required final String name,
@JsonKey(name: 'energy_on_100gr') required final int energy,
@JsonKey(name: 'proteins_on_100gr') required final int proteins,
@JsonKey(name: 'fats_on_100gr') required final int fats,
@JsonKey(name: 'carbohydrates_on_100gr')
required final int carbohydrates}) = _$ProductModerationModelImpl;
factory _ProductModerationModel.fromJson(Map<String, dynamic> json) =
_$ProductModerationModelImpl.fromJson;
@override
String get name;
@override
@JsonKey(name: 'energy_on_100gr')
int get energy;
@override
@JsonKey(name: 'proteins_on_100gr')
int get proteins;
@override
@JsonKey(name: 'fats_on_100gr')
int get fats;
@override
@JsonKey(name: 'carbohydrates_on_100gr')
int get carbohydrates;
/// Create a copy of ProductModerationModel
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$ProductModerationModelImplCopyWith<_$ProductModerationModelImpl>
get copyWith => throw _privateConstructorUsedError;
}

View File

@@ -0,0 +1,27 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'product_moderation_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$ProductModerationModelImpl _$$ProductModerationModelImplFromJson(
Map<String, dynamic> json) =>
_$ProductModerationModelImpl(
name: json['name'] as String,
energy: (json['energy_on_100gr'] as num).toInt(),
proteins: (json['proteins_on_100gr'] as num).toInt(),
fats: (json['fats_on_100gr'] as num).toInt(),
carbohydrates: (json['carbohydrates_on_100gr'] as num).toInt(),
);
Map<String, dynamic> _$$ProductModerationModelImplToJson(
_$ProductModerationModelImpl instance) =>
<String, dynamic>{
'name': instance.name,
'energy_on_100gr': instance.energy,
'proteins_on_100gr': instance.proteins,
'fats_on_100gr': instance.fats,
'carbohydrates_on_100gr': instance.carbohydrates,
};

View File

@@ -25,7 +25,7 @@ class DiaryCubit extends Cubit<DiaryState> {
emit(
DiaryState.loaded(
diary: await diaryRepository.getDiaryByDate(date),
nutrition: await getNutrition(await diaryRepository.getDiaryIndexByDate(date)),
nutrition: await getNutrition((await diaryRepository.getDiaryByDate(date)).id),
currentSelectedSatietyRating: currentSelectedSatietyRating,
),
);
@@ -140,4 +140,20 @@ class DiaryCubit extends Cubit<DiaryState> {
currentSelectedSatietyRating = value;
emit((state as DiaryStateLoaded).copyWith(currentSelectedSatietyRating: currentSelectedSatietyRating));
}
sendProductToModeration({
required String name,
required int energy,
required int proteins,
required int fats,
required int carbohydrates,
}) async {
return await diaryRepository.sendProductToModeration(
name: name,
energy: energy,
proteins: proteins,
fats: fats,
carbohydrates: carbohydrates,
);
}
}

View File

@@ -130,7 +130,7 @@ class _DiaryScreenState extends State<DiaryScreen> {
state.diary,
state.currentSelectedSatietyRating,
),
if (state is DiaryStateLoaded) _chart(state.nutrition),
if (state is DiaryStateLoaded && _selectedDay!.compareTo(today!) != 1) _chart(state.nutrition),
if (state is DiaryStateLoaded && state.diary.closed == 0 && _selectedDay == today)
Padding(
padding: const EdgeInsets.only(top: 12),
@@ -278,23 +278,25 @@ class _DiaryScreenState extends State<DiaryScreen> {
_mealButton(5),
],
),
const SizedBox(height: 12),
_divider(context),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Время приема пищи',
style: TextStyle(
color: Color(0xFF323F4B),
fontSize: 16,
fontWeight: FontWeight.w500,
_selectedDay!.compareTo(today!) == 1 ? const SizedBox.shrink() : const SizedBox(height: 12),
_selectedDay!.compareTo(today!) == 1 ? const SizedBox.shrink() : _divider(context),
_selectedDay!.compareTo(today!) == 1 ? const SizedBox.shrink() : const SizedBox(height: 12),
_selectedDay!.compareTo(today!) == 1
? const SizedBox.shrink()
: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Время приема пищи',
style: TextStyle(
color: Color(0xFF323F4B),
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
_timeOfMeal(time),
],
),
),
_timeOfMeal(time),
],
),
const SizedBox(height: 12),
_divider(context),
const SizedBox(height: 12),
@@ -303,21 +305,25 @@ class _DiaryScreenState extends State<DiaryScreen> {
_addProductButton(diaryDay),
const SizedBox(height: 24),
if (diaryDay.meals[currentSelectedMealNumber - 1].photos.isEmpty)
GestureDetector(
onTap: () => _showImagePicker(diaryDay),
child: SvgPicture.asset(
width: MediaQuery.of(context).size.width - 32,
'assets/no_one_image_was_added.svg',
),
),
_selectedDay!.compareTo(today!) == 1
? const SizedBox.shrink()
: GestureDetector(
onTap: () => _showImagePicker(diaryDay),
child: SvgPicture.asset(
width: MediaQuery.of(context).size.width - 32,
'assets/no_one_image_was_added.svg',
),
),
if (diaryDay.meals[currentSelectedMealNumber - 1].photos.isNotEmpty)
_photoGallery(
diaryDay.meals[currentSelectedMealNumber - 1].photos,
diaryDay,
),
SatietyRatingWidget(
currentSelectedSatietyRating: currentSelectedSatietyRating,
),
_selectedDay!.compareTo(today!) == 1
? const SizedBox.shrink()
: SatietyRatingWidget(
currentSelectedSatietyRating: currentSelectedSatietyRating,
),
],
),
),
@@ -1017,7 +1023,10 @@ class _DiaryScreenState extends State<DiaryScreen> {
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
child: GlossaryModerationRequestModalWidget(productName: productNameController.text),
child: GlossaryModerationRequestModalWidget(
productName: productNameController.text,
diaryCubit: diaryCubit,
),
),
);
}
@@ -1057,6 +1066,7 @@ class _DiaryScreenState extends State<DiaryScreen> {
}
_addProductButton(DiaryDayModel diaryDay) {
if (_selectedDay!.compareTo(today!) == 1) return const SizedBox.shrink();
return InkWell(
onTap: () {
setState(() {

View File

@@ -1,29 +1,38 @@
import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart';
import 'package:eatvio_app/theme/theme_helper.dart';
import 'package:eatvio_app/widgets/custom_text_form_field.dart';
import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart';
class GlossaryModerationRequestModalWidget extends StatefulWidget {
final DiaryCubit diaryCubit;
final String productName;
const GlossaryModerationRequestModalWidget({
super.key,
required this.productName,
required this.productName, required this.diaryCubit,
});
@override
State<GlossaryModerationRequestModalWidget> createState() => _GlossaryModerationRequestModalWidgetState();
}
enum GlossaryState { alert, addNewProduct, finish }
enum GlossaryState { alert, addNewProduct, success }
class _GlossaryModerationRequestModalWidgetState extends State<GlossaryModerationRequestModalWidget> {
GlossaryState glossaryState = GlossaryState.alert;
final productNameController = TextEditingController();
final energyController = TextEditingController();
final proteinsController = TextEditingController();
final fatsController = TextEditingController();
final carbohydratesController = TextEditingController();
@override
Widget build(BuildContext context) {
if (glossaryState == GlossaryState.alert) {
return _alert();
} else if (glossaryState == GlossaryState.success) {
return _success();
} else {
return _addNewGlossaryProduct();
}
@@ -126,6 +135,58 @@ class _GlossaryModerationRequestModalWidgetState extends State<GlossaryModeratio
);
}
_success() {
return Padding(
padding: EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Продукт успешно отправлен\nна модерацию',
textAlign: TextAlign.center,
style: TextStyle(
color: Color(0xFF323F4B),
fontSize: 20,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 16),
Lottie.asset(
'assets/lottie_animations/success.json',
width: 160,
height: 160,
),
const SizedBox(height: 16),
InkWell(
onTap: () {
Navigator.of(context).pop();
},
borderRadius: BorderRadius.circular(20),
child: Ink(
decoration: BoxDecoration(
color: appTheme.blueA400,
borderRadius: BorderRadius.circular(20),
),
child: Container(
padding: EdgeInsets.all(16),
alignment: Alignment.center,
child: Text(
'Закрыть',
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.w500,
),
),
),
),
),
const SizedBox(height: 16),
],
),
);
}
_addNewGlossaryProduct() {
return Padding(
padding: const EdgeInsets.all(16),
@@ -163,12 +224,16 @@ class _GlossaryModerationRequestModalWidgetState extends State<GlossaryModeratio
Expanded(
child: CustomTextFormField(
hintText: 'Калории',
controller: energyController,
keyboardType: TextInputType.number,
),
),
const SizedBox(width: 8),
Expanded(
child: CustomTextFormField(
hintText: 'Белки',
controller: proteinsController,
keyboardType: TextInputType.number,
),
),
],
@@ -180,19 +245,34 @@ class _GlossaryModerationRequestModalWidgetState extends State<GlossaryModeratio
Expanded(
child: CustomTextFormField(
hintText: 'Жиры',
controller: fatsController,
keyboardType: TextInputType.number,
),
),
const SizedBox(width: 8),
Expanded(
child: CustomTextFormField(
hintText: 'Углеводы',
controller: carbohydratesController,
keyboardType: TextInputType.number,
),
),
],
),
const SizedBox(height: 12),
InkWell(
onTap: () {},
onTap: () {
widget.diaryCubit.sendProductToModeration(
name: productNameController.text,
energy: int.parse(energyController.text),
proteins: int.parse(proteinsController.text),
fats: int.parse(fatsController.text),
carbohydrates: int.parse(carbohydratesController.text),
);
setState(() {
glossaryState = GlossaryState.success;
});
},
borderRadius: BorderRadius.circular(20),
child: Ink(
padding: EdgeInsets.symmetric(horizontal: 8),

View File

@@ -1,6 +1,7 @@
import 'dart:io';
import 'package:eatvio_app/features/diary/models/diary_day_model.dart';
import 'package:eatvio_app/features/diary/models/nutrition_model.dart';
import 'package:eatvio_app/features/diary/models/product_moderation_model.dart';
import 'package:eatvio_app/features/diary/services/diary_service.dart';
class DiaryRepository {
@@ -11,14 +12,11 @@ class DiaryRepository {
});
Future<DiaryDayModel> getDiaryByDate(String date) async {
return (await diaryService.getDiaryDay(
index: await getDiaryIndexByDate(date),
))
.diary;
return (await getDiaryIndexByDate(date)).last;
}
Future<int> getDiaryIndexByDate(String date) async {
return (await diaryService.getDiaryIndexByDate(date: date)).id;
Future<List<DiaryDayModel>> getDiaryIndexByDate(String date) async {
return (await diaryService.getDiaryIndexByDate(date: date));
}
Future<NutritionDataModel> getNutritionData(int index) async {
@@ -123,4 +121,22 @@ class DiaryRepository {
// ),
);
}
Future<void> sendProductToModeration({
required String name,
required int energy,
required int proteins,
required int fats,
required int carbohydrates,
}) async {
return await diaryService.sendProductToModeration(
data: ProductModerationModel(
name: name,
energy: energy,
proteins: proteins,
fats: fats,
carbohydrates: carbohydrates,
).toJson(),
);
}
}

View File

@@ -1,7 +1,6 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:eatvio_app/features/diary/models/diary_day_model.dart';
import 'package:eatvio_app/features/diary/models/diary_index_model.dart';
import 'package:eatvio_app/features/diary/models/nutrition_model.dart';
import 'package:retrofit/retrofit.dart';
@@ -12,7 +11,7 @@ abstract class DiaryService {
factory DiaryService(Dio dio, {String baseUrl}) = _DiaryService;
@GET('/api/diary/index/{date}')
Future<DiaryIndexModel> getDiaryIndexByDate({
Future<List<DiaryDayModel>> getDiaryIndexByDate({
@Path('date') required String date, // yyyy-mm-dd
});
@@ -71,4 +70,9 @@ abstract class DiaryService {
@Path('diary_id') required int diaryId,
// @Body() required FinishDiaryDayModel finishDiaryDayModel,
});
@POST('/api/diary/product_nutritional_values/sendToModeration')
Future<void> sendProductToModeration({
@Body() required Map<String, dynamic> data,
});
}

View File

@@ -22,12 +22,13 @@ class _DiaryService implements DiaryService {
final ParseErrorLogger? errorLogger;
@override
Future<DiaryIndexModel> getDiaryIndexByDate({required String date}) async {
Future<List<DiaryDayModel>> getDiaryIndexByDate(
{required String date}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
const Map<String, dynamic>? _data = null;
final _options = _setStreamType<DiaryIndexModel>(Options(
final _options = _setStreamType<List<DiaryDayModel>>(Options(
method: 'GET',
headers: _headers,
extra: _extra,
@@ -43,10 +44,12 @@ class _DiaryService implements DiaryService {
_dio.options.baseUrl,
baseUrl,
)));
final _result = await _dio.fetch<Map<String, dynamic>>(_options);
late DiaryIndexModel _value;
final _result = await _dio.fetch<List<dynamic>>(_options);
late List<DiaryDayModel> _value;
try {
_value = DiaryIndexModel.fromJson(_result.data!);
_value = _result.data!
.map((dynamic i) => DiaryDayModel.fromJson(i as Map<String, dynamic>))
.toList();
} on Object catch (e, s) {
errorLogger?.logError(e, s, _options);
rethrow;
@@ -317,6 +320,33 @@ class _DiaryService implements DiaryService {
await _dio.fetch<void>(_options);
}
@override
Future<void> sendProductToModeration(
{required Map<String, dynamic> data}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
final _data = <String, dynamic>{};
_data.addAll(data);
final _options = _setStreamType<void>(Options(
method: 'POST',
headers: _headers,
extra: _extra,
)
.compose(
_dio.options,
'/api/diary/product_nutritional_values/sendToModeration',
queryParameters: queryParameters,
data: _data,
)
.copyWith(
baseUrl: _combineBaseUrls(
_dio.options.baseUrl,
baseUrl,
)));
await _dio.fetch<void>(_options);
}
RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
if (T != dynamic &&
!(requestOptions.responseType == ResponseType.bytes ||

View File

@@ -124,7 +124,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
if (widget.author == null) const SizedBox(height: 16),
_activePlans(state),
if (widget.author == null) const SizedBox(height: 16),
_newBlogPostButton(),
if (widget.author == null) _newBlogPostButton(),
_blogPosts(state),
],
),

View File

@@ -4,7 +4,6 @@ import 'package:eatvio_app/generated/l10n.dart';
import 'package:eatvio_app/theme/theme_helper.dart';
import 'package:eatvio_app/widgets/custom_text_form_field.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:go_router/go_router.dart';
class OtherInfoScreen extends StatefulWidget {
@@ -60,6 +59,7 @@ class _OtherInfoScreenState extends State<OtherInfoScreen> {
autofocus: true,
minLines: 2,
maxLines: 8,
maxLength: 250,
onChanged: (newValue) {},
),
SizedBox(height: 16),
@@ -70,6 +70,7 @@ class _OtherInfoScreenState extends State<OtherInfoScreen> {
hintText: s.enter_information,
autofocus: true,
minLines: 2,
maxLength: 250,
maxLines: 8,
onChanged: (newValue) {},
),
@@ -82,6 +83,7 @@ class _OtherInfoScreenState extends State<OtherInfoScreen> {
"Я занимаюсь плаванием , почти каждый вечер прогулки на свежем воздухе, пару раз в месяц играю в волейбол. Стараюсь не сидеть на месте!",
autofocus: true,
maxLines: 8,
maxLength: 250,
onChanged: (newValue) {},
),
SizedBox(height: 16),
@@ -92,6 +94,7 @@ class _OtherInfoScreenState extends State<OtherInfoScreen> {
hintText: s.enter_information,
maxLines: 7,
minLines: 2,
maxLength: 250,
onChanged: (newValue) {},
),
SizedBox(height: 16),
@@ -102,6 +105,7 @@ class _OtherInfoScreenState extends State<OtherInfoScreen> {
hintText: s.enter_information,
maxLines: 7,
minLines: 2,
maxLength: 250,
onChanged: (newValue) {},
),
SizedBox(height: 16),
@@ -114,9 +118,7 @@ class _OtherInfoScreenState extends State<OtherInfoScreen> {
SizedBox(height: 4),
CustomTextFormField(
controller: additionalInfoController,
inputFormatters: [
new LengthLimitingTextInputFormatter(250),
],
maxLength: 250,
hintText: "У меня крепкое здоровье, но последнее время часто утомляюсь, однако, это мне не мешает радоваться жизни",
maxLines: 8,
onChanged: (newValue) {},