Merge pull request 'fix: remove old unused features and widgets' (#168) from mso7nueh/eatvio_app:main into main

Reviewed-on: eatvio/eatvio_app#168
This commit is contained in:
mso7nueh
2024-11-20 11:12:06 +00:00
96 changed files with 9 additions and 7548 deletions

View File

@@ -1,5 +1,6 @@
import 'package:eatvio_app/core/initialization_cubit/initialization_cubit.dart';
import 'package:eatvio_app/core/token_storage.dart';
import 'package:eatvio_app/features/auth/auth_screen/auth_screen.dart';
import 'package:eatvio_app/features/blog_post/presentation/pages/blog_post_screen.dart';
import 'package:eatvio_app/features/blog_post_create/presentation/pages/blog_post_create_screen.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart';
@@ -34,13 +35,6 @@ import 'package:eatvio_app/features/settings/presentation/pages/physical_activit
import 'package:eatvio_app/features/settings/presentation/pages/physical_form_screen.dart';
import 'package:eatvio_app/features/settings/presentation/pages/pressure_screen.dart';
import 'package:eatvio_app/features/settings/presentation/pages/settings_screen.dart';
import 'package:eatvio_app/old/auth/auth_screen/auth_screen.dart';
import 'package:eatvio_app/old/diets/diet_create_screen/diet_create_screen.dart';
import 'package:eatvio_app/old/diets/diet_select_screen/diet_select_screen.dart';
import 'package:eatvio_app/old/glossary/glossary_screen/glossary_screen.dart';
import 'package:eatvio_app/old/plans/plan_select_screen/plan_select_screen.dart';
import 'package:eatvio_app/old/profile/my_profile_screen/my_settings_screen.dart';
import 'package:eatvio_app/old/recipe/recipe_select_screen/recipe_select_screen.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
@@ -227,11 +221,6 @@ class AppRouter {
),
],
),
GoRoute(
path: '/my-settings-screen',
name: 'mySettingsScreen',
builder: (context, state) => MySettingsScreen.builder(context),
),
GoRoute(
path: '/become-consultant-screen',
name: 'becomeConsultantScreen',
@@ -242,36 +231,11 @@ class AppRouter {
name: 'stepperBecomeConsultantScreen',
builder: (context, state) => StepperBecomeConsultantScreen(),
),
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: '/calendar-screen',
name: 'calendarScreen',
builder: (context, state) => CalendarScreen(),
),
GoRoute(
path: '/glossary-screen',
name: 'glossaryScreen',
builder: (context, state) => GlossaryScreen.builder(context),
),
GoRoute(
path: '/photo-view-screen/:index',
name: 'photoViewScreen',

View File

@@ -1,4 +1,4 @@
import 'package:eatvio_app/old/auth/auth_screen/models/auth_model.dart';
import 'package:eatvio_app/features/auth/auth_screen/models/auth_model.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

View File

@@ -25,7 +25,7 @@ class _BecomeConsultantScreenState extends State<BecomeConsultantScreen> {
body: Padding(
padding: const EdgeInsets.all(16),
child: Text(
' Чтобы стать консультантом необходимо пройти короткую регистрацию. Подготовьте свои дипломы и/или сертификаты в формате jpg, они Вам пригодятся для проверки и подтверждения. Также необходимо будет указать ссылки на Ваши соц. сети и указать тип лица (физ. лицо или юр. лицо). На данный момент доступна регистрация в качестве физ. лица.\n\n Внутри Итвио разница между обычным пользователем и консультантом заключается в дополнительных возможностях последнего создавать публичные диеты, рецепты и планы питания; участвовать в ежемесячных рейтингах.',
'Чтобы стать консультантом необходимо пройти короткую регистрацию. Подготовьте свои дипломы и/или сертификаты в формате jpg, они Вам пригодятся для проверки и подтверждения. Также необходимо будет указать ссылки на Ваши соц. сети и указать тип лица (физ. лицо или юр. лицо). На данный момент доступна регистрация в качестве физ. лица.\n\nВнутри Итвио разница между обычным пользователем и консультантом заключается в дополнительных возможностях последнего создавать публичные диеты, рецепты и планы питания; участвовать в ежемесячных рейтингах.',
style: TextStyle(
fontSize: 16,
),

View File

@@ -7,7 +7,9 @@ import 'package:eatvio_app/features/profile/services/profile_service.dart';
class ProfileRepository {
final ProfileService profileService;
ProfileRepository({required this.profileService});
ProfileRepository({
required this.profileService,
});
Future<ProfileModel> getProfileBySlug(String slug) async {
try {

View File

@@ -1,185 +0,0 @@
// ignore_for_file: must_be_immutable
/*part of '../../../../features/diary/presentation/bloc/diary_bloc.dart';
class DiaryEditState extends Equatable {
const DiaryEditState({this.diaryEditModelObj});
final DiaryEditModel? diaryEditModelObj;
@override
List<Object?> get props => [diaryEditModelObj];
}
class DiaryEditInitialState extends DiaryEditState {
DiaryEditInitialState({required DiaryEditModel diaryEditModelObj}) : super(diaryEditModelObj: diaryEditModelObj);
}
/// Represents the state of DiaryEdit in the application.
class DiaryEditLoadedState extends DiaryEditState {
DiaryEditLoadedState({
this.diaryEditModelObj,
this.diaryDate,
this.token,
this.meals,
this.portions,
this.photos,
this.autocompleteList,
this.wakeupController,
this.retireController,
this.commentController,
this.mealTimeController,
this.portionController,
this.productNameController,
this.productQuantityController,
this.selectedMood,
this.currentMeal,
this.caloriesController,
this.fatsController,
this.proteinsController,
this.carbsController,
this.isLoading = false,
this.isAdded = false,
required this.recommendedChartValues,
required this.consumedChartValues,
required this.diaryId,
required this.selectedSatiety,
required this.moderationExpanded,
required this.wakeupExpanded,
required this.moodExpanded,
}) : super(diaryEditModelObj: null);
DiaryEditModel? diaryEditModelObj;
String? diaryDate;
String? token;
List<dynamic>? meals;
List<dynamic>? portions;
List<dynamic>? photos;
List<dynamic>? autocompleteList;
List<int> recommendedChartValues;
List<int> consumedChartValues;
SelectionPopupModel? currentMeal;
SelectionPopupModel? portionController;
TimeOfDay? wakeupController;
TimeOfDay? retireController;
TimeOfDay? mealTimeController;
TextEditingController? commentController;
TextEditingController? productNameController;
TextEditingController? productQuantityController;
TextEditingController? caloriesController;
TextEditingController? fatsController;
TextEditingController? proteinsController;
TextEditingController? carbsController;
int? selectedMood;
int selectedSatiety;
int diaryId;
bool wakeupExpanded;
bool moderationExpanded;
bool moodExpanded;
bool isLoading;
bool isAdded;
@override
List<Object?> get props => [
diaryEditModelObj,
diaryDate,
token,
meals,
portions,
photos,
recommendedChartValues,
consumedChartValues,
autocompleteList,
wakeupController,
retireController,
mealTimeController,
commentController,
productNameController,
productQuantityController,
portionController,
caloriesController,
fatsController,
proteinsController,
carbsController,
selectedMood,
selectedSatiety,
diaryId,
wakeupExpanded,
moodExpanded,
moderationExpanded,
isLoading,
isAdded,
currentMeal,
];
DiaryEditLoadedState copyWith({
DiaryEditModel? diaryEditModelObj,
SelectionPopupModel? currentMeal,
SelectionPopupModel? portionController,
String? diaryDate,
String? token,
List<int>? recommendedChartValues,
List<int>? consumedChartValues,
List<dynamic>? meals,
List<dynamic>? portions,
List<dynamic>? photos,
List<dynamic>? autocompleteList,
TimeOfDay? wakeupController,
TimeOfDay? retireController,
TimeOfDay? mealTimeController,
TextEditingController? commentController,
TextEditingController? productNameController,
TextEditingController? productQuantityController,
TextEditingController? caloriesController,
TextEditingController? fatsController,
TextEditingController? proteinsController,
TextEditingController? carbsController,
int? selectedMood,
int? selectedSatiety,
int? diaryId,
bool? wakeupExpanded,
bool? moderationExpanded,
bool? moodExpanded,
bool? isLoading,
bool? isAdded,
}) {
return DiaryEditLoadedState(
diaryEditModelObj: diaryEditModelObj ?? this.diaryEditModelObj,
diaryDate: diaryDate ?? this.diaryDate,
token: token ?? this.token,
meals: meals ?? this.meals,
photos: photos ?? this.photos,
autocompleteList: autocompleteList ?? this.autocompleteList,
portions: portions ?? this.portions,
recommendedChartValues: recommendedChartValues ?? this.recommendedChartValues,
consumedChartValues: consumedChartValues ?? this.consumedChartValues,
wakeupController: wakeupController ?? this.wakeupController,
retireController: retireController ?? this.retireController,
mealTimeController: mealTimeController ?? this.mealTimeController,
commentController: commentController ?? this.commentController,
portionController: portionController ?? this.portionController,
productNameController: productNameController ?? this.productNameController,
productQuantityController: productQuantityController ?? this.productQuantityController,
selectedMood: selectedMood ?? this.selectedMood,
selectedSatiety: selectedSatiety ?? this.selectedSatiety,
diaryId: diaryId ?? this.diaryId,
currentMeal: currentMeal ?? this.currentMeal,
caloriesController: caloriesController ?? this.caloriesController,
fatsController: fatsController ?? this.fatsController,
proteinsController: proteinsController ?? this.proteinsController,
carbsController: carbsController ?? this.carbsController,
wakeupExpanded: wakeupExpanded ?? this.wakeupExpanded,
moderationExpanded: moderationExpanded ?? this.moderationExpanded,
moodExpanded: moodExpanded ?? this.moodExpanded,
isAdded: isAdded ?? this.isAdded,
isLoading: isLoading ?? this.isLoading,
);
}
}
class DiaryEditErrorState extends DiaryEditState {
final String errorMessage;
const DiaryEditErrorState(this.errorMessage);
@override
List<Object> get props => [errorMessage];
}*/

View File

@@ -1,26 +0,0 @@
// ignore_for_file: must_be_immutable
import 'package:eatvio_app/data/models/selectionPopupModel/selection_popup_model.dart';
import 'package:equatable/equatable.dart';
/// This class defines the variables used in the [k0_page],
/// and is typically used to hold data that is passed between different parts of the application.
class DiaryEditModel extends Equatable {
DiaryEditModel({this.mealList = const [], this.portionList = const []});
List<SelectionPopupModel> mealList;
List<SelectionPopupModel>? portionList;
DiaryEditModel copyWith({
List<SelectionPopupModel>? mealList,
List<SelectionPopupModel>? portionList,
}) {
return DiaryEditModel(
mealList: mealList ?? this.mealList,
portionList: portionList ?? this.portionList,
);
}
@override
List<Object?> get props => [mealList, portionList];
}

File diff suppressed because one or more lines are too long

View File

@@ -1,21 +0,0 @@
// ignore_for_file: must_be_immutable
part of 'diet_create_bloc.dart';
/// Abstract class for all events that can be dispatched from the
///DietCreate widget.
///
/// Events must be immutable and implement the [Equatable] interface.
@immutable
abstract class DietCreateEvent extends Equatable {}
/// Event that is dispatched when the DietCreate widget is first created.
class DietCreateInitialEvent extends DietCreateEvent {
@override
List<Object?> get props => [];
}
class CreateDiet extends DietCreateEvent {
@override
List<Object?> get props => [];
}

View File

@@ -1,67 +0,0 @@
// ignore_for_file: must_be_immutable
part of 'diet_create_bloc.dart';
/// Represents the state of DietCreate in the application.
///
class DietCreateState extends Equatable {
DietCreateState({
this.dietModelObj,
});
DietCreateModel? dietModelObj;
@override
List<Object?> get props => [dietModelObj];
DietCreateState copyWith({DietCreateModel? dietModelObj}) {
return DietCreateState(
dietModelObj: dietModelObj ?? this.dietModelObj,
);
}
}
class DietCreateLoadedState extends DietCreateState {
DietCreateLoadedState({
this.dietModelObj,
this.name,
this.details,
});
DietCreateModel? dietModelObj;
TextEditingController? name;
TextEditingController? details;
@override
List<Object?> get props => [
dietModelObj,
name,
details,
];
DietCreateLoadedState copyWith({
DietCreateModel? dietModelObj,
TextEditingController? name,
TextEditingController? details,
}) {
return DietCreateLoadedState(
dietModelObj: dietModelObj ?? this.dietModelObj,
name: name ?? this.name,
details: details ?? this.details,
);
}
}
class DietCreateErrorState extends DietCreateState {
final String errorMessage;
DietCreateErrorState(this.errorMessage);
@override
List<Object> get props => [errorMessage];
}
class DietCreateInitialState extends DietCreateState {
DietCreateInitialState({required DietCreateModel dietModelObj}) : super(dietModelObj: dietModelObj);
}

View File

@@ -1,98 +0,0 @@
import 'package:eatvio_app/theme/app_decoration.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'bloc/diet_create_bloc.dart';
import 'models/diet_create_model.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()),
child: DietCreateScreen(),
);
}
Widget build(BuildContext context) {
return BlocBuilder<DietCreateBloc, DietCreateState>(
builder: (context, state) {
if (state is DietCreateInitialState) {
// Это начальное состояние, данные ещё не загружены.
return Container(
color: Colors.white,
);
} else if (state is DietCreateLoadedState) {
// Данные загружены, можно строить экран.
return _buildDietCreate(context, state);
} else if (state is DietCreateErrorState) {
// Произошла ошибка при загрузке данных.
return Center(child: Text(state.errorMessage));
} else {
// Другие состояния, если они есть.
return Container(); // Пустой контейнер для необработанных состояний.
}
},
);
}
_buildDietCreate(BuildContext context, DietCreateLoadedState state) {
return SafeArea(
child: Scaffold(
resizeToAvoidBottomInset: false,
body: Container(
width: double.maxFinite,
decoration: AppDecoration.fillGray,
child: ListView(
children: [
const SizedBox(height: 30),
Container(
margin: EdgeInsets.only(
left: 16,
right: 20,
bottom: 10,
),
padding: EdgeInsets.all(12),
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: [
const SizedBox(height: 16.0),
TextFormField(
controller: state.name,
decoration: InputDecoration(labelText: 'Заголовок'),
),
const SizedBox(height: 16.0),
TextFormField(
controller: state.details,
decoration: InputDecoration(labelText: 'Описание'),
),
const SizedBox(height: 16.0),
// Здесь нужно добавить виджет для загрузки картинки
ElevatedButton(
onPressed: () => context.read<DietCreateBloc>().add(CreateDiet()),
child: Text('Создать'),
),
],
),
),
],
),
),
),
);
}
}

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 [diet_create_screen],
/// and is typically used to hold data that is passed between different parts of the application.
class DietCreateModel extends Equatable {
final String dietID;
DietCreateModel({required String dietID}) : dietID = dietID;
DietCreateModel copyWith(String dietID) {
return DietCreateModel(dietID: dietID);
}
@override
List<Object?> get props => [dietID];
}

View File

@@ -1,120 +0,0 @@
import 'dart:async';
import 'dart:convert';
import 'package:eatvio_app/app_router.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/token_storage.dart';
import 'package:eatvio_app/old/diets/diet_edit_screen/models/diet_edit_model.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart' as http;
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.
class DietEditBloc extends Bloc<DietEditEvent, DietEditState> {
DietEditBloc(DietEditState initialState) : super(initialState) {
on<DietEditInitialEvent>(_onInitialize);
on<EditDiet>(_editDiet);
}
_onInitialize(
DietEditInitialEvent event,
Emitter<DietEditState> emit,
) async {
try {
if (!await TokenStorage.haveTokenInStorage()) {
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final String? dietID = state.dietModelObj?.dietID;
final response = await http.get(
Uri.parse(baseUrl + '/api/diets/show/$dietID'),
headers: {
'Authorization': 'Bearer ${await TokenStorage.getTokenFromStorage()}',
},
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
if (data is Map<String, dynamic>) {
if (state is DietEditLoadedState) {
var currentState = state as DietEditLoadedState;
emit(
currentState.copyWith(
dietModelObj: currentState.dietModelObj,
diet: currentState.diet,
name: currentState.name,
duration: currentState.duration,
details: currentState.details,
),
);
} else {
emit(
DietEditLoadedState(
diet: data,
name: TextEditingController(text: data['name']),
details: TextEditingController(text: data['details']),
duration: TextEditingController(text: data['duration']),
),
);
}
} else {
// Один или несколько ключевых полей отсутствуют, обрабатываем ошибку
emit(DietEditErrorState('Некоторые необходимые данные отсутствуют.'));
}
} else {
// Статус ответа не равен 200, обрабатываем ошибку
emit(DietEditErrorState('Ошибка сервера: ${response.statusCode}'));
}
} catch (e) {
// Ошибка при декодировании JSON или другая ошибка выполнения
emit(DietEditErrorState('Ошибка при получении данных о чатах: $e'));
}
}
FutureOr<void> _editDiet(EditDiet event, Emitter<DietEditState> emit) async {
if (state is DietEditLoadedState) {
var currentState = state as DietEditLoadedState;
int duration = int.parse(currentState.duration!.text) * 7;
var response = await sendDiet(currentState.diet, currentState.name?.text, currentState.details?.text, duration.toString());
if (response.statusCode == 200) {
AppRouter.navigatorKey.currentContext?.goNamed(
'dietScreen',
extra: currentState.diet?['id'].toString(),
);
} else {}
}
}
FutureOr<http.Response> sendDiet(
Map<String, dynamic>? diet,
String? name,
String? details,
String? duration,
) async {
int id = diet?['id'];
String url = '${baseUrl}/api/diets/update/$id';
final Map<String, dynamic> credentials = {
'id': id,
'name': name,
'details': details,
'duration': '14',
};
var response = await http.post(
Uri.parse(url),
headers: {
'Content-Type': 'application/json',
},
body: json.encode(credentials),
encoding: utf8,
);
return response;
}
}

View File

@@ -1,21 +0,0 @@
// ignore_for_file: must_be_immutable
part of 'diet_edit_bloc.dart';
/// Abstract class for all events that can be dispatched from the
///DietEdit widget.
///
/// Events must be immutable and implement the [Equatable] interface.
@immutable
abstract class DietEditEvent extends Equatable {}
/// Event that is dispatched when the DietEdit widget is first created.
class DietEditInitialEvent extends DietEditEvent {
@override
List<Object?> get props => [];
}
class EditDiet extends DietEditEvent {
@override
List<Object?> get props => [];
}

View File

@@ -1,71 +0,0 @@
// ignore_for_file: must_be_immutable
part of 'diet_edit_bloc.dart';
/// Represents the state of DietEdit in the application.
///
class DietEditState extends Equatable {
DietEditState({
this.dietModelObj,
});
DietEditModel? dietModelObj;
@override
List<Object?> get props => [dietModelObj];
DietEditState copyWith({DietEditModel? dietModelObj}) {
return DietEditState(
dietModelObj: dietModelObj ?? this.dietModelObj,
);
}
}
class DietEditLoadedState extends DietEditState {
DietEditLoadedState({
this.dietModelObj,
this.diet,
this.name,
this.details,
this.duration,
});
DietEditModel? dietModelObj;
Map<String, dynamic>? diet;
TextEditingController? name;
TextEditingController? details;
TextEditingController? duration;
@override
List<Object?> get props => [dietModelObj, diet, name, details, duration];
DietEditLoadedState copyWith({
DietEditModel? dietModelObj,
Map<String, dynamic>? diet,
TextEditingController? name,
TextEditingController? details,
TextEditingController? duration,
}) {
return DietEditLoadedState(
dietModelObj: dietModelObj ?? this.dietModelObj,
diet: diet ?? this.diet,
name: name ?? this.name,
details: details ?? this.details,
duration: duration ?? this.duration,
);
}
}
class DietEditErrorState extends DietEditState {
final String errorMessage;
DietEditErrorState(this.errorMessage);
@override
List<Object> get props => [errorMessage];
}
class DietEditInitialState extends DietEditState {
DietEditInitialState({required DietEditModel dietModelObj}) : super(dietModelObj: dietModelObj);
}

View File

@@ -1,120 +0,0 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/theme/app_decoration.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:rive/rive.dart';
import 'bloc/diet_edit_bloc.dart';
import 'models/diet_edit_model.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()),
child: DietEditScreen(),
);
}
Widget build(BuildContext context) {
return BlocBuilder<DietEditBloc, DietEditState>(
builder: (context, state) {
if (state is DietEditInitialState) {
// Это начальное состояние, данные ещё не загружены.
return Container(
color: Colors.white,
);
} else if (state is DietEditLoadedState) {
// Данные загружены, можно строить экран.
return _buildDietEdit(context, state);
} else if (state is DietEditErrorState) {
// Произошла ошибка при загрузке данных.
return Center(child: Text(state.errorMessage));
} else {
// Другие состояния, если они есть.
return Container(); // Пустой контейнер для необработанных состояний.
}
},
);
}
_buildDietEdit(BuildContext context, DietEditLoadedState state) {
return SafeArea(
child: Scaffold(
resizeToAvoidBottomInset: false,
body: Container(
width: double.maxFinite,
decoration: AppDecoration.fillGray,
child: ListView(
children: [
SizedBox(height: 30),
Container(
margin: EdgeInsets.only(
left: 16,
right: 20,
bottom: 10,
),
padding: EdgeInsets.all(12),
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: baseUrl + '/storage/' + (state.diet?['image'] ?? ''),
imageBuilder: (context, imageProvider) => CircleAvatar(
backgroundImage: imageProvider,
radius: 25,
),
placeholder: (context, url) => SizedBox(
height: 32,
width: 32,
child: RiveAnimation.asset(
'assets/rive_animations/loading.riv',
),
),
errorWidget: (context, url, error) => CircleAvatar(
child: Icon(Icons.person),
backgroundColor: Colors.white,
radius: 25,
),
),
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<DietEditBloc>().add(EditDiet()),
child: Text('Сохранить'),
),
],
),
),
],
),
),
),
);
}
}

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 [diet_edit_screen],
/// and is typically used to hold data that is passed between different parts of the application.
class DietEditModel extends Equatable {
final String dietID;
DietEditModel({required String dietID}) : dietID = dietID;
DietEditModel copyWith(String dietID) {
return DietEditModel(dietID: dietID);
}
@override
List<Object?> get props => [dietID];
}

