forked from eatvio/eatvio_app
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:
@@ -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',
|
||||
|
||||
@@ -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';
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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];
|
||||
}*/
|
||||
@@ -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
@@ -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 => [];
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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('Создать'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 => [];
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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('Сохранить'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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'));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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 => [];
|
||||
}
|
||||
@@ -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}'));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 => [];
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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('Сохранить'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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));
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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 => [];
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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 => [];
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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('Создать'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 => [];
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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('Сохранить'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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'));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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 => [];
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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}'));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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}}',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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 => [];
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 => [];
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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'));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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 => [];
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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'));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 => [];
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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 => [];
|
||||
}
|
||||
@@ -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)),
|
||||
),);
|
||||
}
|
||||
}
|
||||
@@ -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; // Возвращаемое значение по умолчанию, если уровень активности не распознан
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user