View File

@@ -1,71 +0,0 @@
//import 'package:eatvio_app/old/auth/auth_screen/auth_screen.dart';
import 'dart:async';
import 'dart:convert';
import 'package:eatvio_app/app_router.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/token_storage.dart';
import 'package:eatvio_app/old/diets/diet_select_screen/models/diet_select_model.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart' as http;
import 'package:image_picker/image_picker.dart';
part 'diet_select_event.dart';
part 'diet_select_state.dart';
/// A bloc that manages the state of a DietSelect according to the event that is dispatched to it.
class DietSelectBloc extends Bloc<DietSelectEvent, DietSelectState> {
DietSelectBloc(DietSelectState initialState) : super(initialState) {
on<DietSelectInitialEvent>(_onInitialize);
}
Future<void> _onInitialize(
DietSelectInitialEvent event,
Emitter<DietSelectState> emit,
) async {
try {
if (!await TokenStorage.haveTokenInStorage()) {
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final response = await http.get(
Uri.parse(baseUrl + '/api/diets/index'),
headers: {
'Authorization': 'Bearer ${await TokenStorage.getTokenFromStorage()}',
},
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
if (data is List<dynamic>) {
if (state is DietSelectLoadedState) {
var currentState = state as DietSelectLoadedState;
emit(
(currentState).copyWith(
posts: data,
myProfileModelObj: currentState.myProfileModelObj,
),
);
} else {
emit(
DietSelectLoadedState(
posts: data,
),
);
}
} else {
// Один или несколько ключевых полей отсутствуют, обрабатываем ошибку
emit(DietSelectErrorState('Неверный формат ответа API.'));
}
} else {
// Статус ответа не равен 200, обрабатываем ошибку
emit(DietSelectErrorState('Ошибка сервера: ${response.statusCode}'));
}
} catch (e) {
// Ошибка при декодировании JSON или другая ошибка выполнения
emit(DietSelectErrorState('Ошибка при получении данных ленты: $e'));
}
}
}

View File

@@ -1,25 +0,0 @@
// ignore_for_file: must_be_immutable
part of 'diet_select_bloc.dart';
/// Abstract class for all events that can be dispatched from the
///DietSelect widget.
///
/// Events must be immutable and implement the [Equatable] interface.
@immutable
abstract class DietSelectEvent extends Equatable {}
/// Event that is dispatched when the DietSelect widget is first created.
class DietSelectInitialEvent extends DietSelectEvent {
@override
List<Object?> get props => [];
}
class UploadAvatar extends DietSelectEvent {
UploadAvatar(this.image);
final XFile image;
@override
List<Object> get props => [image];
}

View File

@@ -1,49 +0,0 @@
// ignore_for_file: must_be_immutable
part of 'diet_select_bloc.dart';
class DietSelectState extends Equatable {
const DietSelectState({this.myProfileModelObj});
final DietSelectModel? myProfileModelObj;
@override
List<Object?> get props => [myProfileModelObj];
}
class DietSelectInitialState extends DietSelectState {
DietSelectInitialState({required DietSelectModel myProfileModelObj}) : super(myProfileModelObj: myProfileModelObj);
}
/// Represents the state of DietSelect in the application.
class DietSelectLoadedState extends DietSelectState {
DietSelectLoadedState({this.myProfileModelObj, this.posts}) : super(myProfileModelObj: null);
DietSelectModel? myProfileModelObj;
List<dynamic>? posts;
@override
List<Object?> get props => [
myProfileModelObj,
posts,
];
DietSelectLoadedState copyWith({
DietSelectModel? myProfileModelObj,
List<dynamic>? posts,
}) {
return DietSelectLoadedState(
myProfileModelObj: myProfileModelObj ?? this.myProfileModelObj,
posts: posts ?? this.posts,
);
}
}
class DietSelectErrorState extends DietSelectState {
final String errorMessage;
const DietSelectErrorState(this.errorMessage);
@override
List<Object> get props => [errorMessage];
}

View File

@@ -1,202 +0,0 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/utils/image_constant.dart';
import 'package:eatvio_app/theme/custom_text_style.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/app_bar/custom_app_bar.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:rive/rive.dart';
import 'bloc/diet_select_bloc.dart';
import 'models/diet_select_model.dart';
class DietSelectScreen extends StatelessWidget {
static Widget builder(BuildContext context) {
return BlocProvider<DietSelectBloc>(
create: (context) => DietSelectBloc(DietSelectState(myProfileModelObj: DietSelectModel()))..add(DietSelectInitialEvent()),
child: DietSelectScreen(),
);
}
Widget build(BuildContext context) {
return BlocBuilder<DietSelectBloc, DietSelectState>(
builder: (context, state) {
if (state is DietSelectInitialState) {
// Это начальное состояние, данные ещё не загружены.
return Scaffold(
body: Center(
child: SizedBox(
height: 32,
width: 32,
child: RiveAnimation.asset(
'assets/rive_animations/loading.riv',
),
),
),
);
} else if (state is DietSelectLoadedState) {
// Данные загружены, можно строить экран.
return _buildSetingsScreen(context, state);
} else {
// Другие состояния, если они есть.
return Scaffold(
body: Center(
child: SizedBox(
height: 32,
width: 32,
child: RiveAnimation.asset(
'assets/rive_animations/loading.riv',
),
),
),
);
}
},
);
}
_buildSetingsScreen(BuildContext context, DietSelectLoadedState 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: 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,
leadingWidth: 28,
leading: AppbarLeadingImage(
imagePath: ImageConstant.imgArrowLeft,
margin: EdgeInsets.only(left: 16, top: 15, bottom: 15),
onTap: () => GoRouter.of(context).pop(),
),
title: Padding(
padding: EdgeInsets.only(left: 20),
child: Row(
children: [
AppbarSubtitle(
text: "Диеты",
margin: EdgeInsets.only(left: 80, top: 12, bottom: 9),
),
Spacer(),
Padding(
padding: const EdgeInsets.only(right: 20.0),
child: GestureDetector(
onTap: () => GoRouter.of(context).goNamed('dietCreateScreen'),
child: Icon(Icons.create_outlined),
),
),
],
),
),
),
),
SizedBox(height: 20),
_buildDietList(context, state),
],
),
),
),
),
);
}
_buildDietList(BuildContext context, DietSelectLoadedState state) {
return ListView.builder(
shrinkWrap: true, // Add this line
itemCount: state.posts?.length ?? 0,
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),
),
],
),
child: ListTile(
leading: CachedNetworkImage(
imageUrl: baseUrl + '/storage/' + post['image'],
imageBuilder: (context, imageProvider) => CircleAvatar(
backgroundImage: imageProvider,
radius: 25,
),
placeholder: (context, url) => SizedBox(
height: 32,
width: 32,
child: RiveAnimation.asset(
'assets/rive_animations/loading.riv',
),
),
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(
'dietScreen',
extra: post?['id'].toString(),
),
trailing: GestureDetector(
onTap: () => GoRouter.of(context).goNamed(
'dietEditScreen',
extra: post?['id'].toString(),
),
child: Icon(Icons.edit),
),
),
);
},
);
}
Function onTapBack(BuildContext context) {
return () {
onTapArrowLeft(context);
};
}
onTapArrowLeft(BuildContext context) {
GoRouter.of(context).pop();
}
}

View File

@@ -1,16 +0,0 @@
// ignore_for_file: must_be_immutable
import 'package:equatable/equatable.dart';
/// This class defines the variables used in the [k0_page],
/// and is typically used to hold data that is passed between different parts of the application.
class DietSelectModel extends Equatable {
DietSelectModel();
DietSelectModel copyWith() {
return DietSelectModel();
}
@override
List<Object?> get props => [];
}

View File

@@ -1,80 +0,0 @@
import 'dart:async';
import 'dart:convert';
import 'package:eatvio_app/app_router.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/token_storage.dart';
import 'package:eatvio_app/old/diets/diet_show_screen/models/diet_show_model.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart' as http;
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.
class DietBloc extends Bloc<DietEvent, DietState> {
DietBloc(DietState initialState) : super(initialState) {
on<DietInitialEvent>(_onInitialize);
on<DeleteEntry>(_deleteEntry);
}
_onInitialize(
DietInitialEvent event,
Emitter<DietState> emit,
) async {
try {
if (!await TokenStorage.haveTokenInStorage()) {
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final String? dietID = state.dietModelObj?.dietID;
final response = await http.get(
Uri.parse(baseUrl + '/api/diets/show/$dietID'),
headers: {
'Authorization': 'Bearer ${await TokenStorage.getTokenFromStorage()}',
},
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
if (data is Map<String, dynamic>) {
if (state is DietLoadedState) {
var currentState = state as DietLoadedState;
emit(currentState.copyWith(dietModelObj: currentState.dietModelObj, diet: currentState.diet));
} else {
emit(DietLoadedState(diet: data));
}
} else {
// Один или несколько ключевых полей отсутствуют, обрабатываем ошибку
emit(DietErrorState('Некоторые необходимые данные отсутствуют.'));
}
} else {
// Статус ответа не равен 200, обрабатываем ошибку
emit(DietErrorState('Ошибка сервера: ${response.statusCode}'));
}
} catch (e) {
// Ошибка при декодировании JSON или другая ошибка выполнения
emit(DietErrorState('Ошибка при получении данных о чатах: $e'));
}
}
FutureOr<void> _deleteEntry(DeleteEntry event, Emitter<DietState> emit) async {
if (!await TokenStorage.haveTokenInStorage()) {
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final int id = event.id;
final response = await http.post(
Uri.parse(baseUrl + '/api/diets/delete/$id'),
headers: {
'Authorization': 'Bearer ${await TokenStorage.getTokenFromStorage()}',
},
);
if (response.statusCode == 200) {
AppRouter.navigatorKey.currentContext?.goNamed('dietSelectScreen');
} else {
// Статус ответа не равен 200, обрабатываем ошибку
emit(DietErrorState('Ошибка: ${response.statusCode}'));
}
}
}

View File

@@ -1,25 +0,0 @@
// ignore_for_file: must_be_immutable
part of 'diet_show_bloc.dart';
/// Abstract class for all events that can be dispatched from the
///Diet widget.
///
/// Events must be immutable and implement the [Equatable] interface.
@immutable
abstract class DietEvent extends Equatable {}
/// Event that is dispatched when the Diet widget is first created.
class DietInitialEvent extends DietEvent {
@override
List<Object?> get props => [];
}
class DeleteEntry extends DietEvent {
DeleteEntry(this.id);
final int id;
@override
List<Object?> get props => [id];
}

View File

@@ -1,53 +0,0 @@
// ignore_for_file: must_be_immutable
part of 'diet_show_bloc.dart';
/// Represents the state of Diet in the application.
///
class DietState extends Equatable {
DietState({
this.dietModelObj,
});
DietModel? dietModelObj;
@override
List<Object?> get props => [dietModelObj];
DietState copyWith({DietModel? dietModelObj}) {
return DietState(
dietModelObj: dietModelObj ?? this.dietModelObj,
);
}
}
class DietLoadedState extends DietState {
DietLoadedState({this.dietModelObj, this.diet});
DietModel? dietModelObj;
Map<String, dynamic>? diet;
@override
List<Object?> get props => [dietModelObj, diet];
DietLoadedState copyWith({DietModel? dietModelObj, Map<String, dynamic>? diet}) {
return DietLoadedState(
dietModelObj: dietModelObj ?? this.dietModelObj,
diet: diet ?? this.diet,
);
}
}
class DietErrorState extends DietState {
final String errorMessage;
DietErrorState(this.errorMessage);
@override
List<Object> get props => [errorMessage];
}
class DietInitialState extends DietState {
DietInitialState({required DietModel dietModelObj}) : super(dietModelObj: dietModelObj);
}

View File

@@ -1,147 +0,0 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/theme/app_decoration.dart';
import 'package:eatvio_app/theme/custom_text_style.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:rive/rive.dart';
import 'bloc/diet_show_bloc.dart';
import 'models/diet_show_model.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(),
);
}
Widget build(BuildContext context) {
return BlocBuilder<DietBloc, DietState>(
builder: (context, state) {
if (state is DietInitialState) {
// Это начальное состояние, данные ещё не загружены.
return Container(
color: Colors.white,
);
} else if (state is DietLoadedState) {
// Данные загружены, можно строить экран.
return _buildDiet(context, state);
} else if (state is DietErrorState) {
// Произошла ошибка при загрузке данных.
return Center(child: Text(state.errorMessage));
} else {
// Другие состояния, если они есть.
return Container(); // Пустой контейнер для необработанных состояний.
}
},
);
}
_buildDiet(BuildContext context, DietLoadedState state) {
return SafeArea(
child: Scaffold(
resizeToAvoidBottomInset: false,
body: Container(
width: double.maxFinite,
decoration: AppDecoration.fillGray,
child: ListView(
children: [
SizedBox(height: 30),
Container(
margin: EdgeInsets.only(
left: 16,
right: 20,
bottom: 10,
),
padding: EdgeInsets.all(12),
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: baseUrl + '/storage/' + (state.diet?['image'] ?? ''),
imageBuilder: (context, imageProvider) => CircleAvatar(
backgroundImage: imageProvider,
radius: 25,
),
placeholder: (context, url) => SizedBox(
height: 32,
width: 32,
child: RiveAnimation.asset(
'assets/rive_animations/loading.riv',
),
),
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('Редактировать'),
),
ElevatedButton(
onPressed: () => context.read<DietBloc>().add(DeleteEntry(state.diet?['id'])),
child: Text('Удалить'),
),
],
),
],
),
),
],
),
),
),
);
}
///Handling page based on route
Widget getCurrentPage(
BuildContext context,
String currentRoute,
) {
switch (currentRoute) {
case '/diet':
return DietScreen.builder(context);
default:
return DietScreen();
}
}
/// Navigates to the previous screen.
onTapArrowleftone(BuildContext context) {
GoRouter.of(context).pop();
}
}

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 [diet_screen],
/// and is typically used to hold data that is passed between different parts of the application.
class DietModel extends Equatable {
final String dietID;
DietModel({required String dietID}) : dietID = dietID;
DietModel copyWith(String dietID) {
return DietModel(dietID: dietID);
}
@override
List<Object?> get props => [dietID];
}

View File

@@ -1,103 +0,0 @@
import 'dart:async';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:eatvio_app/app_router.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
import 'package:eatvio_app/core/token_storage.dart';
import 'package:eatvio_app/locator_service.dart';
import 'package:eatvio_app/old/feed/blog_post_create_screen/models/blog_post_create_model.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:image_picker/image_picker.dart';
part 'blog_post_create_event.dart';
part 'blog_post_create_state.dart';
/// A bloc that manages the state of a BlogPostCreate according to the event that is dispatched to it.
class BlogPostCreateBloc extends Bloc<BlogPostCreateEvent, BlogPostCreateState> {
BlogPostCreateBloc(BlogPostCreateState initialState) : super(initialState) {
on<BlogPostCreateInitialEvent>(_onInitialize);
on<CreateBlogPost>(_createBlogPost);
on<UploadImages>(_uploadImages);
}
_onInitialize(
BlogPostCreateInitialEvent event,
Emitter<BlogPostCreateState> emit,
) async {
if (!await TokenStorage.haveTokenInStorage()) {
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
if (state is BlogPostCreateLoadedState) {
var currentState = state as BlogPostCreateLoadedState;
emit(
currentState.copyWith(
blog_postModelObj: currentState.blog_postModelObj,
title: currentState.title,
description: currentState.description,
youtubeURL: currentState.youtubeURL,
token: currentState.token,
),
);
} else {
emit(
BlogPostCreateLoadedState(
token: await TokenStorage.getTokenFromStorage(),
title: TextEditingController(),
description: TextEditingController(),
youtubeURL: TextEditingController(),
),
);
}
}
FutureOr<void> _createBlogPost(
CreateBlogPost event,
Emitter<BlogPostCreateState> emit,
) async {
if (state is BlogPostCreateLoadedState) {
var currentState = state as BlogPostCreateLoadedState;
var response = await sendBlogPost(
currentState.title?.text,
currentState.youtubeURL?.text,
currentState.description?.text,
currentState.image,
);
if (response.statusCode == 200 || response.statusCode == 201) {
AppRouter.navigatorKey.currentContext?.goNamed('feedScreen');
} else {}
}
}
Future<Response> sendBlogPost(String? title, String? youtubeURL, String? description, XFile? image) async {
String url = baseUrl + '/api/blogPosts/create';
FormData formData = FormData.fromMap({
'title': title,
'description': description,
'state': 'active',
'youtube_url': youtubeURL,
'image': image != null ? await MultipartFile.fromFile((await ImageCompressUtility.compressFile(File(image.path))).path, filename: image.name) : null,
});
final response = await sl<Dio>().post(
url,
data: formData,
);
return response;
}
FutureOr<void> _uploadImages(UploadImages event, Emitter<BlogPostCreateState> emit) {
if (state is BlogPostCreateLoadedState) {
var currentState = state as BlogPostCreateLoadedState;
emit(currentState.copyWith(image: event.image));
}
}
}

View File

@@ -1,30 +0,0 @@
// ignore_for_file: must_be_immutable
part of 'blog_post_create_bloc.dart';
/// Abstract class for all events that can be dispatched from the
///BlogPostCreate widget.
///
/// Events must be immutable and implement the [Equatable] interface.
@immutable
abstract class BlogPostCreateEvent extends Equatable {}
/// Event that is dispatched when the BlogPostCreate widget is first created.
class BlogPostCreateInitialEvent extends BlogPostCreateEvent {
@override
List<Object?> get props => [];
}
class CreateBlogPost extends BlogPostCreateEvent {
@override
List<Object?> get props => [];
}
class UploadImages extends BlogPostCreateEvent {
UploadImages(this.image);
final XFile image;
@override
List<Object> get props => [image];
}

View File

@@ -1,87 +0,0 @@
// ignore_for_file: must_be_immutable
part of 'blog_post_create_bloc.dart';
/// Represents the state of BlogPostCreate in the application.
///
class BlogPostCreateState extends Equatable {
BlogPostCreateState({
this.blog_postModelObj,
});
BlogPostCreateModel? blog_postModelObj;
@override
List<Object?> get props => [blog_postModelObj];
BlogPostCreateState copyWith({BlogPostCreateModel? blog_postModelObj}) {
return BlogPostCreateState(
blog_postModelObj: blog_postModelObj ?? this.blog_postModelObj,
);
}
}
class BlogPostCreateLoadedState extends BlogPostCreateState {
BlogPostCreateLoadedState({
this.token,
this.blog_postModelObj,
this.title,
this.description,
this.youtubeURL,
this.image,
this.isLoading = false,
});
BlogPostCreateModel? blog_postModelObj;
TextEditingController? title;
TextEditingController? description;
TextEditingController? youtubeURL;
String? token;
bool isLoading;
XFile? image;
@override
List<Object?> get props => [
blog_postModelObj,
title,
description,
youtubeURL,
token,
image,
isLoading,
];
BlogPostCreateLoadedState copyWith({
BlogPostCreateModel? blog_postModelObj,
TextEditingController? title,
TextEditingController? description,
TextEditingController? youtubeURL,
String? token,
XFile? image,
bool isLoading = false,
}) {
return BlogPostCreateLoadedState(
blog_postModelObj: blog_postModelObj ?? this.blog_postModelObj,
title: title ?? this.title,
description: description ?? this.description,
youtubeURL: youtubeURL ?? this.youtubeURL,
token: token ?? this.token,
image: image ?? this.image,
isLoading: this.isLoading,
);
}
}
class BlogPostCreateErrorState extends BlogPostCreateState {
final String errorMessage;
BlogPostCreateErrorState(this.errorMessage);
@override
List<Object> get props => [errorMessage];
}
class BlogPostCreateInitialState extends BlogPostCreateState {
BlogPostCreateInitialState({required BlogPostCreateModel blog_postModelObj}) : super(blog_postModelObj: blog_postModelObj);
}

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 [blog_post_create_screen],
/// and is typically used to hold data that is passed between different parts of the application.
class BlogPostCreateModel extends Equatable {
final String blog_postID;
BlogPostCreateModel({required String blog_postID}) : blog_postID = blog_postID;
BlogPostCreateModel copyWith(String blog_postID) {
return BlogPostCreateModel(blog_postID: blog_postID);
}
@override
List<Object?> get props => [blog_postID];
}

View File

@@ -1,121 +0,0 @@
import 'dart:async';
import 'dart:convert';
import 'package:eatvio_app/app_router.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/token_storage.dart';
import 'package:eatvio_app/old/feed/blog_post_edit_screen/models/blog_post_edit_model.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart' as http;
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.
class BlogPostEditBloc extends Bloc<BlogPostEditEvent, BlogPostEditState> {
BlogPostEditBloc(BlogPostEditState initialState) : super(initialState) {
on<BlogPostEditInitialEvent>(_onInitialize);
on<EditBlogPost>(_editBlogPost);
}
_onInitialize(
BlogPostEditInitialEvent event,
Emitter<BlogPostEditState> emit,
) async {
try {
if (!await TokenStorage.haveTokenInStorage()) {
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final String? blog_postID = state.blog_postModelObj?.blog_postID;
final response = await http.get(
Uri.parse(baseUrl + '/api/blogPosts/show/$blog_postID'),
headers: {
'Authorization': 'Bearer ${await TokenStorage.getTokenFromStorage()}',
},
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
if (data is Map<String, dynamic>) {
if (state is BlogPostEditLoadedState) {
var currentState = state as BlogPostEditLoadedState;
emit(
currentState.copyWith(
blog_postModelObj: currentState.blog_postModelObj,
blog_post: currentState.blog_post,
title: currentState.title,
description: currentState.description,
youtubeURL: currentState.youtubeURL,
),
);
} else {
emit(
BlogPostEditLoadedState(
blog_post: data,
title: TextEditingController(text: data['title']),
description: TextEditingController(text: data['description']),
youtubeURL: TextEditingController(text: data['youtubeURL']),
),
);
}
} else {
// Один или несколько ключевых полей отсутствуют, обрабатываем ошибку
emit(BlogPostEditErrorState('Некоторые необходимые данные отсутствуют.'));
}
} else {
// Статус ответа не равен 200, обрабатываем ошибку
emit(BlogPostEditErrorState('Ошибка сервера: ${response.statusCode}'));
}
} catch (e) {
// Ошибка при декодировании JSON или другая ошибка выполнения
emit(BlogPostEditErrorState('Ошибка при получении данных о чатах: $e'));
}
}
FutureOr<void> _editBlogPost(EditBlogPost event, Emitter<BlogPostEditState> emit) async {
if (state is BlogPostEditLoadedState) {
var currentState = state as BlogPostEditLoadedState;
var response = await sendBlogPost(
currentState.blog_post,
currentState.title?.text,
currentState.youtubeURL?.text,
currentState.description?.text,
);
if (response.statusCode == 200) {
AppRouter.navigatorKey.currentContext?.goNamed('blogPostScreen', extra: currentState.blog_post?['id'].toString());
} else {}
}
}
FutureOr<http.Response> sendBlogPost(
Map<String, dynamic>? blog_post,
String? title,
String? youtubeURL,
String? description,
) async {
int id = blog_post?['id'];
String url = '${baseUrl}/api/feed/update/$id';
final Map<String, dynamic> credentials = {
'id': id,
'title': title,
'description': description,
'youtubeURL': youtubeURL,
};
var response = await http.post(
Uri.parse(url),
headers: {
'Content-Type': 'application/json',
},
body: json.encode(credentials),
encoding: utf8,
);
return response;
}
}

View File

@@ -1,21 +0,0 @@
// ignore_for_file: must_be_immutable
part of 'blog_post_edit_bloc.dart';
/// Abstract class for all events that can be dispatched from the
///BlogPostEdit widget.
///
/// Events must be immutable and implement the [Equatable] interface.
@immutable
abstract class BlogPostEditEvent extends Equatable {}
/// Event that is dispatched when the BlogPostEdit widget is first created.
class BlogPostEditInitialEvent extends BlogPostEditEvent {
@override
List<Object?> get props => [];
}
class EditBlogPost extends BlogPostEditEvent {
@override
List<Object?> get props => [];
}

View File

@@ -1,71 +0,0 @@
// ignore_for_file: must_be_immutable
part of 'blog_post_edit_bloc.dart';
/// Represents the state of BlogPostEdit in the application.
///
class BlogPostEditState extends Equatable {
BlogPostEditState({
this.blog_postModelObj,
});
BlogPostEditModel? blog_postModelObj;
@override
List<Object?> get props => [blog_postModelObj];
BlogPostEditState copyWith({BlogPostEditModel? blog_postModelObj}) {
return BlogPostEditState(
blog_postModelObj: blog_postModelObj ?? this.blog_postModelObj,
);
}
}
class BlogPostEditLoadedState extends BlogPostEditState {
BlogPostEditLoadedState({
this.blog_postModelObj,
this.blog_post,
this.title,
this.description,
this.youtubeURL,
});
BlogPostEditModel? blog_postModelObj;
Map<String, dynamic>? blog_post;
TextEditingController? title;
TextEditingController? description;
TextEditingController? youtubeURL;
@override
List<Object?> get props => [blog_postModelObj, blog_post, title, description, youtubeURL];
BlogPostEditLoadedState copyWith({
BlogPostEditModel? blog_postModelObj,
Map<String, dynamic>? blog_post,
TextEditingController? title,
TextEditingController? description,
TextEditingController? youtubeURL,
}) {
return BlogPostEditLoadedState(
blog_postModelObj: blog_postModelObj ?? this.blog_postModelObj,
blog_post: blog_post ?? this.blog_post,
title: title ?? this.title,
description: description ?? this.description,
youtubeURL: youtubeURL ?? this.youtubeURL,
);
}
}
class BlogPostEditErrorState extends BlogPostEditState {
final String errorMessage;
BlogPostEditErrorState(this.errorMessage);
@override
List<Object> get props => [errorMessage];
}
class BlogPostEditInitialState extends BlogPostEditState {
BlogPostEditInitialState({required BlogPostEditModel blog_postModelObj}) : super(blog_postModelObj: blog_postModelObj);
}

View File

@@ -1,139 +0,0 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/theme/app_decoration.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:rive/rive.dart';
import 'bloc/blog_post_edit_bloc.dart';
import 'models/blog_post_edit_model.dart';
class BlogPostEditScreen extends StatelessWidget {
const BlogPostEditScreen({Key? key}) : super(key: key);
static Widget builder(BuildContext context, blog_postID) {
return BlocProvider<BlogPostEditBloc>(
create: (context) =>
BlogPostEditBloc(BlogPostEditState(blog_postModelObj: BlogPostEditModel(blog_postID: blog_postID.toString())))..add(BlogPostEditInitialEvent()),
child: BlogPostEditScreen(),
);
}
Widget build(BuildContext context) {
return BlocBuilder<BlogPostEditBloc, BlogPostEditState>(
builder: (context, state) {
if (state is BlogPostEditInitialState) {
return Scaffold(
body: Center(
child: SizedBox(
height: 32,
width: 32,
child: RiveAnimation.asset(
'assets/rive_animations/loading.riv',
),
),
),
);
} else if (state is BlogPostEditLoadedState) {
// Данные загружены, можно строить экран.
return _buildBlogPostEdit(context, state);
} else if (state is BlogPostEditErrorState) {
// Произошла ошибка при загрузке данных.
return Center(child: Text(state.errorMessage));
} else {
return Scaffold(
body: Center(
child: SizedBox(
height: 32,
width: 32,
child: RiveAnimation.asset(
'assets/rive_animations/loading.riv',
),
),
),
);
}
},
);
}
_buildBlogPostEdit(BuildContext context, BlogPostEditLoadedState state) {
return Scaffold(
resizeToAvoidBottomInset: false,
body: Container(
width: double.maxFinite,
decoration: AppDecoration.fillGray,
child: ListView(
children: [
SizedBox(height: 30),
Container(
margin: EdgeInsets.only(
left: 16,
right: 20,
bottom: 10,
),
padding: EdgeInsets.all(12),
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: baseUrl + '/storage/' + (state.blog_post?['image'] ?? ''),
imageBuilder: (context, imageProvider) => CircleAvatar(
backgroundImage: imageProvider,
radius: 25,
),
placeholder: (context, url) => SizedBox(
height: 32,
width: 32,
child: RiveAnimation.asset(
'assets/rive_animations/loading.riv',
),
),
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.youtubeURL,
decoration: InputDecoration(labelText: 'Ссылка'),
),
SizedBox(height: 16.0),
// Здесь нужно добавить виджет для загрузки картинки
ElevatedButton(
onPressed: () => context.read<BlogPostEditBloc>().add(EditBlogPost()),
child: Text('Сохранить'),
),
],
),
),
],
),
),
);
}
}

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 [blog_post_edit_screen],
/// and is typically used to hold data that is passed between different parts of the application.
class BlogPostEditModel extends Equatable {
final String blog_postID;
BlogPostEditModel({required String blog_postID}) : blog_postID = blog_postID;
BlogPostEditModel copyWith(String blog_postID) {
return BlogPostEditModel(blog_postID: blog_postID);
}
@override
List<Object?> get props => [blog_postID];
}

View File

@@ -1,22 +0,0 @@
// ignore_for_file: must_be_immutable
import 'package:eatvio_app/old/feed/blog_post_screen/models/comment_model.dart';
import 'package:equatable/equatable.dart';
class BlogPostModel extends Equatable {
final String blog_postID;
BlogPostModel({required this.blog_postID, this.comments = const []});
List<CommentModel> comments;
BlogPostModel copyWith({String? blog_postID, List<CommentModel>? comments}) {
return BlogPostModel(
blog_postID: blog_postID ?? this.blog_postID,
comments: comments ?? this.comments,
);
}
@override
List<Object?> get props => [blog_postID, comments];
}

View File

@@ -1,27 +0,0 @@
import 'package:eatvio_app/core/utils/image_constant.dart';
class CommentModel {
CommentModel({
this.userImage,
this.username,
this.dateText,
this.greetingText,
required this.id,
}) {
userImage = userImage ?? ImageConstant.imageNotFound;
username = username;
dateText = dateText;
greetingText = greetingText;
id = id;
}
String? userImage;
String? username;
String? dateText;
String? greetingText;
String id;
}

View File

@@ -1,113 +0,0 @@
// ignore_for_file: must_be_immutable
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/utils/image_constant.dart';
import 'package:eatvio_app/old/feed/blog_post_screen/models/comment_model.dart';
import 'package:eatvio_app/theme/custom_text_style.dart';
import 'package:eatvio_app/theme/theme_helper.dart';
import 'package:eatvio_app/widgets/custom_image_view.dart';
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
class CommentWidget extends StatelessWidget {
CommentWidget(this.commentModelObj, {Key? key})
: super(
key: key,
);
CommentModel commentModelObj;
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(
left: 16,
right: 20,
bottom: 10,
),
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, // Move the main axis alignment to space between
crossAxisAlignment: CrossAxisAlignment.center, // Center the items vertically
children: [
CachedNetworkImage(
imageUrl: baseUrl + '/storage/' + commentModelObj.userImage!,
imageBuilder: (context, imageProvider) => CircleAvatar(
backgroundImage: imageProvider,
radius: 25,
),
placeholder: (context, url) => SizedBox(
height: 32,
width: 32,
child: RiveAnimation.asset(
'assets/rive_animations/loading.riv',
),
),
errorWidget: (context, url, error) => CircleAvatar(
child: Icon(Icons.person),
backgroundColor: Colors.white,
radius: 25,
),
),
Expanded(
child: Padding(
padding: EdgeInsets.only(
left: 12,
top: 2,
bottom: 11,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 227,
child: Row(
children: [
Text(
commentModelObj.username!.length <= 15 ? commentModelObj.username! : commentModelObj.username!.substring(0, 15) + '...',
style: theme.textTheme.titleMedium,
),
Spacer(),
Padding(
padding: EdgeInsets.only(left: 6),
child: Text(
commentModelObj.dateText!,
style: theme.textTheme.bodyMedium,
),
),
],
),
),
SizedBox(height: 6),
Text(
commentModelObj.greetingText!,
style: CustomTextStyles.bodyMediumBluegray800,
),
],
),
),
),
CustomImageView(
imagePath: ImageConstant.imgSend,
height: 30,
width: 30,
margin: EdgeInsets.only(left: 5),
),
],
),
);
}
}

View File

@@ -1,23 +0,0 @@
import 'package:eatvio_app/theme/custom_text_style.dart';
import 'package:flutter/material.dart';
class ScrollItem extends StatelessWidget {
final String title;
const ScrollItem({required this.title});
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
margin: EdgeInsets.only(right: 10),
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(20)),
),
child: Text(
title,
style: CustomTextStyles.titleMediumSemiBold,
),
);
}
}

View File

@@ -1,152 +0,0 @@
import 'package:eatvio_app/theme/custom_text_style.dart';
import 'package:flutter/material.dart';
class CustomSearchView extends StatelessWidget {
CustomSearchView({
Key? key,
this.alignment,
this.width,
this.scrollPadding,
this.controller,
this.focusNode,
this.autofocus = false,
this.textStyle,
this.textInputType = TextInputType.text,
this.maxLines,
this.hintText,
this.hintStyle,
this.prefix,
this.prefixConstraints,
this.suffix,
this.suffixConstraints,
this.contentPadding,
this.borderDecoration,
this.fillColor,
this.filled = true,
this.validator,
this.onChanged,
}) : super(
key: key,
);
final Alignment? alignment;
final double? width;
final TextEditingController? scrollPadding;
final TextEditingController? controller;
final FocusNode? focusNode;
final bool? autofocus;
final TextStyle? textStyle;
final TextInputType? textInputType;
final int? maxLines;
final String? hintText;
final TextStyle? hintStyle;
final Widget? prefix;
final BoxConstraints? prefixConstraints;
final Widget? suffix;
final BoxConstraints? suffixConstraints;
final EdgeInsets? contentPadding;
final InputBorder? borderDecoration;
final Color? fillColor;
final bool? filled;
final FormFieldValidator<String>? validator;
final Function(String)? onChanged;
@override
Widget build(BuildContext 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),
controller: controller,
focusNode: focusNode,
onTapOutside: (event) => onTapOutside(),
autofocus: autofocus!,
style: textStyle ?? CustomTextStyles.bodySmallMulishBluegray800,
keyboardType: textInputType,
maxLines: maxLines ?? 1,
decoration: decoration,
validator: validator,
onChanged: (String value) => onChanged?.call(value),
),
);
void onTapOutside() {
if (focusNode != null) {
focusNode?.unfocus();
} else {
FocusManager.instance.primaryFocus?.unfocus();
}
}
InputDecoration get decoration => InputDecoration(
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,
),
suffixIconConstraints: suffixConstraints ??
BoxConstraints(
maxHeight: 40,
),
isDense: true,
contentPadding: contentPadding ??
EdgeInsets.only(
top: 11,
right: 11,
bottom: 11,
),
fillColor: fillColor ?? Colors.grey[200],
filled: filled,
border: borderDecoration ??
OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide.none,
),
enabledBorder: borderDecoration ??
OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide.none,
),
focusedBorder: borderDecoration ??
OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide.none,
),
);
}

View File

@@ -1,131 +0,0 @@
import 'dart:async';
import 'dart:convert';
import 'package:eatvio_app/app_router.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/token_storage.dart';
import 'package:eatvio_app/data/models/selectionPopupModel/selection_popup_model.dart';
import 'package:eatvio_app/old/glossary/glossary_screen/models/glossary_screen_model.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart' as http;
part 'glossary_screen_event.dart';
part 'glossary_screen_state.dart';
/// A bloc that manages the state of a Glossary according to the event that is dispatched to it.
class GlossaryBloc extends Bloc<GlossaryEvent, GlossaryState> {
GlossaryBloc(GlossaryState initialState) : super(initialState) {
on<GlossaryInitialEvent>(_onInitialize);
on<ChangeProductGroup>(_changeProductGroup);
}
_onInitialize(
GlossaryInitialEvent event,
Emitter<GlossaryState> emit,
) async {
try {
if (!await TokenStorage.haveTokenInStorage()) {
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final responseProductGroups = await http.get(
Uri.parse(baseUrl + '/api/glossaries/group/index'),
headers: {
'Authorization': 'Bearer ${await TokenStorage.getTokenFromStorage()}',
},
);
if (responseProductGroups.statusCode == 200) {
final dataProductGroups = jsonDecode(responseProductGroups.body);
if (dataProductGroups is List<dynamic>) {
if (state is GlossaryLoadedState) {
var currentState = state as GlossaryLoadedState;
emit(
currentState.copyWith(
glossaryModelObj: currentState.glossaryModelObj,
token: currentState.token,
productGroups: currentState.productGroups,
productList: currentState.productList,
),
);
} else {
List<SelectionPopupModel> productGroupsModel = _fillProductGroups(dataProductGroups);
emit(
GlossaryLoadedState(
token: await TokenStorage.getTokenFromStorage(),
productGroups: productGroupsModel,
productList: [],
),
);
await _getProductListById(await TokenStorage.getTokenFromStorage(), emit, productGroupsModel[0].id);
}
} else {
// Один или несколько ключевых полей отсутствуют, обрабатываем ошибку
emit(GlossaryErrorState('Список категорий некорректного формата'));
}
} else {
// Статус ответа не равен 200, обрабатываем ошибку
emit(
GlossaryErrorState(
'Ошибка сервера: ${responseProductGroups.statusCode}',
),
);
}
} catch (e) {
// Ошибка при декодировании JSON или другая ошибка выполнения
emit(GlossaryErrorState('Ошибка при получении данных о чатах: $e'));
}
}
void _changeProductGroup(
ChangeProductGroup event,
Emitter<GlossaryState> emit,
) async {
if (state is GlossaryLoadedState) {
var currentState = state as GlossaryLoadedState;
await _getProductListById(currentState.token, emit, event.id);
}
}
Future<void> _getProductListById(
token,
Emitter<GlossaryState> emit,
int? productGroupID,
) async {
final responseProductList = await http.get(
Uri.parse(baseUrl + '/api/glossary/index/$productGroupID'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $token',
},
);
final dataProductList = jsonDecode(responseProductList.body);
if (dataProductList is List<dynamic>) {
var currentState = state as GlossaryLoadedState;
emit(
currentState.copyWith(
glossaryModelObj: currentState.glossaryModelObj,
productGroups: currentState.productGroups,
productList: dataProductList,
),
);
} else {
emit(GlossaryErrorState('Список продуктов некорректного формата'));
}
}
List<SelectionPopupModel> _fillProductGroups(List<dynamic> productGroups) {
List<SelectionPopupModel> result = [];
for (int i = 0; i < productGroups.length; i++) {
result.add(
SelectionPopupModel(
id: productGroups[i]['id'],
title: productGroups[i]['name'],
isSelected: i == 0,
),
);
}
return result;
}
}

View File

@@ -1,34 +0,0 @@
// ignore_for_file: must_be_immutable
part of 'glossary_screen_bloc.dart';
/// Abstract class for all events that can be dispatched from the
///Glossary widget.
///
/// Events must be immutable and implement the [Equatable] interface.
@immutable
abstract class GlossaryEvent extends Equatable {}
/// Event that is dispatched when the Glossary widget is first created.
class GlossaryInitialEvent extends GlossaryEvent {
@override
List<Object?> get props => [];
}
class DeleteEntry extends GlossaryEvent {
DeleteEntry(this.id);
final int id;
@override
List<Object?> get props => [id];
}
class ChangeProductGroup extends GlossaryEvent {
ChangeProductGroup(this.id);
final int id;
@override
List<Object?> get props => [id];
}

View File

@@ -1,67 +0,0 @@
// ignore_for_file: must_be_immutable
part of 'glossary_screen_bloc.dart';
/// Represents the state of Glossary in the application.
///
class GlossaryState extends Equatable {
GlossaryState({
this.glossaryModelObj,
});
GlossaryModel? glossaryModelObj;
@override
List<Object?> get props => [glossaryModelObj];
GlossaryState copyWith({GlossaryModel? glossaryModelObj}) {
return GlossaryState(
glossaryModelObj: glossaryModelObj ?? this.glossaryModelObj,
);
}
}
class GlossaryLoadedState extends GlossaryState {
GlossaryLoadedState({
this.glossaryModelObj,
this.productGroups,
this.productList,
this.token,
});
GlossaryModel? glossaryModelObj;
String? token;
List<SelectionPopupModel>? productGroups;
List<dynamic>? productList;
@override
List<Object?> get props => [glossaryModelObj, productGroups, productList, token];
GlossaryLoadedState copyWith({
GlossaryModel? glossaryModelObj,
String? token,
List<SelectionPopupModel>? productGroups,
List<dynamic>? productList,
}) {
return GlossaryLoadedState(
glossaryModelObj: glossaryModelObj ?? this.glossaryModelObj,
productGroups: productGroups ?? this.productGroups,
productList: productList ?? this.productList,
token: token ?? this.token,
);
}
}
class GlossaryErrorState extends GlossaryState {
final String errorMessage;
GlossaryErrorState(this.errorMessage);
@override
List<Object> get props => [errorMessage];
}
class GlossaryInitialState extends GlossaryState {
GlossaryInitialState({required GlossaryModel glossaryModelObj}) : super(glossaryModelObj: glossaryModelObj);
}

View File

@@ -1,355 +0,0 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/data/models/selectionPopupModel/selection_popup_model.dart';
import 'package:eatvio_app/old/glossary/glossary_screen/widgets/glossary_scroll_item.dart';
import 'package:eatvio_app/old/glossary/glossary_screen/widgets/glossary_search_widget.dart';
import 'package:eatvio_app/theme/custom_button_style.dart';
import 'package:eatvio_app/theme/custom_text_style.dart';
import 'package:eatvio_app/widgets/custom_drop_down.dart';
import 'package:eatvio_app/widgets/custom_outlined_button.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:rive/rive.dart';
import 'bloc/glossary_screen_bloc.dart';
import 'models/glossary_screen_model.dart';
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(),
);
}
Widget build(BuildContext context) {
return BlocBuilder<GlossaryBloc, GlossaryState>(
builder: (context, state) {
if (state is GlossaryInitialState) {
// Это начальное состояние, данные ещё не загружены.
return Scaffold(
body: Center(
child: SizedBox(
height: 32,
width: 32,
child: RiveAnimation.asset(
'assets/rive_animations/loading.riv',
),
),
),
);
} else if (state is GlossaryLoadedState) {
// Данные загружены, можно строить экран.
return _buildGlossary(context, state);
} else if (state is GlossaryErrorState) {
// Произошла ошибка при загрузке данных.
return Center(child: Text(state.errorMessage));
} else {
// Другие состояния, если они есть.
return Scaffold(
body: Center(
child: SizedBox(
height: 32,
width: 32,
child: RiveAnimation.asset(
'assets/rive_animations/loading.riv',
),
),
),
);
}
},
);
}
_buildGlossary(BuildContext context, GlossaryLoadedState 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),
],
),
),
),
),
);
}
_buildSearch(BuildContext context) {
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),
),
],
),
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
children: [
CustomSearchView(
hintText: "Поиск записей",
),
SizedBox(height: 10),
CustomOutlinedButton(
text: "Найти продукт",
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
),
],
),
),
);
}
_buildMenu(BuildContext context) {
return Container(
padding: EdgeInsets.all(12),
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: Column(
children: [
Align(
alignment: Alignment.centerLeft,
child: Text(
"Поиск по алфавиту",
style: CustomTextStyles.titleMediumSemiBold,
),
),
SizedBox(
height: 8,
),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: List.generate(33, _fillAlphabet),
),
),
],
),
);
}
_buildProducts(BuildContext context, GlossaryLoadedState state) {
return Container(
padding: EdgeInsets.all(12),
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: Column(
children: [
Align(
alignment: Alignment.centerLeft,
child: Text(
"Группа продуктов",
style: CustomTextStyles.titleMediumSemiBold,
),
),
SizedBox(
height: 8,
),
CustomDropDown(
items: state.productGroups,
value: state.productGroups?[0],
fillColor: Colors.grey[200]!,
hintText: 'Выберите категорию',
borderDecoration: OutlineInputBorder(
borderRadius: const BorderRadius.all(Radius.circular(8)),
borderSide: BorderSide(color: Colors.grey[200]!, width: 1),
),
contentPadding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 9,
),
onChanged: _changeProductGroup(context, state),
width: MediaQuery.of(context).size.width, // Set a percentage value as per your layout
),
SizedBox(
height: 8,
),
Align(alignment: Alignment.centerLeft, child: Text("*КБЖУ указано за 100гр")),
SizedBox(
height: 8,
),
_buildProductList(context, state),
],
),
);
}
_buildProductList(BuildContext context, GlossaryLoadedState state) {
List<Widget> postWidgets = [];
if (state.productList != null) {
for (var post in state.productList!) {
postWidgets.add(_buildProductEntry(post, context));
}
}
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: postWidgets,
);
}
_buildProductEntry(product, BuildContext context) {
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: 1,
blurRadius: 3,
offset: Offset(0, 3),
),
],
),
child: Column(
children: [
ListTile(
trailing: CachedNetworkImage(
imageUrl: baseUrl + '/storage/' + (product['image'] ?? ''),
imageBuilder: (context, imageProvider) => CircleAvatar(
backgroundImage: imageProvider,
radius: 25,
),
placeholder: (context, url) => SizedBox(
height: 32,
width: 32,
child: RiveAnimation.asset(
'assets/rive_animations/loading.riv',
),
),
errorWidget: (context, url, error) => CircleAvatar(
child: Icon(Icons.person),
backgroundColor: Colors.white,
radius: 25,
),
),
title: Text(
product?['name'] ?? '',
style: CustomTextStyles.titleMediumSemiBold,
),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Row(
children: [
Column(
children: [
Text("Ккал - ${product['energy_on_100gr'].toString()}"),
SizedBox(height: 8),
Text("Белки - ${product['proteins_on_100gr'].toString()}"),
],
),
Spacer(),
Padding(
padding: const EdgeInsets.only(right: 24.0),
child: Column(
children: [
Text("Жиры - ${product['fats_on_100gr'].toString()}"),
SizedBox(height: 8),
Text("Углеводы - ${product['carbohydrates_on_100gr'].toString()}"),
],
),
),
],
),
],
),
),
],
),
);
}
Widget _fillAlphabet(int index) {
return ScrollItem(title: String.fromCharCode(1040 + index));
}
List<SelectionPopupModel> fillProductGroups(List<dynamic> productGroups) {
List<SelectionPopupModel> result = [];
for (int i = 0; i < productGroups.length; i++) {
result.add(
SelectionPopupModel(
id: productGroups[i]['id'],
title: productGroups[i]['name'],
isSelected: true,
),
);
}
return result;
}
///Handling page based on route
Widget getCurrentPage(
BuildContext context,
String currentRoute,
) {
switch (currentRoute) {
case '/glossary':
return GlossaryScreen.builder(context);
default:
return GlossaryScreen();
}
}
_changeProductGroup(BuildContext context, GlossaryLoadedState state) {
return (value) {
context.read<GlossaryBloc>().add(ChangeProductGroup(value.id));
};
}
}

View File

@@ -1,17 +0,0 @@
// ignore_for_file: must_be_immutable
import 'package:equatable/equatable.dart';
/// This class defines the variables used in the [glossary_screen],
/// and is typically used to hold data that is passed between different parts of the application.
class GlossaryModel extends Equatable {
GlossaryModel();
GlossaryModel copyWith() {
return GlossaryModel();
}
@override
List<Object?> get props => [];
}

View File

@@ -1,23 +0,0 @@
import 'package:eatvio_app/theme/custom_text_style.dart';
import 'package:flutter/material.dart';
class ScrollItem extends StatelessWidget {
final String title;
const ScrollItem({required this.title});
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
margin: EdgeInsets.only(right: 10),
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(20)),
),
child: Text(
title,
style: CustomTextStyles.titleMediumSemiBold,
),
);
}
}

View File

@@ -1,152 +0,0 @@
import 'package:eatvio_app/theme/custom_text_style.dart';
import 'package:flutter/material.dart';
class CustomSearchView extends StatelessWidget {
CustomSearchView({
Key? key,
this.alignment,
this.width,
this.scrollPadding,
this.controller,
this.focusNode,
this.autofocus = false,
this.textStyle,
this.textInputType = TextInputType.text,
this.maxLines,
this.hintText,
this.hintStyle,
this.prefix,
this.prefixConstraints,
this.suffix,
this.suffixConstraints,
this.contentPadding,
this.borderDecoration,
this.fillColor,
this.filled = true,
this.validator,
this.onChanged,
}) : super(
key: key,
);
final Alignment? alignment;
final double? width;
final TextEditingController? scrollPadding;
final TextEditingController? controller;
final FocusNode? focusNode;
final bool? autofocus;
final TextStyle? textStyle;
final TextInputType? textInputType;
final int? maxLines;
final String? hintText;
final TextStyle? hintStyle;
final Widget? prefix;
final BoxConstraints? prefixConstraints;
final Widget? suffix;
final BoxConstraints? suffixConstraints;
final EdgeInsets? contentPadding;
final InputBorder? borderDecoration;
final Color? fillColor;
final bool? filled;
final FormFieldValidator<String>? validator;
final Function(String)? onChanged;
@override
Widget build(BuildContext 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),
controller: controller,
focusNode: focusNode,
onTapOutside: (event) => onTapOutside(),
autofocus: autofocus!,
style: textStyle ?? CustomTextStyles.bodySmallMulishBluegray800,
keyboardType: textInputType,
maxLines: maxLines ?? 1,
decoration: decoration,
validator: validator,
onChanged: (String value) => onChanged?.call(value),
),
);
void onTapOutside() {
if (focusNode != null) {
focusNode?.unfocus();
} else {
FocusManager.instance.primaryFocus?.unfocus();
}
}
InputDecoration get decoration => InputDecoration(
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,
),
suffixIconConstraints: suffixConstraints ??
BoxConstraints(
maxHeight: 40,
),
isDense: true,
contentPadding: contentPadding ??
EdgeInsets.only(
top: 11,
right: 11,
bottom: 11,
),
fillColor: fillColor ?? Colors.grey[200],
filled: filled,
border: borderDecoration ??
OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide.none,
),
enabledBorder: borderDecoration ??
OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide.none,
),
focusedBorder: borderDecoration ??
OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide.none,
),
);
}

File diff suppressed because one or more lines are too long

View File

@@ -1,21 +0,0 @@
// ignore_for_file: must_be_immutable
part of 'plan_create_bloc.dart';
/// Abstract class for all events that can be dispatched from the
///PlanCreate widget.
///
/// Events must be immutable and implement the [Equatable] interface.
@immutable
abstract class PlanCreateEvent extends Equatable {}
/// Event that is dispatched when the PlanCreate widget is first created.
class PlanCreateInitialEvent extends PlanCreateEvent {
@override
List<Object?> get props => [];
}
class CreatePlan extends PlanCreateEvent {
@override
List<Object?> get props => [];
}

View File

@@ -1,67 +0,0 @@
// ignore_for_file: must_be_immutable
part of 'plan_create_bloc.dart';
/// Represents the state of PlanCreate in the application.
///
class PlanCreateState extends Equatable {
PlanCreateState({
this.planModelObj,
});
PlanCreateModel? planModelObj;
@override
List<Object?> get props => [planModelObj];
PlanCreateState copyWith({PlanCreateModel? planModelObj}) {
return PlanCreateState(
planModelObj: planModelObj ?? this.planModelObj,
);
}
}
class PlanCreateLoadedState extends PlanCreateState {
PlanCreateLoadedState({
this.planModelObj,
this.name,
this.details,
});
PlanCreateModel? planModelObj;
TextEditingController? name;
TextEditingController? details;
@override
List<Object?> get props => [
planModelObj,
name,
details,
];
PlanCreateLoadedState copyWith({
PlanCreateModel? planModelObj,
TextEditingController? name,
TextEditingController? details,
}) {
return PlanCreateLoadedState(
planModelObj: planModelObj ?? this.planModelObj,
name: name ?? this.name,
details: details ?? this.details,
);
}
}
class PlanCreateErrorState extends PlanCreateState {
final String errorMessage;
PlanCreateErrorState(this.errorMessage);
@override
List<Object> get props => [errorMessage];
}
class PlanCreateInitialState extends PlanCreateState {
PlanCreateInitialState({required PlanCreateModel planModelObj}) : super(planModelObj: planModelObj);
}

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 [plan_create_screen],
/// and is typically used to hold data that is passed between different parts of the application.
class PlanCreateModel extends Equatable {
final String planID;
PlanCreateModel({required String planID}) : planID = planID;
PlanCreateModel copyWith(String planID) {
return PlanCreateModel(planID: planID);
}
@override
List<Object?> get props => [planID];
}

View File

@@ -1,98 +0,0 @@
import 'package:eatvio_app/theme/app_decoration.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'bloc/plan_create_bloc.dart';
import 'models/plan_create_model.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()),
child: PlanCreateScreen(),
);
}
Widget build(BuildContext context) {
return BlocBuilder<PlanCreateBloc, PlanCreateState>(
builder: (context, state) {
if (state is PlanCreateInitialState) {
// Это начальное состояние, данные ещё не загружены.
return Container(
color: Colors.white,
);
} else if (state is PlanCreateLoadedState) {
// Данные загружены, можно строить экран.
return _buildPlanCreate(context, state);
} else if (state is PlanCreateErrorState) {
// Произошла ошибка при загрузке данных.
return Center(child: Text(state.errorMessage));
} else {
// Другие состояния, если они есть.
return Container(); // Пустой контейнер для необработанных состояний.
}
},
);
}
_buildPlanCreate(BuildContext context, PlanCreateLoadedState state) {
return SafeArea(
child: Scaffold(
resizeToAvoidBottomInset: false,
body: Container(
width: double.maxFinite,
decoration: AppDecoration.fillGray,
child: ListView(
children: [
SizedBox(height: 30),
Container(
margin: EdgeInsets.only(
left: 16,
right: 20,
bottom: 10,
),
padding: EdgeInsets.all(12),
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<PlanCreateBloc>().add(CreatePlan()),
child: Text('Создать'),
),
],
),
),
],
),
),
),
);
}
}

View File

@@ -1,120 +0,0 @@
import 'dart:async';
import 'dart:convert';
import 'package:eatvio_app/app_router.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/token_storage.dart';
import 'package:eatvio_app/old/plans/plan_edit_screen/models/plan_edit_model.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart' as http;
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.
class PlanEditBloc extends Bloc<PlanEditEvent, PlanEditState> {
PlanEditBloc(PlanEditState initialState) : super(initialState) {
on<PlanEditInitialEvent>(_onInitialize);
on<EditPlan>(_editPlan);
}
_onInitialize(
PlanEditInitialEvent event,
Emitter<PlanEditState> emit,
) async {
try {
if (!await TokenStorage.haveTokenInStorage()) {
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final String? planID = state.planModelObj?.planID;
final response = await http.get(
Uri.parse(baseUrl + '/api/plans/show/$planID'),
headers: {
'Authorization': 'Bearer ${await TokenStorage.getTokenFromStorage()}',
},
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
if (data is Map<String, dynamic>) {
if (state is PlanEditLoadedState) {
var currentState = state as PlanEditLoadedState;
emit(
currentState.copyWith(
planModelObj: currentState.planModelObj,
plan: currentState.plan,
name: currentState.name,
duration: currentState.duration,
details: currentState.details,
),
);
} else {
emit(
PlanEditLoadedState(
plan: data,
name: TextEditingController(text: data['name']),
details: TextEditingController(text: data['details']),
duration: TextEditingController(text: data['duration'].toString()),
),
);
}
} else {
// Один или несколько ключевых полей отсутствуют, обрабатываем ошибку
emit(PlanEditErrorState('Некоторые необходимые данные отсутствуют.'));
}
} else {
// Статус ответа не равен 200, обрабатываем ошибку
emit(PlanEditErrorState('Ошибка сервера: ${response.statusCode}'));
}
} catch (e) {
// Ошибка при декодировании JSON или другая ошибка выполнения
emit(PlanEditErrorState('Ошибка при получении данных о чатах: $e'));
}
}
FutureOr<void> _editPlan(EditPlan event, Emitter<PlanEditState> emit) async {
if (state is PlanEditLoadedState) {
var currentState = state as PlanEditLoadedState;
int duration = int.parse(currentState.duration!.text) * 7;
var response = await sendPlan(currentState.plan, currentState.name?.text, currentState.details?.text, duration.toString());
if (response.statusCode == 200) {
AppRouter.navigatorKey.currentContext?.goNamed(
'planScreen',
extra: currentState.plan?['id'].toString(),
);
} else {}
}
}
FutureOr<http.Response> sendPlan(
Map<String, dynamic>? plan,
String? name,
String? details,
String? duration,
) async {
int id = plan?['id'];
String url = '${baseUrl}/api/plans/update/$id';
final Map<String, dynamic> credentials = {
'id': id,
'name': name,
'details': details,
'duration': '14',
};
var response = await http.post(
Uri.parse(url),
headers: {
'Content-Type': 'application/json',
},
body: json.encode(credentials),
encoding: utf8,
);
return response;
}
}

View File

@@ -1,21 +0,0 @@
// ignore_for_file: must_be_immutable
part of 'plan_edit_bloc.dart';
/// Abstract class for all events that can be dispatched from the
///PlanEdit widget.
///
/// Events must be immutable and implement the [Equatable] interface.
@immutable
abstract class PlanEditEvent extends Equatable {}
/// Event that is dispatched when the PlanEdit widget is first created.
class PlanEditInitialEvent extends PlanEditEvent {
@override
List<Object?> get props => [];
}
class EditPlan extends PlanEditEvent {
@override
List<Object?> get props => [];
}

View File

@@ -1,71 +0,0 @@
// ignore_for_file: must_be_immutable
part of 'plan_edit_bloc.dart';
/// Represents the state of PlanEdit in the application.
///
class PlanEditState extends Equatable {
PlanEditState({
this.planModelObj,
});
PlanEditModel? planModelObj;
@override
List<Object?> get props => [planModelObj];
PlanEditState copyWith({PlanEditModel? planModelObj}) {
return PlanEditState(
planModelObj: planModelObj ?? this.planModelObj,
);
}
}
class PlanEditLoadedState extends PlanEditState {
PlanEditLoadedState({
this.planModelObj,
this.plan,
this.name,
this.details,
this.duration,
});
PlanEditModel? planModelObj;
Map<String, dynamic>? plan;
TextEditingController? name;
TextEditingController? details;
TextEditingController? duration;
@override
List<Object?> get props => [planModelObj, plan, name, details, duration];
PlanEditLoadedState copyWith({
PlanEditModel? planModelObj,
Map<String, dynamic>? plan,
TextEditingController? name,
TextEditingController? details,
TextEditingController? duration,
}) {
return PlanEditLoadedState(
planModelObj: planModelObj ?? this.planModelObj,
plan: plan ?? this.plan,
name: name ?? this.name,
details: details ?? this.details,
duration: duration ?? this.duration,
);
}
}
class PlanEditErrorState extends PlanEditState {
final String errorMessage;
PlanEditErrorState(this.errorMessage);
@override
List<Object> get props => [errorMessage];
}
class PlanEditInitialState extends PlanEditState {
PlanEditInitialState({required PlanEditModel planModelObj}) : super(planModelObj: planModelObj);
}

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 [plan_edit_screen],
/// and is typically used to hold data that is passed between different parts of the application.
class PlanEditModel extends Equatable {
final String planID;
PlanEditModel({required String planID}) : planID = planID;
PlanEditModel copyWith(String planID) {
return PlanEditModel(planID: planID);
}
@override
List<Object?> get props => [planID];
}

View File

@@ -1,120 +0,0 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/theme/app_decoration.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:rive/rive.dart';
import 'bloc/plan_edit_bloc.dart';
import 'models/plan_edit_model.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()),
child: PlanEditScreen(),
);
}
Widget build(BuildContext context) {
return BlocBuilder<PlanEditBloc, PlanEditState>(
builder: (context, state) {
if (state is PlanEditInitialState) {
// Это начальное состояние, данные ещё не загружены.
return Container(
color: Colors.white,
);
} else if (state is PlanEditLoadedState) {
// Данные загружены, можно строить экран.
return _buildPlanEdit(context, state);
} else if (state is PlanEditErrorState) {
// Произошла ошибка при загрузке данных.
return Center(child: Text(state.errorMessage));
} else {
// Другие состояния, если они есть.
return Container(); // Пустой контейнер для необработанных состояний.
}
},
);
}
_buildPlanEdit(BuildContext context, PlanEditLoadedState state) {
return SafeArea(
child: Scaffold(
resizeToAvoidBottomInset: false,
body: Container(
width: double.maxFinite,
decoration: AppDecoration.fillGray,
child: ListView(
children: [
SizedBox(height: 30),
Container(
margin: EdgeInsets.only(
left: 16,
right: 20,
bottom: 10,
),
padding: EdgeInsets.all(12),
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: baseUrl + '/storage/' + (state.plan?['image'] ?? ''),
imageBuilder: (context, imageProvider) => CircleAvatar(
backgroundImage: imageProvider,
radius: 25,
),
placeholder: (context, url) => SizedBox(
height: 32,
width: 32,
child: RiveAnimation.asset(
'assets/rive_animations/loading.riv',
),
),
errorWidget: (context, url, error) => CircleAvatar(
child: Icon(Icons.person),
backgroundColor: Colors.white,
radius: 25,
),
),
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<PlanEditBloc>().add(EditPlan()),
child: Text('Сохранить'),
),
],
),
),
],
),
),
),
);
}
}

View File

@@ -1,68 +0,0 @@
//import 'package:eatvio_app/old/auth/auth_screen/auth_screen.dart';
import 'dart:async';
import 'dart:convert';
import 'package:eatvio_app/app_router.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/token_storage.dart';
import 'package:eatvio_app/old/plans/plan_select_screen/models/plan_select_model.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart' as http;
import 'package:image_picker/image_picker.dart';
part 'plan_select_event.dart';
part 'plan_select_state.dart';
/// A bloc that manages the state of a PlanSelect according to the event that is dispatched to it.
class PlanSelectBloc extends Bloc<PlanSelectEvent, PlanSelectState> {
PlanSelectBloc(PlanSelectState initialState) : super(initialState) {
on<PlanSelectInitialEvent>(_onInitialize);
}
Future<void> _onInitialize(
PlanSelectInitialEvent event,
Emitter<PlanSelectState> emit,
) async {
try {
if (!await TokenStorage.haveTokenInStorage()) {
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final response = await http.get(
Uri.parse(baseUrl + '/api/plans/index'),
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
if (data is List<dynamic>) {
if (state is PlanSelectLoadedState) {
var currentState = state as PlanSelectLoadedState;
emit(
(currentState).copyWith(
posts: data,
myProfileModelObj: currentState.myProfileModelObj,
),
);
} else {
emit(
PlanSelectLoadedState(
posts: data,
),
);
}
} else {
// Один или несколько ключевых полей отсутствуют, обрабатываем ошибку
emit(PlanSelectErrorState('Неверный формат ответа API.'));
}
} else {
// Статус ответа не равен 200, обрабатываем ошибку
emit(PlanSelectErrorState('Ошибка сервера: ${response.statusCode}'));
}
} catch (e) {
// Ошибка при декодировании JSON или другая ошибка выполнения
emit(PlanSelectErrorState('Ошибка при получении данных ленты: $e'));
}
}
}

View File

@@ -1,25 +0,0 @@
// ignore_for_file: must_be_immutable
part of 'plan_select_bloc.dart';
/// Abstract class for all events that can be dispatched from the
///PlanSelect widget.
///
/// Events must be immutable and implement the [Equatable] interface.
@immutable
abstract class PlanSelectEvent extends Equatable {}
/// Event that is dispatched when the PlanSelect widget is first created.
class PlanSelectInitialEvent extends PlanSelectEvent {
@override
List<Object?> get props => [];
}
class UploadAvatar extends PlanSelectEvent {
UploadAvatar(this.image);
final XFile image;
@override
List<Object> get props => [image];
}

View File

@@ -1,49 +0,0 @@
// ignore_for_file: must_be_immutable
part of 'plan_select_bloc.dart';
class PlanSelectState extends Equatable {
const PlanSelectState({this.myProfileModelObj});
final PlanSelectModel? myProfileModelObj;
@override
List<Object?> get props => [myProfileModelObj];
}
class PlanSelectInitialState extends PlanSelectState {
PlanSelectInitialState({required PlanSelectModel myProfileModelObj}) : super(myProfileModelObj: myProfileModelObj);
}
/// Represents the state of PlanSelect in the application.
class PlanSelectLoadedState extends PlanSelectState {
PlanSelectLoadedState({this.myProfileModelObj, this.posts}) : super(myProfileModelObj: null);
PlanSelectModel? myProfileModelObj;
List<dynamic>? posts;
@override
List<Object?> get props => [
myProfileModelObj,
posts,
];
PlanSelectLoadedState copyWith({
PlanSelectModel? myProfileModelObj,
List<dynamic>? posts,
}) {
return PlanSelectLoadedState(
myProfileModelObj: myProfileModelObj ?? this.myProfileModelObj,
posts: posts ?? this.posts,
);
}
}
class PlanSelectErrorState extends PlanSelectState {
final String errorMessage;
const PlanSelectErrorState(this.errorMessage);
@override
List<Object> get props => [errorMessage];
}

View File

@@ -1,16 +0,0 @@
// ignore_for_file: must_be_immutable
import 'package:equatable/equatable.dart';
/// This class defines the variables used in the [k0_page],
/// and is typically used to hold data that is passed between different parts of the application.
class PlanSelectModel extends Equatable {
PlanSelectModel();
PlanSelectModel copyWith() {
return PlanSelectModel();
}
@override
List<Object?> get props => [];
}

View File

@@ -1,202 +0,0 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/utils/image_constant.dart';
import 'package:eatvio_app/theme/custom_text_style.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/app_bar/custom_app_bar.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:rive/rive.dart';
import 'bloc/plan_select_bloc.dart';
import 'models/plan_select_model.dart';
class PlanSelectScreen extends StatelessWidget {
static Widget builder(BuildContext context) {
return BlocProvider<PlanSelectBloc>(
create: (context) => PlanSelectBloc(PlanSelectState(myProfileModelObj: PlanSelectModel()))..add(PlanSelectInitialEvent()),
child: PlanSelectScreen(),
);
}
Widget build(BuildContext context) {
return BlocBuilder<PlanSelectBloc, PlanSelectState>(
builder: (context, state) {
if (state is PlanSelectInitialState) {
// Это начальное состояние, данные ещё не загружены.
return Scaffold(
body: Center(
child: SizedBox(
height: 32,
width: 32,
child: RiveAnimation.asset(
'assets/rive_animations/loading.riv',
),
),
),
);
} else if (state is PlanSelectLoadedState) {
// Данные загружены, можно строить экран.
return _buildSetingsScreen(context, state);
} else {
// Другие состояния, если они есть.
return Scaffold(
body: Center(
child: SizedBox(
height: 32,
width: 32,
child: RiveAnimation.asset(
'assets/rive_animations/loading.riv',
),
),
),
);
}
},
);
}
_buildSetingsScreen(BuildContext context, PlanSelectLoadedState 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: 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,
leadingWidth: 28,
leading: AppbarLeadingImage(
imagePath: ImageConstant.imgArrowLeft,
margin: EdgeInsets.only(left: 16, top: 15, bottom: 15),
onTap: () => GoRouter.of(context).pop(),
),
title: Padding(
padding: EdgeInsets.only(left: 20),
child: Row(
children: [
AppbarSubtitle(
text: "Планы",
margin: EdgeInsets.only(left: 80, top: 12, bottom: 9),
),
Spacer(),
Padding(
padding: const EdgeInsets.only(right: 20.0),
child: GestureDetector(
onTap: () => GoRouter.of(context).goNamed('planCreateScreen'),
child: Icon(Icons.create_outlined),
),
),
],
),
),
),
),
SizedBox(height: 20),
_buildPlanList(context, state),
],
),
),
),
),
);
}
_buildPlanList(BuildContext context, PlanSelectLoadedState state) {
return ListView.builder(
shrinkWrap: true, // Add this line
itemCount: state.posts?.length ?? 0,
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),
),
],
),
child: ListTile(
leading: CachedNetworkImage(
imageUrl: baseUrl + '/storage/' + post['image'],
imageBuilder: (context, imageProvider) => CircleAvatar(
backgroundImage: imageProvider,
radius: 25,
),
placeholder: (context, url) => SizedBox(
height: 32,
width: 32,
child: RiveAnimation.asset(
'assets/rive_animations/loading.riv',
),
),
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(),
),
child: Icon(Icons.edit),
),
),
);
},
);
}
Function onTapBack(BuildContext context) {
return () {
onTapArrowLeft(context);
};
}
onTapArrowLeft(BuildContext context) {
GoRouter.of(context).pop();
}
}

View File

@@ -1,80 +0,0 @@
import 'dart:async';
import 'dart:convert';
import 'package:eatvio_app/app_router.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/token_storage.dart';
import 'package:eatvio_app/old/plans/plan_show_screen/models/plan_show_model.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart' as http;
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.
class PlanBloc extends Bloc<PlanEvent, PlanState> {
PlanBloc(PlanState initialState) : super(initialState) {
on<PlanInitialEvent>(_onInitialize);
on<DeleteEntry>(_deleteEntry);
}
_onInitialize(
PlanInitialEvent event,
Emitter<PlanState> emit,
) async {
try {
if (!await TokenStorage.haveTokenInStorage()) {
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final String? planID = state.planModelObj?.planID;
final response = await http.get(
Uri.parse(baseUrl + '/api/plans/show/$planID'),
headers: {
'Authorization': 'Bearer ${await TokenStorage.getTokenFromStorage()}',
},
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
if (data is Map<String, dynamic>) {
if (state is PlanLoadedState) {
var currentState = state as PlanLoadedState;
emit(currentState.copyWith(planModelObj: currentState.planModelObj, plan: currentState.plan));
} else {
emit(PlanLoadedState(plan: data));
}
} else {
// Один или несколько ключевых полей отсутствуют, обрабатываем ошибку
emit(PlanErrorState('Некоторые необходимые данные отсутствуют.'));
}
} else {
// Статус ответа не равен 200, обрабатываем ошибку
emit(PlanErrorState('Ошибка сервера: ${response.statusCode}'));
}
} catch (e) {
// Ошибка при декодировании JSON или другая ошибка выполнения
emit(PlanErrorState('Ошибка при получении данных о чатах: $e'));
}
}
FutureOr<void> _deleteEntry(DeleteEntry event, Emitter<PlanState> emit) async {
if (!await TokenStorage.haveTokenInStorage()) {
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final int id = event.id;
final response = await http.post(
Uri.parse(baseUrl + '/api/plans/delete/$id'),
headers: {
'Authorization': 'Bearer ${await TokenStorage.getTokenFromStorage()}',
},
);
if (response.statusCode == 200) {
AppRouter.navigatorKey.currentContext?.goNamed('planSelectScreen');
} else {
// Статус ответа не равен 200, обрабатываем ошибку
emit(PlanErrorState('Ошибка: ${response.statusCode}'));
}
}
}

View File

@@ -1,25 +0,0 @@
// ignore_for_file: must_be_immutable
part of 'plan_show_bloc.dart';
/// Abstract class for all events that can be dispatched from the
///Plan widget.
///
/// Events must be immutable and implement the [Equatable] interface.
@immutable
abstract class PlanEvent extends Equatable {}
/// Event that is dispatched when the Plan widget is first created.
class PlanInitialEvent extends PlanEvent {
@override
List<Object?> get props => [];
}
class DeleteEntry extends PlanEvent {
DeleteEntry(this.id);
final int id;
@override
List<Object?> get props => [id];
}

View File

@@ -1,53 +0,0 @@
// ignore_for_file: must_be_immutable
part of 'plan_show_bloc.dart';
/// Represents the state of Plan in the application.
///
class PlanState extends Equatable {
PlanState({
this.planModelObj,
});
PlanModel? planModelObj;
@override
List<Object?> get props => [planModelObj];
PlanState copyWith({PlanModel? planModelObj}) {
return PlanState(
planModelObj: planModelObj ?? this.planModelObj,
);
}
}
class PlanLoadedState extends PlanState {
PlanLoadedState({this.planModelObj, this.plan});
PlanModel? planModelObj;
Map<String, dynamic>? plan;
@override
List<Object?> get props => [planModelObj, plan];
PlanLoadedState copyWith({PlanModel? planModelObj, Map<String, dynamic>? plan}) {
return PlanLoadedState(
planModelObj: planModelObj ?? this.planModelObj,
plan: plan ?? this.plan,
);
}
}
class PlanErrorState extends PlanState {
final String errorMessage;
PlanErrorState(this.errorMessage);
@override
List<Object> get props => [errorMessage];
}
class PlanInitialState extends PlanState {
PlanInitialState({required PlanModel planModelObj}) : super(planModelObj: planModelObj);
}

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 [plan_screen],
/// and is typically used to hold data that is passed between different parts of the application.
class PlanModel extends Equatable {
final String planID;
PlanModel({required String planID}) : planID = planID;
PlanModel copyWith(String planID) {
return PlanModel(planID: planID);
}
@override
List<Object?> get props => [planID];
}

View File

@@ -1,146 +0,0 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/theme/app_decoration.dart';
import 'package:eatvio_app/theme/custom_text_style.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:rive/rive.dart';
import 'bloc/plan_show_bloc.dart';
import 'models/plan_show_model.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(),
);
}
Widget build(BuildContext context) {
return BlocBuilder<PlanBloc, PlanState>(
builder: (context, state) {
if (state is PlanInitialState) {
// Это начальное состояние, данные ещё не загружены.
return Container(
color: Colors.white,
);
} else if (state is PlanLoadedState) {
// Данные загружены, можно строить экран.
return _buildPlan(context, state);
} else if (state is PlanErrorState) {
// Произошла ошибка при загрузке данных.
return Center(child: Text(state.errorMessage));
} else {
// Другие состояния, если они есть.
return Container(); // Пустой контейнер для необработанных состояний.
}
},
);
}
_buildPlan(BuildContext context, PlanLoadedState state) {
return SafeArea(
child: Scaffold(
resizeToAvoidBottomInset: false,
body: Container(
width: double.maxFinite,
decoration: AppDecoration.fillGray,
child: ListView(
children: [
SizedBox(height: 30),
Container(
margin: EdgeInsets.only(
left: 16,
right: 20,
bottom: 10,
),
padding: EdgeInsets.all(12),
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: baseUrl + '/storage/' + (state.plan?['image'] ?? ''),
imageBuilder: (context, imageProvider) => CircleAvatar(
backgroundImage: imageProvider,
radius: 25,
),
placeholder: (context, url) => SizedBox(
height: 32,
width: 32,
child: RiveAnimation.asset(
'assets/rive_animations/loading.riv',
),
),
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('Редактировать'),
),
ElevatedButton(
onPressed: () => context.read<PlanBloc>().add(DeleteEntry(state.plan?['id'])),
child: Text('Удалить'),
),
],
),
],
),
),
],
),
),
),
);
}
///Handling page based on route
Widget getCurrentPage(
BuildContext context,
String currentRoute,
) {
switch (currentRoute) {
case '/plan':
return PlanScreen.builder(context);
default:
return PlanScreen();
}
}
/// Navigates to the previous screen.
onTapArrowleftone(BuildContext context) {
GoRouter.of(context).pop();
}
}

View File

@@ -1,294 +0,0 @@
import 'dart:async';
import 'dart:convert';
import 'package:eatvio_app/app_router.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/token_storage.dart';
import 'package:eatvio_app/old/profile/my_profile_screen/models/my_profile_model.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart' as http;
import 'package:image_picker/image_picker.dart';
part 'my_profile_event.dart';
part 'my_profile_state.dart';
/// A bloc that manages the state of a MyProfile according to the event that is dispatched to it.
class MyProfileBloc extends Bloc<MyProfileEvent, MyProfileState> {
MyProfileBloc(MyProfileState initialState) : super(initialState) {
on<MyProfileInitialEvent>(_onInitialize);
on<UploadAvatar>(_uploadAvatar);
}
Future<void> _onInitialize(
MyProfileInitialEvent event,
Emitter<MyProfileState> emit,
) async {
try {
if (!await TokenStorage.haveTokenInStorage()) {
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final response = await http.get(
Uri.parse(baseUrl + '/api/profile/index'),
headers: {
'Authorization': 'Bearer ${await TokenStorage.getTokenFromStorage()}',
},
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
if (data is Map<String, dynamic> &&
data.containsKey('user') &&
data.containsKey('userDetails') &&
data.containsKey('chartPoints') &&
data.containsKey('circumferences')) {
if (state is MyProfileLoadedState) {
var currentState = state as MyProfileLoadedState;
var plans = []
..addAll(data["finishedPlans"])
..addAll(data["rejectedPlans"])
..addAll(data["startedPlan"])
..addAll(data["paidPlan"])
..addAll(data["requestedPlan"])
..addAll(data["initedPlan"]);
emit(
(currentState).copyWith(
chartPoints: currentState.chartPoints,
chartDates: currentState.chartDates,
circumferences: currentState.circumferences,
user: data['user'],
userDetails: data['userDetails'],
plans: plans,
token: await TokenStorage.getTokenFromStorage(),
chestCircumferenceController: TextEditingController(),
waistCircumferenceController: TextEditingController(),
hipCircumferenceController: TextEditingController(),
weightController: TextEditingController(),
chestCircumferenceFormController: currentState.chestCircumferenceFormController,
waistCircumferenceFormController: currentState.waistCircumferenceFormController,
hipCircumferenceFormController: currentState.hipCircumferenceFormController,
firstNameFormController: currentState.firstNameFormController,
lastNameFormController: currentState.lastNameFormController,
countryFormController: currentState.countryFormController,
cityFormController: currentState.cityFormController,
heightFormController: currentState.heightFormController,
upPressureFormController: currentState.upPressureFormController,
downPressureFormController: currentState.downPressureFormController,
allergiesFormController: currentState.allergiesFormController,
pillsFormController: currentState.pillsFormController,
favoriteFormController: currentState.favoriteFormController,
unfavoriteFormController: currentState.unfavoriteFormController,
additionalFormController: currentState.additionalFormController,
sportsFormController: currentState.sportsFormController,
dateFormController: currentState.dateFormController,
hypoFormController: currentState.hypoFormController,
hyperFormController: currentState.hyperFormController,
selectedCity: currentState.selectedCity,
selectedCountry: currentState.selectedCountry,
selectedActivity: currentState.selectedActivity,
myProfileModelObj: currentState.myProfileModelObj,
),
);
} else {
var plans = []
..addAll(data["finishedPlans"])
..addAll(data["rejectedPlans"])
..addAll(data["startedPlan"])
..addAll(data["paidPlan"])
..addAll(data["requestedPlan"])
..addAll(data["initedPlan"]);
/*
*/
if (data["chartPoints"].length == 1) {
data["chartPoints"] = [];
}
if (data["circumferences"].length == 1) {
data["circumferences"] = [];
}
var circumferences = data["circumferences"].isEmpty
? data["circumferences"].asMap().map((index, value) {
return MapEntry(index.toString(), value);
})
: data["circumferences"];
var chartPoints = data["chartPoints"].isEmpty
? data["chartPoints"].asMap().map((index, value) {
return MapEntry(index.toString(), value);
})
: data["chartPoints"];
List<String> dates = [
"0000-00-00 00:00:00",
"0000-00-00 00:00:00",
"0000-00-00 00:00:00",
"0000-00-00 00:00:00",
];
for (int i = 0; i < data["circumferences"].length; i++) {
dates[i] = data["circumferences"]['$i']["created_at"];
}
if (chartPoints.length < 4) {
for (int i = chartPoints.length; i < 4; i++) {
chartPoints["$i"] = {
"id": 0,
"user_id": 0,
"weight": 0,
"created_at": "0000-00-00 00:00:00",
"updated_at": "0000-00-00 00:00:00",
};
}
}
if (circumferences.length < 4) {
for (int i = circumferences.length; i < 4; i++) {
circumferences["$i"] = {
"id": 0,
"user_id": 0,
"chest_circumference": 0,
"waist_circumference": 0,
"hip_circumference": 0,
"created_at": "0000-00-00 00:00:00",
"updated_at": "0000-00-00 00:00:00",
};
}
}
emit(
MyProfileLoadedState(
chartPoints: chartPoints,
chartDates: dates,
circumferences: circumferences,
user: data['user'],
userDetails: data['userDetails'],
plans: plans,
token: await TokenStorage.getTokenFromStorage(),
chestCircumferenceController: TextEditingController(),
waistCircumferenceController: TextEditingController(),
hipCircumferenceController: TextEditingController(),
hypoFormController: data['userDetails']['has_hypo'].toString(),
hyperFormController: data['userDetails']['has_hyper'].toString(),
chestCircumferenceFormController: TextEditingController(
text: data['userDetails']['chest_circumference']?.toString(),
),
waistCircumferenceFormController: TextEditingController(
text: data['userDetails']['waist_circumference']?.toString(),
),
hipCircumferenceFormController: TextEditingController(
text: data['userDetails']['hip_circumference']?.toString(),
),
weightController: TextEditingController(
text: data['userDetails']['weight']?.toString(),
),
weightFormController: TextEditingController(
text: data['userDetails']['weight']?.toString(),
),
firstNameFormController: TextEditingController(
text: data['userDetails']['first_name'],
),
lastNameFormController: TextEditingController(text: data['userDetails']['last_name']),
countryFormController: TextEditingController(text: data['userDetails']['country']),
cityFormController: TextEditingController(text: data['userDetails']['city']),
heightFormController: TextEditingController(
text: data['userDetails']['height']?.toString(),
),
upPressureFormController: TextEditingController(
text: data['userDetails']['blood_pressure_up']?.toString(),
),
downPressureFormController: TextEditingController(
text: data['userDetails']['blood_pressure_down']?.toString(),
),
allergiesFormController: TextEditingController(
text: data['userDetails']['about_alergy'],
),
pillsFormController: TextEditingController(
text: data['userDetails']['about_taking_pils'],
),
sportsFormController: TextEditingController(
text: data['userDetails']['about_sport_hobbies'],
),
favoriteFormController: TextEditingController(
text: data['userDetails']['favorite_foods'],
),
unfavoriteFormController: TextEditingController(
text: data['userDetails']['unfavorite_foods'],
),
additionalFormController: TextEditingController(
text: data['userDetails']['additional_info'],
),
dateFormController: TextEditingController(text: data['userDetails']['birthdate']),
selectedCity: data['userDetails']['city'],
selectedCountry: data['userDetails']['country'],
selectedActivity: determineActivityLevel(
data['userDetails']['activity_coefficient'],
),
),
);
}
} else {
// Один или несколько ключевых полей отсутствуют, обрабатываем ошибку
emit(
MyProfileErrorState('Некоторые необходимые данные отсутствуют.'),
);
}
} else {
// Статус ответа не равен 200, обрабатываем ошибку
emit(MyProfileErrorState('Ошибка сервера: ${response.statusCode}'));
}
} catch (e) {
// Ошибка при декодировании JSON или другая ошибка выполнения
emit(MyProfileErrorState('Ошибка при получении данных профиля: $e'));
}
}
String determineActivityLevel(double activityFactor) {
if (activityFactor <= 1.2) {
return "сидячий";
} else if (activityFactor <= 1.375) {
return "низкий (1-3 тренировки в неделю по 20 мин)";
} else if (activityFactor <= 1.55) {
return "умеренный (3-4 тренировки в неделю по 30-60 мин)";
} else if (activityFactor <= 1.7) {
return "высокий (5-7 тренировок по 30-60 мин)";
} else {
return "экстремальный (несколько интенсивных тренировок в день)";
}
} // колхоз, но свитчкейсы в дарте странно ведут себя при сравнении double
Future<void> _uploadAvatar(
UploadAvatar event,
Emitter<MyProfileState> emit,
) async {
if (state is MyProfileLoadedState) {
var currentState = state as MyProfileLoadedState;
await uploadImageToServer(event.image.path, currentState.token);
this.add(MyProfileInitialEvent());
}
}
Future<String> uploadImageToServer(String imagePath, String? token) async {
String url = '${baseUrl}/api/profile/photo/upload';
var request = http.MultipartRequest('POST', Uri.parse(url));
request.headers['Authorization'] = 'Bearer $token';
request.files.add(await http.MultipartFile.fromPath('photo', imagePath));
var client = http.Client();
var streamedResponse = await client.send(request);
if (streamedResponse.statusCode == 200) {
var response = await http.Response.fromStream(streamedResponse);
var imageUrl = response.body;
return imageUrl;
} else {
throw Exception(
'Failed to upload image. Status code: ${streamedResponse.statusCode} ${streamedResponse}}',
);
}
}
}

View File

@@ -1,25 +0,0 @@
// ignore_for_file: must_be_immutable
part of 'my_profile_bloc.dart';
/// Abstract class for all events that can be dispatched from the
///MyProfile widget.
///
/// Events must be immutable and implement the [Equatable] interface.
@immutable
abstract class MyProfileEvent extends Equatable {}
/// Event that is dispatched when the MyProfile widget is first created.
class MyProfileInitialEvent extends MyProfileEvent {
@override
List<Object?> get props => [];
}
class UploadAvatar extends MyProfileEvent {
UploadAvatar(this.image);
final XFile image;
@override
List<Object> get props => [image];
}

View File

@@ -1,216 +0,0 @@
// ignore_for_file: must_be_immutable
part of 'my_profile_bloc.dart';
class MyProfileState extends Equatable {
const MyProfileState({this.myProfileModelObj});
final MyProfileModel? myProfileModelObj;
@override
List<Object?> get props => [myProfileModelObj];
}
class MyProfileInitialState extends MyProfileState {
MyProfileInitialState({required MyProfileModel myProfileModelObj}) : super(myProfileModelObj: myProfileModelObj);
}
/// Represents the state of MyProfile in the application.
class MyProfileLoadedState extends MyProfileState {
MyProfileLoadedState({
this.chestCircumferenceController,
this.waistCircumferenceController,
this.hipCircumferenceController,
this.myProfileModelObj,
this.weightController,
this.user,
this.userDetails,
this.chartPoints,
this.chartDates,
this.plans,
this.token,
this.selectedCountry,
this.selectedCity,
this.selectedActivity,
this.chestCircumferenceFormController,
this.waistCircumferenceFormController,
this.hipCircumferenceFormController,
this.firstNameFormController,
this.lastNameFormController,
this.countryFormController,
this.dateFormController,
this.cityFormController,
this.heightFormController,
this.weightFormController,
this.upPressureFormController,
this.downPressureFormController,
this.allergiesFormController,
this.pillsFormController,
this.sportsFormController,
this.favoriteFormController,
this.unfavoriteFormController,
this.additionalFormController,
this.hypoFormController,
this.hyperFormController,
this.circumferences,
}) : super(myProfileModelObj: null);
MyProfileModel? myProfileModelObj;
Map<String, dynamic>? user;
Map<String, dynamic>? userDetails;
Map<dynamic, dynamic>? circumferences;
Map<dynamic, dynamic>? chartPoints;
List<String>? chartDates;
List<dynamic>? plans;
TextEditingController? weightController;
TextEditingController? chestCircumferenceController;
TextEditingController? waistCircumferenceController;
TextEditingController? hipCircumferenceController;
TextEditingController? weightFormController;
TextEditingController? chestCircumferenceFormController;
TextEditingController? waistCircumferenceFormController;
TextEditingController? hipCircumferenceFormController;
TextEditingController? firstNameFormController;
TextEditingController? lastNameFormController;
TextEditingController? countryFormController;
TextEditingController? dateFormController;
TextEditingController? cityFormController;
TextEditingController? heightFormController;
TextEditingController? upPressureFormController;
TextEditingController? downPressureFormController;
TextEditingController? allergiesFormController;
TextEditingController? pillsFormController;
TextEditingController? sportsFormController;
TextEditingController? favoriteFormController;
TextEditingController? unfavoriteFormController;
TextEditingController? additionalFormController;
String? token;
String? selectedCountry;
String? selectedCity;
String? selectedActivity;
String? hypoFormController;
String? hyperFormController;
@override
List<Object?> get props => [
myProfileModelObj,
user,
chartPoints,
chartDates,
circumferences,
plans,
weightController,
chestCircumferenceController,
waistCircumferenceController,
hipCircumferenceController,
token,
chestCircumferenceFormController,
waistCircumferenceFormController,
hipCircumferenceFormController,
firstNameFormController,
lastNameFormController,
countryFormController,
dateFormController,
cityFormController,
heightFormController,
weightFormController,
upPressureFormController,
downPressureFormController,
allergiesFormController,
pillsFormController,
sportsFormController,
favoriteFormController,
unfavoriteFormController,
additionalFormController,
selectedCountry,
selectedCity,
selectedActivity,
hypoFormController,
hyperFormController,
];
MyProfileLoadedState copyWith({
MyProfileModel? myProfileModelObj,
Map<String, dynamic>? user,
Map<String, dynamic>? userDetails,
List<dynamic>? plans,
Map<dynamic, dynamic>? chartPoints,
List<String>? chartDates,
Map<dynamic, dynamic>? circumferences,
TextEditingController? weightController,
TextEditingController? chestCircumferenceController,
TextEditingController? waistCircumferenceController,
TextEditingController? hipCircumferenceController,
TextEditingController? chestCircumferenceFormController,
TextEditingController? waistCircumferenceFormController,
TextEditingController? hipCircumferenceFormController,
TextEditingController? firstNameFormController,
TextEditingController? lastNameFormController,
TextEditingController? countryFormController,
TextEditingController? dateFormController,
TextEditingController? cityFormController,
TextEditingController? heightFormController,
TextEditingController? weightFormeFormController,
TextEditingController? allergiesFormController,
TextEditingController? pillsFormController,
TextEditingController? sportsFormController,
TextEditingController? favoriteFormController,
TextEditingController? unfavoriteFormController,
TextEditingController? additionalFormController,
TextEditingController? downPressureFormController,
TextEditingController? upPressureFormController,
String? token,
String? selectedCountry,
String? selectedCity,
String? selectedActivity,
String? hypoFormController,
String? hyperFormController,
}) {
return MyProfileLoadedState(
myProfileModelObj: myProfileModelObj ?? this.myProfileModelObj,
user: user ?? this.user,
plans: plans ?? this.plans,
userDetails: userDetails ?? this.userDetails,
circumferences: circumferences ?? this.circumferences,
chartPoints: chartPoints ?? this.chartPoints,
chartDates: chartDates ?? this.chartDates,
weightController: weightController ?? this.weightController,
chestCircumferenceController: chestCircumferenceController ?? this.chestCircumferenceController,
waistCircumferenceController: waistCircumferenceController ?? this.waistCircumferenceController,
hipCircumferenceController: hipCircumferenceController ?? this.hipCircumferenceController,
chestCircumferenceFormController: chestCircumferenceFormController ?? this.chestCircumferenceFormController,
waistCircumferenceFormController: waistCircumferenceFormController ?? this.waistCircumferenceFormController,
hipCircumferenceFormController: hipCircumferenceFormController ?? this.hipCircumferenceFormController,
firstNameFormController: firstNameFormController ?? this.firstNameFormController,
lastNameFormController: lastNameFormController ?? this.lastNameFormController,
countryFormController: countryFormController ?? this.countryFormController,
dateFormController: dateFormController ?? this.dateFormController,
cityFormController: cityFormController ?? this.cityFormController,
heightFormController: heightFormController ?? this.heightFormController,
weightFormController: weightFormController ?? this.weightFormController,
upPressureFormController: upPressureFormController ?? this.upPressureFormController,
downPressureFormController: downPressureFormController ?? this.downPressureFormController,
allergiesFormController: allergiesFormController ?? this.allergiesFormController,
pillsFormController: pillsFormController ?? this.pillsFormController,
sportsFormController: sportsFormController ?? this.sportsFormController,
favoriteFormController: favoriteFormController ?? this.favoriteFormController,
unfavoriteFormController: unfavoriteFormController ?? this.unfavoriteFormController,
additionalFormController: additionalFormController ?? this.additionalFormController,
token: token ?? this.token,
selectedCountry: selectedCountry ?? this.selectedCountry,
selectedCity: selectedCity ?? this.selectedCity,
selectedActivity: selectedActivity ?? this.selectedActivity,
hypoFormController: hypoFormController ?? this.hypoFormController,
hyperFormController: hyperFormController ?? this.hyperFormController,
);
}
}
class MyProfileErrorState extends MyProfileState {
final String errorMessage;
const MyProfileErrorState(this.errorMessage);
@override
List<Object> get props => [errorMessage];
}

View File

@@ -1,16 +0,0 @@
// ignore_for_file: must_be_immutable
import 'package:equatable/equatable.dart';
/// This class defines the variables used in the [k0_page],
/// and is typically used to hold data that is passed between different parts of the application.
class MyProfileModel extends Equatable {
MyProfileModel();
MyProfileModel copyWith() {
return MyProfileModel();
}
@override
List<Object?> get props => [];
}

View File

@@ -1,202 +0,0 @@
import 'package:eatvio_app/core/utils/image_constant.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/app_bar/custom_app_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_language_drop_down.dart';
import 'package:eatvio_app/widgets/custom_notification_screen.dart';
import 'package:eatvio_app/widgets/custom_section_title.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:rive/rive.dart';
import 'bloc/my_profile_bloc.dart';
import 'models/my_profile_model.dart';
class MySettingsScreen extends StatelessWidget {
static Widget builder(BuildContext context) {
return BlocProvider<MyProfileBloc>(
create: (context) => MyProfileBloc(MyProfileState(myProfileModelObj: MyProfileModel()))..add(MyProfileInitialEvent()),
child: MySettingsScreen(),
);
}
Widget build(BuildContext context) {
return BlocBuilder<MyProfileBloc, MyProfileState>(
builder: (context, state) {
if (state is MyProfileInitialState) {
// Это начальное состояние, данные ещё не загружены.
return Scaffold(
body: Center(
child: SizedBox(
height: 32,
width: 32,
child: RiveAnimation.asset(
'assets/rive_animations/loading.riv',
),
),
),
);
} else if (state is MyProfileLoadedState) {
// Данные загружены, можно строить экран.
return _buildSetingsScreen(context, state);
} else {
// Другие состояния, если они есть.
return Scaffold(
body: Center(
child: SizedBox(
height: 32,
width: 32,
child: RiveAnimation.asset(
'assets/rive_animations/loading.riv',
),
),
),
);
}
},
);
}
_buildSetingsScreen(BuildContext context, MyProfileLoadedState 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: 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,
leadingWidth: 28,
leading: AppbarLeadingImage(
imagePath: ImageConstant.imgArrowLeft,
margin: EdgeInsets.only(left: 16, top: 15, bottom: 15),
onTap: () => GoRouter.of(context).pop(),
),
title: Padding(
padding: EdgeInsets.only(left: 20),
child: Row(children: [AppbarSubtitle(text: state.user?['name'], margin: EdgeInsets.only(left: 80, top: 12, bottom: 9))]),
),
),
),
SizedBox(height: 20),
Container(
padding: EdgeInsets.symmetric(
horizontal: 24,
vertical: 12,
),
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(),
],
),
),
SizedBox(height: 20),
Container(
padding: EdgeInsets.symmetric(
horizontal: 24,
vertical: 12,
),
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,
vertical: 12,
),
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-н.в.']),
],
),
),
],
),
),
),
),
);
}
Function onTapBack(BuildContext context) {
return () {
onTapArrowLeft(context);
};
}
onTapArrowLeft(BuildContext context) {
GoRouter.of(context).pop();
}
}

View File

@@ -1,225 +0,0 @@
import 'dart:async';
import 'dart:convert';
import 'package:eatvio_app/app_router.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/token_storage.dart';
import 'package:eatvio_app/old/recipe/recipe_edit_screen/models/recipe_edit_model.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart' as http;
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.
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(
RecipeEditInitialEvent event,
Emitter<RecipeEditState> emit,
) async {
try {
if (!await TokenStorage.haveTokenInStorage()) {
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final String? recipeID = state.recipeModelObj?.recipeID;
final response = await http.get(
Uri.parse(baseUrl + '/api/recipes/show/$recipeID'),
headers: {
'Authorization': 'Bearer ${await TokenStorage.getTokenFromStorage()}',
},
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
if (data is Map<String, dynamic>) {
if (state is RecipeEditLoadedState) {
var currentState = state as RecipeEditLoadedState;
emit(
currentState.copyWith(
recipeModelObj: currentState.recipeModelObj,
title: currentState.title,
token: currentState.token,
recipe: currentState.recipe,
description: currentState.description,
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(
token: await TokenStorage.getTokenFromStorage(),
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('Некоторые необходимые данные отсутствуют.'));
}
} else {
// Статус ответа не равен 200, обрабатываем ошибку
emit(RecipeEditErrorState('Ошибка сервера: ${response.statusCode}'));
}
} catch (e) {
// Ошибка при декодировании JSON или другая ошибка выполнения
emit(RecipeEditErrorState('Ошибка при получении данных о чатах: $e'));
}
}
FutureOr<void> _editRecipe(EditRecipe event, Emitter<RecipeEditState> emit) async {
if (state is RecipeEditLoadedState) {
var currentState = state as RecipeEditLoadedState;
var response = await sendRecipe(
currentState.recipe,
currentState.title?.text,
currentState.video_url?.text,
currentState.description?.text,
);
if (response.statusCode == 200) {
AppRouter.navigatorKey.currentContext?.goNamed(
'recipeScreen',
extra: currentState.recipe?['id'].toString(),
);
} else {}
}
}
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,
String? video_url,
String? description,
) async {
int id = recipe?['id'];
String url = '${baseUrl}/api/recipe/update/$id';
final Map<String, dynamic> credentials = {
'id': id,
'title': title,
'description': description,
'video_url': video_url,
'steps':
'[{"image": "steps/bT91V52zY2hIkjZezUxO4jW4njfhDfCe2PcMxciZ.jpg", "description": "Excepturi fuga sed quaerat impedit facere recusandae. Nihil exercitationem architecto accusantium ut. Qui consequuntur rerum quam asperiores repellendus molestias."}, {"image": "steps/7vaLlotrw65NXbqaD0dK4OTLVyxMgmB9wL3xBzg3.jpg", "description": "Sit hic sint ullam qui. Odit nihil possimus quo id itaque nisi non."}, {"image": "steps/et7cKUsnQkegcRAE72TS0mfAf2jLJXW93QDAGfjz.jpg", "description": "Ab dolores qui voluptatem sed quasi vero. Culpa minus optio in voluptatibus aut corrupti. Dolor expedita minus aut nesciunt quos quibusdam rerum."}, {"image": "steps/260401J6r71r9kuUt694VcsuMMQKTlQMrlIwfaKo.jpg", "description": "Eos dolores corrupti cupiditate aut. Nam nesciunt eveniet eos nam vitae ut. Quibusdam ab commodi ut natus. Nisi earum porro quidem et eligendi iste doloribus."}, {"image": "steps/Ol10BkOFeZ9EG4BAShgB1wnVwtCimoUMcSNDh2EB.jpg", "description": "Consectetur optio porro nobis voluptas quibusdam. Consequatur dolor dolorem laboriosam magnam iste atque."}, {"image": "steps/8PanRq2aWWdXG1xCYkRyJ0M3Z39y6jMPfnHCqlVi.jpg", "description": "A optio porro eligendi ullam. Sed numquam ullam quasi aperiam inventore corrupti enim. Alias id et consequatur perferendis. Ullam consequatur incidunt nesciunt ut et aut."}, {"image": "steps/19U2sYSGDkprS3L8SIFT3xz59OwQJ5xs0A8etZjr.jpg", "description": "In doloribus vel voluptas dolorum. Temporibus iusto est repellat quo sed. Perspiciatis enim sit beatae in dolor ut. Quis ad qui eligendi nisi. Est suscipit dolore ratione dolores suscipit molestiae."}, {"image": "steps/CRxJtjgQw84kG3teJVtl9GqwWUQ1WKpvOeBbwf9a.jpg", "description": "Aut quia maiores et aut. Quidem ab voluptates blanditiis. Est quam quia repellat exercitationem rerum et iste mollitia. Et sed dolores tempore nam et similique qui."}, {"image": "steps/NoG9rHtvHNLrYI2LujURT3DMQCmEe68UbpiXco2y.jpg", "description": "Nam et dolorum delectus. Labore fuga asperiores nam cumque sit. Eum hic officiis ea asperiores qui. Soluta totam enim quasi explicabo sed aut."}]',
'ingredients':
'[{"name": "Cheeseburger", "unit": "pcs", "portion": 336}, {"name": "Little Cheeseburger", "unit": "gr", "portion": 623}, {"name": "Bacon Burger", "unit": "ml", "portion": 713}, {"name": "Little Bacon Burger", "unit": "pcs", "portion": 160}, {"name": "Hamburger", "unit": "tablespoon", "portion": 390}, {"name": "Bacon Cheese Dog", "unit": "teespons", "portion": 295}, {"name": "Hamburger", "unit": "pcs", "portion": 813}, {"name": "Cheeseburger", "unit": "gr", "portion": 951}]',
};
var response = await http.post(
Uri.parse(url),
headers: {
'Content-Type': 'application/json',
},
body: json.encode(credentials),
encoding: utf8,
);
return response;
}
_fetchTags(int id) async {
if (!await TokenStorage.haveTokenInStorage()) {
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final response = await http.get(
Uri.parse(baseUrl + '/api/recipes/getTags/$id'),
headers: {
'Authorization': 'Bearer ${await TokenStorage.getTokenFromStorage()}',
},
);
final data = jsonDecode(response.body);
return data;
}
FutureOr<void> _reloadScreen(RecipeEditEvent? event, Emitter<RecipeEditState> emit) async {
if (!await TokenStorage.haveTokenInStorage()) {
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,
),
);
}
}
}

View File

@@ -1,44 +0,0 @@
// ignore_for_file: must_be_immutable
part of 'recipe_edit_bloc.dart';
/// Abstract class for all events that can be dispatched from the
///RecipeEdit widget.
///
/// Events must be immutable and implement the [Equatable] interface.
@immutable
abstract class RecipeEditEvent extends Equatable {}
/// Event that is dispatched when the RecipeEdit widget is first created.
class RecipeEditInitialEvent extends RecipeEditEvent {
@override
List<Object?> get props => [];
}
class EditRecipe extends RecipeEditEvent {
@override
List<Object?> get props => [];
}
class AddStep extends RecipeEditEvent {
AddStep();
@override
List<Object?> get props => [];
}
class RemoveStep extends RecipeEditEvent {
RemoveStep(this.id);
final int id;
@override
List<Object?> get props => [id];
}
class ReloadScreen extends RecipeEditEvent {
ReloadScreen();
@override
List<Object?> get props => [];
}

View File

@@ -1,91 +0,0 @@
// ignore_for_file: must_be_immutable
part of 'recipe_edit_bloc.dart';
/// Represents the state of RecipeEdit in the application.
///
class RecipeEditState extends Equatable {
RecipeEditState({
this.recipeModelObj,
});
RecipeEditModel? recipeModelObj;
@override
List<Object?> get props => [recipeModelObj];
RecipeEditState copyWith({RecipeEditModel? recipeModelObj}) {
return RecipeEditState(
recipeModelObj: recipeModelObj ?? this.recipeModelObj,
);
}
}
class RecipeEditLoadedState extends RecipeEditState {
RecipeEditLoadedState({
this.recipeModelObj,
this.token,
this.recipe,
this.title,
this.description,
this.video_url,
this.tagsController,
this.stepDescriptions,
this.tags,
this.steps,
});
RecipeEditModel? recipeModelObj;
Map<String, dynamic>? recipe;
String? token;
TextEditingController? title;
TextEditingController? description;
TextEditingController? video_url;
TextEditingController? tagsController;
List<dynamic>? steps;
List<dynamic>? tags;
List<TextEditingController>? stepDescriptions;
@override
List<Object?> get props => [recipeModelObj, recipe, title, description, video_url];
RecipeEditLoadedState copyWith({
RecipeEditModel? recipeModelObj,
Map<String, dynamic>? recipe,
String? token,
TextEditingController? title,
TextEditingController? description,
TextEditingController? video_url,
TextEditingController? tagsController,
List<dynamic>? steps,
List<dynamic>? tags,
List<TextEditingController>? stepDescriptions,
}) {
return RecipeEditLoadedState(
recipeModelObj: recipeModelObj ?? this.recipeModelObj,
token: token ?? this.token,
recipe: recipe ?? this.recipe,
title: title ?? this.title,
description: description ?? this.description,
video_url: video_url ?? this.video_url,
tagsController: tagsController ?? this.tagsController,
steps: steps ?? this.steps,
tags: tags ?? this.tags,
stepDescriptions: stepDescriptions ?? this.stepDescriptions,
);
}
}
class RecipeEditErrorState extends RecipeEditState {
final String errorMessage;
RecipeEditErrorState(this.errorMessage);
@override
List<Object> get props => [errorMessage];
}
class RecipeEditInitialState extends RecipeEditState {
RecipeEditInitialState({required RecipeEditModel recipeModelObj}) : super(recipeModelObj: recipeModelObj);
}

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 [recipe_edit_screen],
/// and is typically used to hold data that is passed between different parts of the application.
class RecipeEditModel extends Equatable {
final String recipeID;
RecipeEditModel({required String recipeID}) : recipeID = recipeID;
RecipeEditModel copyWith(String recipeID) {
return RecipeEditModel(recipeID: recipeID);
}
@override
List<Object?> get props => [recipeID];
}

View File

@@ -1,460 +0,0 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/utils/image_constant.dart';
import 'package:eatvio_app/theme/app_decoration.dart';
import 'package:eatvio_app/theme/custom_button_style.dart';
import 'package:eatvio_app/theme/custom_text_style.dart';
import 'package:eatvio_app/theme/theme_helper.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/app_bar/custom_app_bar.dart';
import 'package:eatvio_app/widgets/custom_outlined_button.dart';
import 'package:eatvio_app/widgets/custom_text_form_field.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:rive/rive.dart';
import 'bloc/recipe_edit_bloc.dart';
import 'models/recipe_edit_model.dart';
class RecipeEditScreen extends StatelessWidget {
const RecipeEditScreen({Key? key}) : super(key: key);
static Widget builder(BuildContext context) {
final recipeID = ModalRoute.of(context)!.settings.arguments;
return BlocProvider<RecipeEditBloc>(
create: (context) => RecipeEditBloc(RecipeEditState(recipeModelObj: RecipeEditModel(recipeID: recipeID.toString())))..add(RecipeEditInitialEvent()),
child: RecipeEditScreen(),
);
}
Widget build(BuildContext context) {
return BlocBuilder<RecipeEditBloc, RecipeEditState>(
builder: (context, state) {
if (state is RecipeEditInitialState) {
// Это начальное состояние, данные ещё не загружены.
return Container(
color: Colors.white,
);
} else if (state is RecipeEditLoadedState) {
// Данные загружены, можно строить экран.
return _buildRecipeEdit(context, state);
} else if (state is RecipeEditErrorState) {
// Произошла ошибка при загрузке данных.
return Center(child: Text(state.errorMessage));
} else {
// Другие состояния, если они есть.
return Container(); // Пустой контейнер для необработанных состояний.
}
},
);
}
_buildRecipeEdit(BuildContext context, RecipeEditLoadedState state) {
List<Widget> steps = [];
if (state.steps != null) {
for (int i = 0; i < state.steps!.length; i++) {
steps.add(_buildStep(context, state, i));
}
}
return RefreshIndicator(
onRefresh: refreshPage(context),
child: Scaffold(
appBar: AppBar(
toolbarHeight: 0,
automaticallyImplyLeading: false,
),
resizeToAvoidBottomInset: false,
body: SafeArea(
child: Container(
width: double.maxFinite,
decoration: BoxDecoration(color: Colors.grey[200]),
child: ListView(
children: [
_buldAppBar(context),
SizedBox(height: 20),
_buildRecipeInfo(state, context),
SizedBox(height: 20),
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: steps,
),
SizedBox(height: 20),
_buildAdditional(context, state),
],
),
),
),
),
);
}
_buldAppBar(BuildContext context) {
return Container(
margin: EdgeInsets.all(16),
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,
leadingWidth: 28,
leading: AppbarLeadingImage(
imagePath: ImageConstant.imgArrowLeft,
margin: EdgeInsets.only(left: 16, top: 15, bottom: 15),
onTap: onTapBack(context),
),
title: Align(
alignment: Alignment.center,
child: AppbarSubtitle(
text: "Редактирование рецепта",
),
),
),
);
}
_buildRecipeInfo(RecipeEditLoadedState state, BuildContext context) {
return Container(
margin: EdgeInsets.only(
left: 16,
right: 16,
bottom: 10,
),
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(height: 16.0),
Align(alignment: Alignment.centerLeft, child: Text("Изображение")),
if (state.recipe?['image'] != null)
CachedNetworkImage(
imageUrl: baseUrl + '/storage/' + (state.recipe?['image']),
width: double.infinity,
// Установите ширину равной ширине родительского элемента
height: 200.0,
fit: BoxFit.cover,
placeholder: (context, url) => SizedBox(
height: 32,
width: 32,
child: RiveAnimation.asset(
'assets/rive_animations/loading.riv',
),
),
errorWidget: (context, url, error) => Icon(Icons.error),
)
else
_buildAddPhoto(context, state),
SizedBox(height: 16.0),
Align(alignment: Alignment.centerLeft, child: Text("Название рецепта")),
CustomTextFormField(
controller: state.title,
hintText: "Введите название рецепта",
fillColor: appTheme.gray50,
borderDecoration: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: BorderSide(
color: appTheme.gray50,
width: 1,
),
),
),
SizedBox(height: 16.0),
Align(alignment: Alignment.centerLeft, child: Text("Описание")),
CustomTextFormField(
controller: state.description,
hintText: "Введите описание рецепта",
maxLines: 4,
fillColor: appTheme.gray50,
borderDecoration: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: BorderSide(
color: appTheme.gray50,
width: 1,
),
),
),
SizedBox(height: 16.0),
],
),
);
}
refreshPage(BuildContext context) {
return () async {
context.read<RecipeEditBloc>().add(ReloadScreen());
};
}
_buildAddPhoto(BuildContext context, RecipeEditLoadedState state) {
bool isLoading = false;
return GestureDetector(
child: Stack(
children: [
if (!isLoading)
Container(
padding: EdgeInsets.symmetric(
horizontal: 40,
vertical: 80,
),
decoration: AppDecoration.fillGray.copyWith(
borderRadius: BorderRadiusStyle.roundedBorder5,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: EdgeInsets.only(
left: 7,
top: 5,
),
child: Text(
"Добавить фото",
selectionColor: appTheme.blueA400,
),
),
Spacer(),
Icon(
Icons.photo_library,
color: appTheme.blueGray300,
),
],
),
),
/*if (isLoading)
Container(
margin: EdgeInsets.symmetric(horizontal: 10),
padding: EdgeInsets.symmetric(
horizontal: 40,
vertical: 80.v,
),
decoration: AppDecoration.fillGray.copyWith(
borderRadius: BorderRadiusStyle.roundedBorder5,
),
child: Center(
child: CircularProgressIndicator(
color: appTheme
.blueGray300), // Замените на свой прелоадер при необходимости
),
),*/
],
),
);
}
_buildStep(BuildContext context, RecipeEditLoadedState state, int i) {
bool isLastStep = i == state.steps!.length - 1;
bool isFirstStep = i == 0;
return Container(
margin: EdgeInsets.only(
left: 16,
right: 16,
bottom: 10,
),
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Align(
alignment: Alignment.centerLeft,
child: Text(
"Шаг ${i + 1}",
style: CustomTextStyles.titleMediumSemiBold,
),
),
SizedBox(height: 8.0),
CustomTextFormField(
controller: state.stepDescriptions?[i],
hintText: "Введите описание шага",
maxLines: 4,
fillColor: appTheme.gray50,
borderDecoration: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: BorderSide(
color: appTheme.gray50,
width: 1,
),
),
),
SizedBox(height: 16.0),
if (state.steps?[i]['image'] != null)
CachedNetworkImage(
imageUrl: baseUrl + '/storage/' + (state.steps?[i]['image']),
width: double.infinity,
// Установите ширину равной ширине родительского элемента
height: 200.0,
// Задайте постоянную высоту элемента
fit: BoxFit.cover,
placeholder: (context, url) => SizedBox(
height: 32,
width: 32,
child: RiveAnimation.asset(
'assets/rive_animations/loading.riv',
),
),
errorWidget: (context, url, error) => Icon(Icons.error),
)
else
_buildAddPhoto(context, state),
SizedBox(height: 16.0),
if (isFirstStep && isLastStep)
CustomOutlinedButton(
onPressed: () => context.read<RecipeEditBloc>().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<RecipeEditBloc>().add(AddStep()),
text: "Добавить шаг",
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
),
),
SizedBox(width: 8), // Add some spacing between the buttons
Flexible(
flex: 1,
child: CustomOutlinedButton(
text: "х",
onPressed: () => context.read<RecipeEditBloc>().add(RemoveStep(i)),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
),
),
],
),
],
),
if (!isLastStep && !isFirstStep)
CustomOutlinedButton(
onPressed: () => context.read<RecipeEditBloc>().add(RemoveStep(i)),
text: "x",
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
),
],
),
);
}
_buildAdditional(BuildContext context, RecipeEditLoadedState state) {
return Container(
margin: EdgeInsets.only(
left: 16,
right: 16,
bottom: 10,
),
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Align(alignment: Alignment.centerLeft, child: Text("Теги")),
_buildEditableTags(state.tags),
SizedBox(height: 8.0),
Align(alignment: Alignment.centerLeft, child: Text("Ссылка на видео (необязательно)")),
CustomTextFormField(
controller: state.video_url,
hintText: "YouTube",
fillColor: appTheme.gray50,
borderDecoration: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: BorderSide(
color: appTheme.gray50,
width: 1,
),
),
),
SizedBox(height: 16.0),
CustomOutlinedButton(
text: "Добавить рецепт",
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
),
],
),
);
}
_buildEditableTags(List<dynamic>? tags) {
String initialText = tags!.map((tag) => tag['name']).join(', ');
return CustomTextFormField(
initialValue: initialText,
fillColor: appTheme.gray50,
borderDecoration: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: BorderSide(
color: appTheme.gray50,
width: 1,
),
),
onChanged: _trimTags,
);
}
void _trimTags(newValue) {
List<String> newTagNames = newValue!.split(',').map((tag) => tag.trim()).toList();
newTagNames.map((tagName) => {'name': tagName}).toList();
}
Function onTapBack(BuildContext context) {
return () {
onTapArrowLeft(context);
};
}
onTapArrowLeft(BuildContext context) {
GoRouter.of(context).pop();
}
}

View File

@@ -1,65 +0,0 @@
import 'dart:async';
import 'package:dio/dio.dart';
import 'package:eatvio_app/app_router.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/token_storage.dart';
import 'package:eatvio_app/locator_service.dart';
import 'package:eatvio_app/old/recipe/recipe_select_screen/models/recipe_select_model.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:image_picker/image_picker.dart';
part 'recipe_select_event.dart';
part 'recipe_select_state.dart';
/// A bloc that manages the state of a RecipeSelect according to the event that is dispatched to it.
class RecipeSelectBloc extends Bloc<RecipeSelectEvent, RecipeSelectState> {
RecipeSelectBloc(RecipeSelectState initialState) : super(initialState) {
on<RecipeSelectInitialEvent>(_onInitialize);
}
Future<void> _onInitialize(
RecipeSelectInitialEvent event,
Emitter<RecipeSelectState> emit,
) async {
try {
if (!await TokenStorage.haveTokenInStorage()) {
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final response = await sl<Dio>().get(baseUrl + '/api/recipes/index');
if (response.statusCode == 200) {
final data = response.data['data'];
if (data is List<dynamic>) {
if (state is RecipeSelectLoadedState) {
var currentState = state as RecipeSelectLoadedState;
emit(
(currentState).copyWith(
posts: data,
myProfileModelObj: currentState.myProfileModelObj,
),
);
} else {
emit(
RecipeSelectLoadedState(
posts: data,
),
);
}
} else {
// Один или несколько ключевых полей отсутствуют, обрабатываем ошибку
emit(RecipeSelectErrorState('Неверный формат ответа API.'));
}
} else {
// Статус ответа не равен 200, обрабатываем ошибку
emit(RecipeSelectErrorState('Ошибка сервера: ${response.statusCode}'));
}
} catch (e) {
// Ошибка при декодировании JSON или другая ошибка выполнения
emit(RecipeSelectErrorState('Ошибка при получении данных ленты: $e'));
}
}
}

View File

@@ -1,25 +0,0 @@
// ignore_for_file: must_be_immutable
part of 'recipe_select_bloc.dart';
/// Abstract class for all events that can be dispatched from the
///RecipeSelect widget.
///
/// Events must be immutable and implement the [Equatable] interface.
@immutable
abstract class RecipeSelectEvent extends Equatable {}
/// Event that is dispatched when the RecipeSelect widget is first created.
class RecipeSelectInitialEvent extends RecipeSelectEvent {
@override
List<Object?> get props => [];
}
class UploadAvatar extends RecipeSelectEvent {
UploadAvatar(this.image);
final XFile image;
@override
List<Object> get props => [image];
}

View File

@@ -1,49 +0,0 @@
// ignore_for_file: must_be_immutable
part of 'recipe_select_bloc.dart';
class RecipeSelectState extends Equatable {
const RecipeSelectState({this.myProfileModelObj});
final RecipeSelectModel? myProfileModelObj;
@override
List<Object?> get props => [myProfileModelObj];
}
class RecipeSelectInitialState extends RecipeSelectState {
RecipeSelectInitialState({required RecipeSelectModel myProfileModelObj}) : super(myProfileModelObj: myProfileModelObj);
}
/// Represents the state of RecipeSelect in the application.
class RecipeSelectLoadedState extends RecipeSelectState {
RecipeSelectLoadedState({this.myProfileModelObj, this.posts}) : super(myProfileModelObj: null);
RecipeSelectModel? myProfileModelObj;
List<dynamic>? posts;
@override
List<Object?> get props => [
myProfileModelObj,
posts,
];
RecipeSelectLoadedState copyWith({
RecipeSelectModel? myProfileModelObj,
List<dynamic>? posts,
}) {
return RecipeSelectLoadedState(
myProfileModelObj: myProfileModelObj ?? this.myProfileModelObj,
posts: posts ?? this.posts,
);
}
}
class RecipeSelectErrorState extends RecipeSelectState {
final String errorMessage;
const RecipeSelectErrorState(this.errorMessage);
@override
List<Object> get props => [errorMessage];
}

View File

@@ -1,16 +0,0 @@
// ignore_for_file: must_be_immutable
import 'package:equatable/equatable.dart';
/// This class defines the variables used in the [k0_page],
/// and is typically used to hold data that is passed between different parts of the application.
class RecipeSelectModel extends Equatable {
RecipeSelectModel();
RecipeSelectModel copyWith() {
return RecipeSelectModel();
}
@override
List<Object?> get props => [];
}

View File

@@ -1,201 +0,0 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/utils/image_constant.dart';
import 'package:eatvio_app/theme/custom_text_style.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/app_bar/custom_app_bar.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:rive/rive.dart';
import 'bloc/recipe_select_bloc.dart';
import 'models/recipe_select_model.dart';
class RecipeSelectScreen extends StatelessWidget {
static Widget builder(BuildContext context) {
return BlocProvider<RecipeSelectBloc>(
create: (context) => RecipeSelectBloc(RecipeSelectState(myProfileModelObj: RecipeSelectModel()))..add(RecipeSelectInitialEvent()),
child: RecipeSelectScreen(),
);
}
Widget build(BuildContext context) {
return BlocBuilder<RecipeSelectBloc, RecipeSelectState>(
builder: (context, state) {
if (state is RecipeSelectInitialState) {
return Scaffold(
body: Center(
child: SizedBox(
height: 32,
width: 32,
child: RiveAnimation.asset(
'assets/rive_animations/loading.riv',
),
),
),
);
} else if (state is RecipeSelectLoadedState) {
// Данные загружены, можно строить экран.
return _buildRecipeScreen(context, state);
} else {
return Scaffold(
body: Center(
child: SizedBox(
height: 32,
width: 32,
child: RiveAnimation.asset(
'assets/rive_animations/loading.riv',
),
),
),
);
}
},
);
}
_buildRecipeScreen(BuildContext context, RecipeSelectLoadedState 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: 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,
leadingWidth: 28,
leading: AppbarLeadingImage(
imagePath: ImageConstant.imgArrowLeft,
margin: EdgeInsets.only(left: 16, top: 15, bottom: 15),
onTap: () => GoRouter.of(context).pop(),
),
title: Padding(
padding: EdgeInsets.only(left: 20),
child: Row(
children: [
AppbarSubtitle(
text: "Рецепты",
margin: EdgeInsets.only(left: 80, top: 12, bottom: 9),
),
Spacer(),
Padding(
padding: const EdgeInsets.only(right: 20.0),
child: GestureDetector(
onTap: () => GoRouter.of(context).pushNamed('recipeCreateScreen'),
child: Icon(Icons.create_outlined),
),
),
],
),
),
),
),
SizedBox(height: 20),
_buildRecipeList(context, state),
],
),
),
),
),
);
}
_buildRecipeList(BuildContext context, RecipeSelectLoadedState state) {
return ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true, // Add this line
itemCount: state.posts?.length ?? 0,
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),
),
],
),
child: ListTile(
leading: CachedNetworkImage(
imageUrl: baseUrl + '/storage/' + post['image'],
imageBuilder: (context, imageProvider) => CircleAvatar(
backgroundImage: imageProvider,
radius: 25,
),
placeholder: (context, url) => SizedBox(
height: 32,
width: 32,
child: RiveAnimation.asset(
'assets/rive_animations/loading.riv',
),
),
errorWidget: (context, url, error) => CircleAvatar(
child: Icon(Icons.person),
backgroundColor: Colors.white,
radius: 25,
),
),
title: Text(
post?['title'] ?? '',
style: CustomTextStyles.titleMediumSemiBold,
),
subtitle: Text(post?['description'] ?? ''),
onTap: () => GoRouter.of(context).goNamed(
'recipeScreen',
extra: post?['id'].toString(),
),
trailing: GestureDetector(
onTap: () => GoRouter.of(context).pushNamed(
'recipeEditScreen',
extra: post?['slug'].toString(),
),
child: Icon(Icons.edit),
),
),
);
},
);
}
Function onTapBack(BuildContext context) {
return () {
onTapArrowLeft(context);
};
}
onTapArrowLeft(BuildContext context) {
GoRouter.of(context).pop();
}
}

View File

@@ -1,66 +0,0 @@
//import 'package:eatvio_app/old/auth/auth_screen/auth_screen.dart';
import 'dart:async';
import 'dart:convert';
import 'package:eatvio_app/app_router.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/token_storage.dart';
import 'package:eatvio_app/old/users/subscribers_screen/models/subscribers_model.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart' as http;
part 'subscribers_event.dart';
part 'subscribers_state.dart';
/// A bloc that manages the state of a Subscribers according to the event that is dispatched to it.
class SubscribersBloc extends Bloc<SubscribersEvent, SubscribersState> {
SubscribersBloc(SubscribersState initialState) : super(initialState) {
on<SubscribersInitialEvent>(_onInitialize);
}
Future<void> _onInitialize(
SubscribersInitialEvent event,
Emitter<SubscribersState> emit,
) async {
try {
if (!await TokenStorage.haveTokenInStorage()) {
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final response = await http.get(
Uri.parse(baseUrl + '/api/profile/subscribers'),
headers: {
'Authorization': 'Bearer ${await TokenStorage.getTokenFromStorage()}',
},
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
if (data is List<dynamic>) {
if (state is SubscribersLoadedState) {
var currentState = state as SubscribersLoadedState;
emit((currentState).copyWith(
subscribers: data,
myProfileModelObj: currentState.myProfileModelObj,
),);
} else {
emit(SubscribersLoadedState(
subscribers: data,
),);
}
} else {
// Один или несколько ключевых полей отсутствуют, обрабатываем ошибку
emit(SubscribersErrorState('Неверный формат ответа API.'));
}
} else {
// Статус ответа не равен 200, обрабатываем ошибку
emit(SubscribersErrorState('Ошибка сервера: ${response.statusCode}'));
}
} catch (e) {
// Ошибка при декодировании JSON или другая ошибка выполнения
emit(SubscribersErrorState('Ошибка при получении данных ленты: $e'));
}
}
}

View File

@@ -1,16 +0,0 @@
// ignore_for_file: must_be_immutable
part of 'subscribers_bloc.dart';
/// Abstract class for all events that can be dispatched from the
///Subscribers widget.
///
/// Events must be immutable and implement the [Equatable] interface.
@immutable
abstract class SubscribersEvent extends Equatable {}
/// Event that is dispatched when the Subscribers widget is first created.
class SubscribersInitialEvent extends SubscribersEvent {
@override
List<Object?> get props => [];
}

View File

@@ -1,49 +0,0 @@
// ignore_for_file: must_be_immutable
part of 'subscribers_bloc.dart';
class SubscribersState extends Equatable {
const SubscribersState({this.myProfileModelObj});
final SubscribersModel? myProfileModelObj;
@override
List<Object?> get props => [myProfileModelObj];
}
class SubscribersInitialState extends SubscribersState {
SubscribersInitialState({required SubscribersModel myProfileModelObj}) : super(myProfileModelObj: myProfileModelObj);
}
/// Represents the state of Subscribers in the application.
class SubscribersLoadedState extends SubscribersState {
SubscribersLoadedState({this.myProfileModelObj, this.subscribers}) : super(myProfileModelObj: null);
SubscribersModel? myProfileModelObj;
dynamic subscribers;
@override
List<Object?> get props => [
myProfileModelObj,
subscribers,
];
SubscribersLoadedState copyWith({
SubscribersModel? myProfileModelObj,
dynamic subscribers,
}) {
return SubscribersLoadedState(
myProfileModelObj: myProfileModelObj ?? this.myProfileModelObj,
subscribers: subscribers ?? this.subscribers,
);
}
}
class SubscribersErrorState extends SubscribersState {
final String errorMessage;
const SubscribersErrorState(this.errorMessage);
@override
List<Object> get props => [errorMessage];
}

View File

@@ -1,16 +0,0 @@
// ignore_for_file: must_be_immutable
import 'package:equatable/equatable.dart';
/// This class defines the variables used in the [k0_page],
/// and is typically used to hold data that is passed between different parts of the application.
class SubscribersModel extends Equatable {
SubscribersModel();
SubscribersModel copyWith() {
return SubscribersModel();
}
@override
List<Object?> get props => [];
}

View File

@@ -1,217 +0,0 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/utils/image_constant.dart';
import 'package:eatvio_app/theme/custom_button_style.dart';
import 'package:eatvio_app/theme/custom_text_style.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/app_bar/custom_app_bar.dart';
import 'package:eatvio_app/widgets/custom_outlined_button.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:rive/rive.dart' as rive;
import 'package:rive/rive.dart';
import 'bloc/subscribers_bloc.dart';
import 'models/subscribers_model.dart';
class SubscribersScreen extends StatelessWidget {
static Widget builder(BuildContext context) {
return BlocProvider<SubscribersBloc>(
create: (context) => SubscribersBloc(SubscribersState(myProfileModelObj: SubscribersModel()))..add(SubscribersInitialEvent()),
child: SubscribersScreen(),);
}
Widget build(BuildContext context) {
return BlocBuilder<SubscribersBloc, SubscribersState>(
builder: (context, state) {
if (state is SubscribersInitialState) {
// Это начальное состояние, данные ещё не загружены.
return SizedBox(
height: 32,
width: 32,
child: rive.RiveAnimation.asset(
'assets/rive_animations/loading.riv',
),
);
} else if (state is SubscribersLoadedState) {
// Данные загружены, можно строить экран.
return _buildSubscribersScreen(context, state);
} else {
// Другие состояния, если они есть.
return Container(); // Пустой контейнер для необработанных состояний.
}
},
);
}
_buildSubscribersScreen(BuildContext context, SubscribersLoadedState state) {
return 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: 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,
leadingWidth: 28,
leading: AppbarLeadingImage(
imagePath: ImageConstant.imgArrowLeft,
margin: EdgeInsets.only(left: 16, top: 15, bottom: 15),
onTap: () => GoRouter.of(context).pop(),
),
title: Padding(
padding: EdgeInsets.only(left: 20),
child: Row(
children: [
AppbarSubtitle(
text: "Мои подписчики",
margin: EdgeInsets.only(left: 80, top: 12, bottom: 9),
),
Spacer(),
Padding(
padding: const EdgeInsets.only(right: 20.0),
child: GestureDetector(child: Icon(Icons.create_outlined)),
),
],
),
),
),
),
_buildSearch(context),
SizedBox(height: 16),
_buildSubscribersList(context, state),
],
),
),
),
),
);
}
_buildSearch(BuildContext context) {
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),
),
],
),
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
children: [
SizedBox(height: 10),
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: () => GoRouter.of(context).pushNamed('blogPostCreateScreen'),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
),
),
],
),
],
),
),
);
}
_buildSubscribersList(BuildContext context, SubscribersLoadedState state) {
List<Widget> subscriberWidgets = [];
if (state.subscribers != null) {
for (var subscriber in state.subscribers!) {
subscriberWidgets.add(_buildSubscriberEntry(subscriber, context));
}
}
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: subscriberWidgets,
);
}
_buildSubscriberEntry(BuildContext context, subscriber) {
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),
),
],
),
child: ListTile(
leading: CachedNetworkImage(
imageUrl: baseUrl + '/storage/' + subscriber['image'],
imageBuilder: (context, imageProvider) => CircleAvatar(
backgroundImage: imageProvider,
radius: 25,
),
placeholder: (context, url) => SizedBox(
height: 32,
width: 32,
child: RiveAnimation.asset(
'assets/rive_animations/loading.riv',
),
),
errorWidget: (context, url, error) => CircleAvatar(
child: Icon(Icons.person),
backgroundColor: Colors.white,
radius: 25,
),
),
title: Text(
subscriber?['title'] ?? '',
style: CustomTextStyles.titleMediumSemiBold,
),
subtitle: Text(subscriber?['description'] ?? ''),
trailing: GestureDetector(child: Icon(Icons.edit)),
),);
}
}

View File

@@ -1,466 +0,0 @@
import 'dart:convert';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/token_storage.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:eatvio_app/old/profile/my_profile_screen/bloc/my_profile_bloc.dart';
import 'package:eatvio_app/theme/custom_button_style.dart';
import 'package:eatvio_app/widgets/custom_hyper_switch.dart';
import 'package:eatvio_app/widgets/custom_hypo_switch.dart';
import 'package:eatvio_app/widgets/next_step_button.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart' as http;
import 'package:intl/intl.dart';
import 'package:rive/rive.dart' as rive;
class ExpandableList extends StatefulWidget {
final String title;
ExpandableList(this.title);
@override
_ExpandableListState createState() => _ExpandableListState();
}
class _ExpandableListState extends State<ExpandableList> {
bool isExpanded = false;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
GestureDetector(
onTap: toggleExpand(),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
widget.title,
style: TextStyle(
color: Colors.black,
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
Icon(isExpanded ? Icons.arrow_drop_up : Icons.arrow_drop_down),
],
),
),
if (isExpanded)
Padding(
padding: const EdgeInsets.only(left: 16.0),
child: BlocBuilder<MyProfileBloc, MyProfileState>(
builder: (context, state) {
if (state is MyProfileInitialState) {
// Это начальное состояние, данные ещё не загружены.
return SizedBox(
height: 32,
width: 32,
child: rive.RiveAnimation.asset(
'assets/rive_animations/loading.riv',
),
);
} else if (state is MyProfileLoadedState) {
// Данные загружены, можно строить форму.
return _buildForm(context, state);
} else if (state is MyProfileErrorState) {
// Произошла ошибка при загрузке данных.
return Center(child: Text(state.errorMessage));
} else {
// Другие состояния, если они есть.
return Container(); // Пустой контейнер для необработанных состояний.
}
},
),
),
Divider(),
],
);
}
toggleExpand() {
return () {
setState(() {
isExpanded = !isExpanded;
});
};
}
_buildForm(BuildContext context, MyProfileLoadedState state) {
final s = S.of(context);
List<String> cities = [
'Москва',
'Санкт-Петербург',
'Новосибирск',
'Екатеринбург',
'Нижний Новгород',
'Казань',
'Челябинск',
'Омск',
'Самара',
'Ростов-на-Дону',
'Уфа',
'Красноярск',
'Воронеж',
'Пермь',
'Волгоград',
'Краснодар',
'Саратов',
'Тюмень',
'Тольятти',
'Ижевск',
'Барнаул',
'Ульяновск',
'Иркутск',
'Хабаровск',
'Ярославль',
'Владивосток',
'Махачкала',
'Томск',
'Оренбург',
'Кемерово',
];
List<String> countries = [
'Россия',
];
List<String> activities = [
'сидячий',
'низкий (1-3 тренировки в неделю по 20 мин)',
'умеренный (3-4 тренировки в неделю по 30-60 мин)',
'высокий (5-7 тренировок по 30-60 мин)',
'экстремальный (несколько интенсивных тренировок в день)',
];
final digitsOnly = FilteringTextInputFormatter.digitsOnly;
return Column(
children: [
TextField(
controller: state.firstNameFormController,
decoration: InputDecoration(labelText: s.first_name),
),
TextField(
controller: state.lastNameFormController,
decoration: InputDecoration(labelText: s.last_name),
),
TextField(
decoration: InputDecoration(labelText: s.date_of_birth),
controller: state.dateFormController,
onTap: inputTime(context, state),
),
DropdownButtonFormField<String>(
value: state.selectedCountry,
hint: Text(state.selectedCountry ?? "Не выбрано"),
onChanged: setCountry(state),
items: countries.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
decoration: InputDecoration(labelText: s.country),
),
DropdownButtonFormField<String>(
hint: Text(state.selectedCity ?? "Не выбрано"),
value: state.selectedCity,
onChanged: setCity(state),
items: cities.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
decoration: InputDecoration(labelText: s.city_of_living),
),
TextField(
controller: state.weightFormController,
decoration: InputDecoration(labelText: s.weight_measurements),
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[digitsOnly],
),
TextField(
controller: state.heightFormController,
decoration: InputDecoration(labelText: s.height_measurements),
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[digitsOnly],
),
TextField(
controller: state.chestCircumferenceFormController,
decoration: InputDecoration(labelText: s.chest_circumference),
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[digitsOnly],
),
TextField(
controller: state.waistCircumferenceFormController,
decoration: InputDecoration(labelText: s.waist_circumference),
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[digitsOnly],
),
TextField(
controller: state.hipCircumferenceFormController,
decoration: InputDecoration(labelText: s.hip_circumference),
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[digitsOnly],
),
TextField(
controller: state.upPressureFormController,
decoration: InputDecoration(labelText: s.up_pressure),
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[digitsOnly],
),
TextField(
controller: state.downPressureFormController,
decoration: InputDecoration(labelText: s.down_pressure),
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[digitsOnly],
),
TextField(
controller: state.allergiesFormController,
decoration: InputDecoration(labelText: s.allergies_presence),
maxLines: null,
),
TextField(
controller: state.pillsFormController,
decoration: InputDecoration(labelText: s.do_you_take_pills),
maxLines: null,
),
TextField(
controller: state.sportsFormController,
decoration: InputDecoration(labelText: s.your_favorite_sports),
maxLines: null,
),
TextField(
controller: state.favoriteFormController,
decoration: InputDecoration(labelText: s.favorite_foods),
maxLines: null,
),
TextField(
controller: state.unfavoriteFormController,
decoration: InputDecoration(labelText: s.unfavorite_foods),
maxLines: null,
),
TextField(
controller: state.additionalFormController,
decoration: InputDecoration(labelText: s.additional_information),
maxLines: null,
),
DropdownButtonFormField<String>(
hint: Text(state.selectedActivity ?? "Не выбрано"),
value: state.selectedActivity,
onChanged: setActivity(state),
items: activities.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: SizedBox(
// Используем SizedBox для ограничения ширины элемента списка
width: MediaQuery.of(context).size.width * 0.8, // Устанавливаем желаемую ширину элемента списка
child: Text(value),
),
);
}).toList(),
isExpanded: true,
// Расширяем список на всю доступную ширину
decoration: InputDecoration(labelText: 'Активность'),
),
Row(
children: [
HypoSwitch(s.do_you_have_hypotension_tendency, state),
],
),
Row(
children: [
HyperSwitch(s.do_you_have_hypertension_tendency, state),
],
),
NextStepButton(
onPressed: () => sendProfileData(state),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
text: s.save.toUpperCase(),
margin: EdgeInsets.only(top: 20, bottom: 20),
),
],
);
}
sendData(MyProfileLoadedState state) {
return () {
sendProfileData(state);
};
}
setCity(MyProfileLoadedState state) {
return (newValue) {
// Обработчик изменения выбранного города
setSelectedCity(state, newValue);
};
}
setCountry(MyProfileLoadedState state) {
return (newValue) {
// Обработчик изменения выбранной страны
setSelectedCountry(state, newValue);
};
}
setActivity(MyProfileLoadedState state) {
return (newValue) {
// Обработчик изменения выбранной страны
setSelectedActivity(state, newValue);
};
}
inputTime(BuildContext context, MyProfileLoadedState state) {
return () {
showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(1900),
lastDate: DateTime.now(),
).then((selectedDate) {
if (selectedDate != null) {
setState(() {
state.dateFormController!.text = DateFormat('dd-MM-yyyy').format(selectedDate);
});
}
});
};
}
void setSelectedCity(MyProfileLoadedState? state, String? newValue) {
setState(() {
state!.selectedCity = newValue;
});
}
void setSelectedCountry(MyProfileLoadedState? state, String? newValue) {
setState(() {
state!.selectedCountry = newValue;
});
}
void setSelectedActivity(MyProfileLoadedState? state, String? newValue) {
setState(() {
state!.selectedActivity = newValue;
});
}
Future<void> sendProfileData(MyProfileLoadedState? state) async {
try {
final profileData = _createProfileData(state);
final token = await _getTokenFromBox();
final apiUrl = _buildApiUrl();
final response = await _sendHttpRequest(profileData, apiUrl, token);
if (response.statusCode == 200) {
_navigateToProfilePage();
} else {
final errorMessage = _getErrorMessage(response);
_showErrorDialog(errorMessage);
throw Exception('Ошибка при отправке данных профиля');
}
} catch (error) {
throw Exception('Ошибка при отправке данных профиля: $error');
}
}
Map<String, dynamic> _createProfileData(MyProfileLoadedState? state) {
return {
'first_name': state?.firstNameFormController?.text,
'last_name': state?.lastNameFormController?.text,
'chest_circumference': state?.chestCircumferenceFormController?.text,
'waist_circumference': state?.waistCircumferenceFormController?.text,
'hip_circumference': state?.hipCircumferenceFormController?.text,
'birthdate': state?.dateFormController?.text,
'height': state?.heightFormController?.text,
'weight': state?.weightFormController?.text,
'blood_pressure_up': state?.upPressureFormController?.text,
'blood_pressure_down': state?.downPressureFormController?.text,
'about_alergy': state?.allergiesFormController?.text ?? 'Нет аллергий',
'about_taking_pils': state?.pillsFormController?.text,
'about_sport_hobbies': state?.sportsFormController?.text,
'favorite_foods': state?.favoriteFormController?.text,
'unfavorite_foods': state?.unfavoriteFormController?.text,
'additional_info': state?.additionalFormController?.text,
'country': state?.selectedCountry ?? 'Россия',
'city': state?.selectedCity ?? 'Москва',
'has_hypo': state?.hypoFormController,
'has_hyper': state?.hyperFormController,
'activity_coefficient': determineActivityFactor(state?.selectedActivity ?? 'сидячий'),
};
}
Future<String?> _getTokenFromBox() async {
return await TokenStorage.getTokenFromStorage();
}
String _buildApiUrl() {
return baseUrl + '/api/profile/update';
}
Future<http.Response> _sendHttpRequest(
Map<String, dynamic> profileData,
String apiUrl,
String? token,
) {
return http.post(
Uri.parse(apiUrl),
body: json.encode(profileData),
encoding: utf8,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer $token',
},
);
}
void _navigateToProfilePage() {
GoRouter.of(context).go('/my-profile-screen');
}
String _getErrorMessage(http.Response response) {
final res = jsonDecode(response.body);
return res['message'];
}
void _showErrorDialog(String errorMessage) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Ошибка'),
content: Text(errorMessage),
actions: [
TextButton(
onPressed: onTapOk(context),
child: Text('OK'),
),
],
),
);
}
onTapOk(BuildContext context) {
return () {
Navigator.pop(context);
};
}
double determineActivityFactor(String activityLevel) {
switch (activityLevel) {
case "сидячий":
return 1.2;
case "низкий (1-3 тренировки в неделю по 20 мин)":
return 1.375;
case "умеренный (3-4 тренировки в неделю по 30-60 мин)":
return 1.55;
case "высокий (5-7 тренировок по 30-60 мин)":
return 1.7;
case "экстремальный (несколько интенсивных тренировок в день)":
return 1.9;
default:
return 1.2; // Возвращаемое значение по умолчанию, если уровень активности не распознан
}
}
}

View File

@@ -1,59 +0,0 @@
import 'package:eatvio_app/old/profile/my_profile_screen/bloc/my_profile_bloc.dart';
import 'package:eatvio_app/theme/theme_helper.dart';
import 'package:flutter/material.dart';
class HyperSwitch extends StatefulWidget {
final String title;
final MyProfileLoadedState? state;
HyperSwitch(this.title, this.state);
@override
_HyperState createState() => _HyperState();
}
class _HyperState extends State<HyperSwitch> {
bool switchValue = false;
@override
void initState() {
super.initState();
if (widget.state?.hyperFormController == '1') {
switchValue = true;
}
}
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
widget.title,
style: TextStyle(
color: Colors.black,
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
Switch(
value: switchValue,
onChanged: toggleSwitch(),
inactiveThumbColor: appTheme.blueA400,
inactiveTrackColor: Colors.white,
activeTrackColor: appTheme.blueA400,
activeColor: Colors.white,
),
],
);
}
toggleSwitch() {
return (value) {
setState(() {
switchValue = value;
widget.state!.hyperFormController = value ? "1" : "0";
});
};
}
}

View File

@@ -1,59 +0,0 @@
import 'package:eatvio_app/old/profile/my_profile_screen/bloc/my_profile_bloc.dart';
import 'package:eatvio_app/theme/theme_helper.dart';
import 'package:flutter/material.dart';
class HypoSwitch extends StatefulWidget {
final String title;
final MyProfileLoadedState? state;
HypoSwitch(this.title, this.state);
@override
_HypoState createState() => _HypoState();
}
class _HypoState extends State<HypoSwitch> {
bool switchValue = false;
@override
void initState() {
super.initState();
if (widget.state?.hypoFormController == '1') {
switchValue = true;
}
}
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
widget.title,
style: TextStyle(
color: Colors.black,
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
Switch(
value: switchValue,
onChanged: toggleSwitch(),
inactiveThumbColor: appTheme.blueA400,
inactiveTrackColor: Colors.white,
activeTrackColor: appTheme.blueA400,
activeColor: Colors.white,
),
],
);
}
toggleSwitch() {
return (value) {
setState(() {
switchValue = value;
widget.state!.hypoFormController = value ? "1" : "0";
});
};
}
}

View File

@@ -1,7 +1,7 @@
import 'package:eatvio_app/features/auth/auth_screen/auth_screen.dart';
import 'package:eatvio_app/features/auth/auth_screen/bloc/auth_bloc.dart';
import 'package:eatvio_app/features/auth/auth_screen/models/auth_model.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:eatvio_app/old/auth/auth_screen/auth_screen.dart';
import 'package:eatvio_app/old/auth/auth_screen/bloc/auth_bloc.dart';
import 'package:eatvio_app/old/auth/auth_screen/models/auth_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart';