merge fromdiary to main

This commit is contained in:
Anatoliy Poloz
2024-06-19 12:12:00 +03:00
90 changed files with 4667 additions and 4314 deletions

View File

@@ -3,7 +3,7 @@ name: Android Release
on: [push, pull_request]
env:
flutter_version: "3.19.0"
flutter_version: "3.22.0"
java_version: "12.x"
flutter_channel: "stable"

View File

@@ -3,7 +3,7 @@ name: Eatvio App Android Test
on: [pull_request]
env:
flutter_version: "3.19.0"
flutter_version: "3.22.0"
java_version: "12.x"
flutter_channel: "stable"

View File

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

View File

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

250
lib/app_router.dart Normal file
View File

@@ -0,0 +1,250 @@
import 'package:eatvio_app/presentation/auth/auth_screen/auth_screen.dart';
import 'package:eatvio_app/presentation/chat/chat_screen/chat_screen.dart';
import 'package:eatvio_app/presentation/chat/chat_select_screen/chat_select_screen.dart';
import 'package:eatvio_app/presentation/diary/diary_edit_screen/diary_edit_screen.dart';
import 'package:eatvio_app/presentation/diets/diet_create_screen/diet_create_screen.dart';
import 'package:eatvio_app/presentation/diets/diet_select_screen/diet_select_screen.dart';
import 'package:eatvio_app/presentation/feed/blog_post_create_screen/blog_post_create_screen.dart';
import 'package:eatvio_app/presentation/feed/blog_post_edit_screen/blog_post_edit_screen.dart';
import 'package:eatvio_app/presentation/feed/blog_post_screen/blog_post_screen.dart';
import 'package:eatvio_app/presentation/feed/feed_screen/feed_screen.dart';
import 'package:eatvio_app/presentation/glossary/glossary_screen/glossary_screen.dart';
import 'package:eatvio_app/presentation/plans/plan_select_screen/plan_select_screen.dart';
import 'package:eatvio_app/presentation/profile/my_profile_screen/my_profile_screen.dart';
import 'package:eatvio_app/presentation/profile/my_profile_screen/my_settings_screen.dart';
import 'package:eatvio_app/presentation/recipe/recipe_select_screen/recipe_select_screen.dart';
import 'package:eatvio_app/presentation/registration/additional_information_screen/additional_information_screen.dart';
import 'package:eatvio_app/presentation/registration/allergies_screen/allergies_screen.dart';
import 'package:eatvio_app/presentation/registration/blood_pressure_screen/blood_pressure_screen.dart';
import 'package:eatvio_app/presentation/registration/body_circumferences_screen/body_circumferences_screen.dart';
import 'package:eatvio_app/presentation/registration/city_of_living_screen/city_of_living_screen.dart';
import 'package:eatvio_app/presentation/registration/credentials_screen/credentials_screen.dart';
import 'package:eatvio_app/presentation/registration/date_of_birth_screen/date_of_birth_screen.dart';
import 'package:eatvio_app/presentation/registration/favorite_foods_screen/favorite_foods_screen.dart';
import 'package:eatvio_app/presentation/registration/favorite_sports_screen/favorite_sports_screen.dart';
import 'package:eatvio_app/presentation/registration/gender_screen/gender_screen.dart';
import 'package:eatvio_app/presentation/registration/height_screen/height_screen.dart';
import 'package:eatvio_app/presentation/registration/language_select_screen/language_select_screen.dart';
import 'package:eatvio_app/presentation/registration/medicine_intake_screen/medicine_intake_screen.dart';
import 'package:eatvio_app/presentation/registration/name_screen/name_screen.dart';
import 'package:eatvio_app/presentation/registration/weight_screen/weight_screen.dart';
import 'package:eatvio_app/widgets/bottom_navigation_bar_widget.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:hive_flutter/hive_flutter.dart';
class AppRouter {
static GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
static GoRouter appRouter = GoRouter(
initialLocation: '/auth-screen',
navigatorKey: navigatorKey,
routes: [
GoRoute(
path: '/auth-screen',
name: 'authScreen',
redirect: (context, state) async {
final box = await Hive.openBox('token');
final token = box.get('token');
if (token != null) {
return '/my-profile-screen';
}
return null;
},
builder: (context, state) => AuthScreen.builder(context),
routes: [
GoRoute(
path: 'language-select-screen',
name: 'languageSelectScreen',
builder: (context, state) => LanguageSelectScreen.builder(context),
routes: [
GoRoute(
path: 'credentials-screen',
name: 'credentialsScreen',
builder: (context, state) => CredentialsScreen.builder(context, state.extra),
routes: [
GoRoute(
path: 'name-screen',
name: 'nameScreen',
builder: (context, state) => NameScreen.builder(context, state.extra),
routes: [
GoRoute(
path: 'gender-screen',
name: 'genderScreen',
builder: (context, state) => GenderScreen.builder(context, state.extra),
routes: [
GoRoute(
path: 'date-of-birth-screen',
name: 'dateOfBirthScreen',
builder: (context, state) => DateOfBirthScreen.builder(context, state.extra),
routes: [
GoRoute(
path: 'city-of-living-screen',
name: 'cityOfLivingScreen',
builder: (context, state) => CityOfLivingScreen.builder(context, state.extra),
routes: [
GoRoute(
path: 'body-circumferences-screen',
name: 'bodyCircumferencesScreen',
builder: (context, state) => BodyCircumferencesScreen.builder(context, state.extra),
routes: [
GoRoute(
path: 'weight-screen',
name: 'weightScreen',
builder: (context, state) => WeightScreen.builder(context, state.extra),
routes: [
GoRoute(
path: 'height-screen',
name: 'heightScreen',
builder: (context, state) => HeightScreen.builder(context, state.extra),
routes: [
GoRoute(
path: 'blood-pressure-screen',
name: 'bloodPressureScreen',
builder: (context, state) => BloodPressureScreen.builder(context, state.extra),
routes: [
GoRoute(
path: 'favorite-sports-screen',
name: 'favoriteSportsScreen',
builder: (context, state) => FavoriteSportsScreen.builder(context, state.extra),
routes: [
GoRoute(
name: 'allergiesScreen',
path: 'allergiesScreen',
builder: (context, state) => AllergiesScreen.builder(context, state.extra),
routes: [
GoRoute(
name: 'medicineIntakeScreen',
path: 'medicine-intake-screen',
builder: (context, state) => MedicineIntakeScreen.builder(context, state.extra),
),
GoRoute(
name: 'favoriteFoodsScreen',
path: 'favorite-foods-screen',
builder: (context, state) => FavoriteFoodsScreen.builder(context, state.extra),
routes: [
GoRoute(
path: 'additional-information-screen',
name: 'additionalInformationScreen',
builder: (context, state) => AdditionalInformationScreen.builder(context, state.extra),
),
],
),
],
),
],
),
],
),
],
),
],
),
],
),
],
),
],
),
],
),
],
),
],
),
],
),
],
),
ShellRoute(
builder: (context, state, child) {
return BottomNavigationBarWidget(child: child);
},
routes: [
GoRoute(
path: '/my-profile-screen',
name: 'myProfileScreen',
builder: (context, state) => MyProfileScreen(),
),
GoRoute(
path: '/feed-screen',
name: 'feedScreen',
builder: (context, state) => FeedScreen(),
routes: [
GoRoute(
path: 'blog-post-create-screen',
name: 'blogPostCreateScreen',
builder: (context, state) => BlogPostCreateScreen.builder(context),
),
GoRoute(
path: 'blog-post-screen',
name: 'blogPostScreen',
builder: (context, state) => BlogPostScreen.builder(
context,
state.extra.toString(),
),
routes: [
GoRoute(
path: 'blog-post-edit-screen',
name: 'blogPostEditScreen',
builder: (context, state) => BlogPostEditScreen.builder(
context,
state.extra,
),
),
],
),
],
),
GoRoute(
path: '/diary-screen',
name: 'diaryScreen',
builder: (context, state) => DiaryEditScreen(),
),
GoRoute(
path: '/chat-select-screen',
name: 'chatSelectScreen',
builder: (context, state) => ChatSelectScreen(),
routes: [
GoRoute(
path: 'chat-screen',
name: 'chatScreen',
builder: (context, state) => ChatScreen.builder(context, state.extra),
),
],
),
],
),
GoRoute(
path: '/my-settings-screen',
name: 'mySettingsScreen',
builder: (context, state) => MySettingsScreen.builder(context),
),
GoRoute(
path: '/recipe-select-screen',
name: 'recipeSelectScreen',
builder: (context, state) => RecipeSelectScreen.builder(context),
),
GoRoute(
path: '/diet-select-screen',
name: 'dietSelectScreen',
builder: (context, state) => DietSelectScreen.builder(context),
),
GoRoute(
path: '/diet-create-screen',
name: 'dietCreateScreen',
builder: (context, state) => DietCreateScreen.builder(context),
),
GoRoute(
path: '/plan-select-screen',
name: 'planSelectScreen',
builder: (context, state) => PlanSelectScreen.builder(context),
),
GoRoute(
path: '/glossary-screen',
name: 'glossaryScreen',
builder: (context, state) => GlossaryScreen.builder(context),
),
],
);
}

View File

@@ -4,12 +4,10 @@ export 'package:eatvio_app/core/errors/exceptions.dart';
export 'package:eatvio_app/core/network/network_info.dart';
export 'package:eatvio_app/core/utils/image_constant.dart';
export 'package:eatvio_app/core/utils/logger.dart';
export 'package:eatvio_app/core/utils/navigator_service.dart';
export 'package:eatvio_app/core/utils/pref_utils.dart';
export 'package:eatvio_app/core/utils/size_utils.dart';
export 'package:eatvio_app/data/models/selectionPopupModel/selection_popup_model.dart';
export 'package:eatvio_app/localization/app_localization.dart';
export 'package:eatvio_app/routes/app_routes.dart';
export 'package:eatvio_app/theme/app_decoration.dart';
export 'package:eatvio_app/theme/custom_text_style.dart';
export 'package:eatvio_app/theme/theme_helper.dart';

View File

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

View File

@@ -1,17 +1,15 @@
import 'package:eatvio_app/app_router.dart';
import 'package:flutter/material.dart';
import 'package:eatvio_app/core/app_export.dart';
class ProgressDialogUtils {
static bool isProgressVisible = false;
///common method for showing progress dialog
static void showProgressDialog(
{BuildContext? context, isCancellable = false}) async {
if (!isProgressVisible &&
NavigatorService.navigatorKey.currentState?.overlay?.context != null) {
static void showProgressDialog({BuildContext? context, isCancellable = false}) async {
if (!isProgressVisible && AppRouter.navigatorKey.currentState?.overlay?.context != null) {
showDialog(
barrierDismissible: isCancellable,
context: NavigatorService.navigatorKey.currentState!.overlay!.context,
context: AppRouter.navigatorKey.currentState!.overlay!.context,
builder: (BuildContext context) {
return Center(
child: CircularProgressIndicator.adaptive(
@@ -28,9 +26,7 @@ class ProgressDialogUtils {
///common method for hiding progress dialog
static void hideProgressDialog() {
if (isProgressVisible)
Navigator.pop(
NavigatorService.navigatorKey.currentState!.overlay!.context);
if (isProgressVisible) Navigator.pop(AppRouter.navigatorKey.currentState!.overlay!.context);
isProgressVisible = false;
}
}

View File

@@ -1,8 +1,9 @@
import 'package:eatvio_app/app_router.dart';
import 'ru/ru_translations.dart';
//import 'en_us/en_us_translations.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/cupertino.dart';
import '../core/app_export.dart';
class AppLocalization {
AppLocalization(this.locale);
@@ -17,7 +18,7 @@ class AppLocalization {
};*/
static AppLocalization of() {
final context = NavigatorService.navigatorKey.currentContext;
final context = AppRouter.navigatorKey.currentContext;
if (context != null) {
return Localizations.of<AppLocalization>(context, AppLocalization)!;
} else {

View File

@@ -1,3 +1,12 @@
import 'package:eatvio_app/app_router.dart';
import 'package:eatvio_app/presentation/chat/chat_select_screen/bloc/chat_select_bloc.dart';
import 'package:eatvio_app/presentation/chat/chat_select_screen/models/chat_select_model.dart';
import 'package:eatvio_app/presentation/diary/diary_edit_screen/bloc/diary_edit_bloc.dart';
import 'package:eatvio_app/presentation/diary/diary_edit_screen/models/diary_edit_model.dart';
import 'package:eatvio_app/presentation/feed/feed_screen/bloc/feed_bloc.dart';
import 'package:eatvio_app/presentation/feed/feed_screen/models/feed_model.dart';
import 'package:eatvio_app/presentation/profile/my_profile_screen/bloc/my_profile_bloc.dart';
import 'package:eatvio_app/presentation/profile/my_profile_screen/models/my_profile_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
@@ -8,6 +17,7 @@ import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:keyboard_dismisser/keyboard_dismisser.dart';
var globalMessengerKey = GlobalKey<ScaffoldMessengerState>();
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final appDocumentDir = await getApplicationDocumentsDirectory();
@@ -26,12 +36,39 @@ void main() async {
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => ThemeBloc(
ThemeState(
themeType: PrefUtils().getThemeData(),
return MultiBlocProvider(
providers: [
BlocProvider<ThemeBloc>(
create: (context) => ThemeBloc(
ThemeState(themeType: PrefUtils().getThemeData()),
),
),
),
BlocProvider<MyProfileBloc>(
create: (context) => MyProfileBloc(
MyProfileState(
myProfileModelObj: MyProfileModel(),
),
)..add(MyProfileInitialEvent()),
),
BlocProvider<FeedBloc>(
create: (context) => FeedBloc(FeedState(myProfileModelObj: FeedModel()))
..add(
FeedInitialEvent(),
),
),
BlocProvider<DiaryEditBloc>(
create: (context) => DiaryEditBloc(DiaryEditState(diaryEditModelObj: DiaryEditModel()))
..add(
DiaryEditInitialEvent(),
),
),
BlocProvider<ChatSelectBloc>(
create: (context) => ChatSelectBloc(ChatSelectState(chatSelectModelObj: ChatSelectModel()))
..add(
ChatSelectInitialEvent(),
),
),
],
child: BlocBuilder<ThemeBloc, ThemeState>(
builder: (context, state) {
return KeyboardDismisser(
@@ -39,10 +76,10 @@ class MyApp extends StatelessWidget {
GestureType.onTap,
GestureType.onPanUpdateDownDirection,
],
child: MaterialApp(
child: MaterialApp.router(
theme: theme,
title: 'Eatvio',
navigatorKey: NavigatorService.navigatorKey,
routerConfig: AppRouter.appRouter,
debugShowCheckedModeBanner: false,
localizationsDelegates: [
AppLocalizationDelegate(),
@@ -56,8 +93,6 @@ class MyApp extends StatelessWidget {
'',
),
],
initialRoute: AppRoutes.initialRoute,
routes: AppRoutes.routes,
));
},
),

View File

@@ -1,3 +1,5 @@
import 'package:go_router/go_router.dart';
import 'bloc/auth_bloc.dart';
import 'models/auth_model.dart';
import 'package:eatvio_app/core/app_export.dart';
@@ -19,7 +21,9 @@ class AuthScreen extends StatelessWidget {
AuthState(
authModelObj: AuthModel(),
),
)..add(AuthInitialEvent()),
)..add(
AuthInitialEvent(),
),
child: AuthScreen(),
);
}
@@ -27,72 +31,82 @@ class AuthScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
mediaQueryData = MediaQuery.of(context);
return BlocBuilder<AuthBloc, AuthState>(builder: (context, state) {
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: _buildAppBar(context),
body: Container(
width: double.maxFinite,
padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v),
child: Column(children: [
SizedBox(height: 36.v),
Text("user_authorization".tr,
style: theme.textTheme.headlineSmall),
SizedBox(height: 60.v),
Text("email".tr, style: theme.textTheme.bodyLarge),
SizedBox(height: 3.v),
CustomTextFormField(
controller: state.emailController,
textInputAction: TextInputAction.done),
SizedBox(height: 22.v),
Text("password".tr, style: theme.textTheme.bodyLarge),
SizedBox(height: 5.v),
CustomTextFormField(
obscureText: true,
controller: state.passwordController,
),
SizedBox(height: 50.v),
NextStepButton(
text: "no_account".tr.toUpperCase(),
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: _buildAppBar(context),
body: GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: Container(
width: double.maxFinite,
padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v),
child: Column(children: [
SizedBox(height: 36.v),
Text("user_authorization".tr, style: theme.textTheme.headlineSmall),
SizedBox(height: 60.v),
Text("email".tr, style: theme.textTheme.bodyLarge),
SizedBox(height: 3.v),
BlocSelector<AuthBloc, AuthState, TextEditingController?>(
selector: (state) => state.emailController,
builder: (context, emailController) {
return CustomTextFormField(controller: emailController, textInputAction: TextInputAction.done);
}),
SizedBox(height: 22.v),
Text("password".tr, style: theme.textTheme.bodyLarge),
SizedBox(height: 5.v),
BlocSelector<AuthBloc, AuthState, TextEditingController?>(
selector: (state) => state.passwordController,
builder: (context, passwordController) {
return CustomTextFormField(
obscureText: true,
controller: passwordController,
);
}),
SizedBox(height: 50.v),
NextStepButton(
text: "no_account".tr.toUpperCase(),
margin: EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
onPressed: onTapNoAccount(context),
),
/*NextStepButton(
text: "Тест дневника".toUpperCase(),
margin:
EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
onPressed: onTapNoAccount(context)),
onPressed: onTapDiaryTest(context)),
NextStepButton(
text: "authentificate".tr.toUpperCase(),
margin:
EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
onPressed: onTapAuth(state, context)),
]),
));
});
onPressed: onTapProfileTest(context)),*/
BlocBuilder<AuthBloc, AuthState>(builder: (context, state) {
return NextStepButton(
text: "authentificate".tr.toUpperCase(),
margin: EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
onPressed: onTapAuth(state, context));
}),
]))));
}
onTapAuth(AuthState state, BuildContext context) {
return () async {
List<String> errors = validateFields(
state.emailController!.text, state.passwordController!.text);
List<String> errors = validateFields(state.emailController!.text, state.passwordController!.text);
if (errors.isEmpty) {
final Map<String, dynamic> credentials = {
'email': state.emailController!.text,
'password': state.passwordController!.text,
};
final response = await http.post(
Uri.parse(dotenv.env['SERVER_ROOT']! + '/login'),
headers: {'Content-Type': 'application/json'},
body: json.encode(credentials),
encoding: utf8);
final response = await http.post(Uri.parse(dotenv.env['SERVER_ROOT']! + '/login'),
headers: {'Content-Type': 'application/json'}, body: json.encode(credentials), encoding: utf8);
print(response.statusCode);
if (response.statusCode == 200) {
var res = await jsonDecode(response.body);
final box = await Hive.openBox('token');
final token = res['token'];
box.put('token', token);
Navigator.pushNamed(
context,
AppRoutes.myProfile,
);
GoRouter.of(context).go('/my-profile-screen');
} else {
showDialog(
context: context,
@@ -134,34 +148,37 @@ class AuthScreen extends StatelessWidget {
onTapOk(BuildContext context) {
return () {
Navigator.pop(context);
GoRouter.of(context).pop(context);
};
}
onTapDiaryTest(BuildContext context) {
return () {
Navigator.pushNamed(context, AppRoutes.diaryEdit);
GoRouter.of(context).goNamed('diaryScreen');
};
}
onTapProfileTest(BuildContext context) {
return () {
Navigator.pushNamed(context, AppRoutes.mySettings);
GoRouter.of(context).goNamed('mySettingsScreen');
};
}
onTapNoAccount(BuildContext context) {
return () {
Navigator.pushNamed(context, AppRoutes.languageSelectScreen);
GoRouter.of(context).goNamed('languageSelectScreen');
};
}
/// Section Widget
_buildAppBar(BuildContext context) {
return CustomAppBar(
leadingWidth: 29.h,
centerTitle: true,
title: AppbarTitleImage(imagePath: ImageConstant.imgLogoEatvio));
leadingWidth: 29.h,
centerTitle: true,
title: AppbarTitleImage(
imagePath: ImageConstant.imgLogoEatvio,
),
);
}
List<String> validateFields(String email, String password) {

View File

@@ -2,7 +2,9 @@ import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import '/core/app_export.dart';
import 'package:eatvio_app/presentation/auth/auth_screen/models/auth_model.dart';
part 'auth_event.dart';
part 'auth_state.dart';
/// A bloc that manages the state of a Auth according to the event that is dispatched to it.
@@ -15,8 +17,11 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
AuthInitialEvent event,
Emitter<AuthState> emit,
) async {
emit(state.copyWith(
emit(
state.copyWith(
emailController: TextEditingController(),
passwordController: TextEditingController()));
passwordController: TextEditingController(),
),
);
}
}

View File

@@ -1,13 +1,17 @@
import 'package:eatvio_app/app_router.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:hive/hive.dart';
import 'package:http/http.dart' as http;
import 'package:go_router/go_router.dart';
import 'dart:convert';
import '/core/app_export.dart';
import 'package:eatvio_app/presentation/chat/chat_screen/models/chat_model.dart';
import 'package:eatvio_app/presentation/chat/chat_screen/models/chat_message_model.dart';
part 'chat_event.dart';
part 'chat_state.dart';
/// A bloc that manages the state of a Chat according to the event that is dispatched to it.
@@ -24,9 +28,7 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
final box = await Hive.openBox('token');
final token = box.get('token');
if (token == null) {
NavigatorService.popAndPushNamed(
AppRoutes.authScreen,
);
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final String? chatID = state.chatModelObj?.chatID;
final response = await http.get(
@@ -37,17 +39,12 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
if (data is Map<String, dynamic> &&
data.containsKey('chatRoom') &&
data['chatRoom'].containsKey('messages')) {
if (data is Map<String, dynamic> && data.containsKey('chatRoom') && data['chatRoom'].containsKey('messages')) {
if (state is ChatLoadedState) {
var currentState = state as ChatLoadedState;
emit(currentState.copyWith(
chatModelObj: currentState.chatModelObj,
messages: currentState.messages));
emit(currentState.copyWith(chatModelObj: currentState.chatModelObj, messages: currentState.messages));
} else {
emit(ChatLoadedState(
messages: fillMessages(data['chatRoom']['messages'])));
emit(ChatLoadedState(messages: fillMessages(data['chatRoom']['messages'])));
}
} else {
// Один или несколько ключевых полей отсутствуют, обрабатываем ошибку

View File

@@ -1,3 +1,5 @@
import 'package:go_router/go_router.dart';
import 'bloc/chat_bloc.dart';
import 'models/chat_model.dart';
import 'package:eatvio_app/core/app_export.dart';
@@ -5,14 +7,11 @@ import 'package:flutter/material.dart';
import 'package:eatvio_app/widgets/app_bar/custom_app_bar.dart';
import 'package:eatvio_app/widgets/app_bar/appbar_subtitle.dart';
import 'package:eatvio_app/widgets/app_bar/appbar_leading_image.dart';
import 'package:eatvio_app/widgets/app_bar/appbar_title_image.dart';
import 'package:eatvio_app/widgets/custom_bottom_bar.dart';
import 'package:eatvio_app/widgets/custom_text_form_field.dart';
class ChatScreen extends StatelessWidget {
const ChatScreen({Key? key}) : super(key: key);
static Widget builder(BuildContext context) {
final chatID = ModalRoute.of(context)!.settings.arguments;
static Widget builder(BuildContext context, chatID) {
return BlocProvider<ChatBloc>(
create: (context) => ChatBloc(
ChatState(chatModelObj: ChatModel(chatID: chatID.toString())))
@@ -24,9 +23,8 @@ class ChatScreen extends StatelessWidget {
return BlocBuilder<ChatBloc, ChatState>(
builder: (context, state) {
if (state is ChatInitialState) {
// Это начальное состояние, данные ещё не загружены.
return Container(
color: Colors.white,
return Scaffold(
body: Center(child: CircularProgressIndicator(),),
);
} else if (state is ChatLoadedState) {
// Данные загружены, можно строить экран.
@@ -35,8 +33,9 @@ class ChatScreen extends StatelessWidget {
// Произошла ошибка при загрузке данных.
return Center(child: Text(state.errorMessage));
} else {
// Другие состояния, если они есть.
return Container(); // Пустой контейнер для необработанных состояний.
return Scaffold(
body: Center(child: CircularProgressIndicator(),),
);
}
},
);
@@ -59,7 +58,6 @@ class ChatScreen extends StatelessWidget {
),
),
),
bottomNavigationBar: _buildTextField(context),
);
}
@@ -80,16 +78,13 @@ class ChatScreen extends StatelessWidget {
padding: EdgeInsets.only(left: 12.h),
child: Row(
children: [
AppbarTitleImage(
imagePath: ImageConstant.imgIconMoreHorizontal,
margin: EdgeInsets.only(bottom: 3.v),
),
Padding(
padding: EdgeInsets.only(
left: 12.h,
top: 2.v,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AppbarSubtitle(
text: "Валерий Леонтьев",
@@ -105,88 +100,8 @@ class ChatScreen extends StatelessWidget {
);
}
/// Section Widget
_buildTextField(BuildContext context) {
return Container(
width: double.maxFinite,
padding: EdgeInsets.symmetric(
horizontal: 7.h,
vertical: 6.v,
),
decoration: AppDecoration.outlineBlueGray,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CustomImageView(
imagePath: ImageConstant.imgAttach,
height: 29.adaptSize,
width: 29.adaptSize,
margin: EdgeInsets.only(
top: 1.v,
bottom: 6.v,
),
),
Expanded(
child: Padding(
padding: EdgeInsets.only(
left: 4.h,
bottom: 4.v,
),
child: BlocSelector<ChatBloc, ChatState, TextEditingController?>(
selector: (state) => TextEditingController(),
builder: (context, messageController) {
return CustomTextFormField(
controller: messageController,
hintText: "Напишите сообщение...",
textInputAction: TextInputAction.done,
);
},
),
),
),
CustomImageView(
imagePath: ImageConstant.imgMic,
height: 24.v,
width: 18.h,
margin: EdgeInsets.only(
left: 11.h,
top: 4.v,
bottom: 8.v,
),
)
],
),
);
}
/// Common widget
///Handling route based on bottom click actions
String getCurrentRoute(BottomBarEnum type) {
switch (type) {
case BottomBarEnum.tf:
return AppRoutes.chatScreen;
default:
return "/";
}
}
///Handling page based on route
Widget getCurrentPage(
BuildContext context,
String currentRoute,
) {
switch (currentRoute) {
case AppRoutes.chatScreen:
return ChatScreen.builder(context);
default:
return ChatScreen();
}
}
/// Navigates to the previous screen.
onTapArrowleftone(BuildContext context) {
NavigatorService.goBack();
GoRouter.of(context).pop();
}
}

View File

@@ -1,3 +1,4 @@
import 'package:eatvio_app/app_router.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
@@ -8,7 +9,9 @@ import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'dart:convert';
import '/core/app_export.dart';
import 'package:eatvio_app/presentation/chat/chat_select_screen/models/chat_select_model.dart';
import 'package:eatvio_app/presentation/chat/chat_select_screen/models/chat_entry.dart'; // ignore: must_be_immutable
import 'package:eatvio_app/presentation/chat/chat_select_screen/models/chat_entry.dart';
import 'package:go_router/go_router.dart';
part 'chat_select_event.dart';
part 'chat_select_state.dart';
@@ -47,9 +50,7 @@ class ChatSelectBloc extends Bloc<ChatSelectEvent, ChatSelectState> {
final box = await Hive.openBox('token');
final token = box.get('token');
if (token == null) {
NavigatorService.popAndPushNamed(
AppRoutes.authScreen,
);
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final response = await http.get(
Uri.parse(dotenv.env['SERVER_ROOT']! + '/chat/index'),

View File

@@ -2,19 +2,21 @@ import 'bloc/chat_select_bloc.dart';
import 'models/chat_select_model.dart';
import 'package:eatvio_app/core/app_export.dart';
import '../chat_select_screen/chat_search_view.dart';
import 'package:eatvio_app/widgets/custom_bottom_bar.dart';
import 'package:flutter/material.dart';
import 'package:eatvio_app/presentation/chat/chat_select_screen/models/chat_entry.dart'; // ignore: must_be_immutable
import 'package:eatvio_app/presentation/chat/chat_select_screen/widgets/chat_entry_widget.dart'; // ignore: must_be_immutable
class ChatSelectScreen extends StatelessWidget {
const ChatSelectScreen({Key? key}) : super(key: key);
static Widget builder(BuildContext context) {
return BlocProvider<ChatSelectBloc>(
create: (context) => ChatSelectBloc(
ChatSelectState(chatSelectModelObj: ChatSelectModel()))
..add(ChatSelectInitialEvent()),
child: ChatSelectScreen());
create: (context) => ChatSelectBloc(ChatSelectState(chatSelectModelObj: ChatSelectModel()))
..add(
ChatSelectInitialEvent(),
),
child: ChatSelectScreen(),
);
}
Widget build(BuildContext context) {
@@ -40,50 +42,49 @@ class ChatSelectScreen extends StatelessWidget {
}
_buildScreen(BuildContext context, ChatSelectLoadedState state) {
mediaQueryData = MediaQuery.of(context);
return Scaffold(
resizeToAvoidBottomInset: false,
body: SafeArea(
child: Container(
width: double.maxFinite,
decoration: AppDecoration.fillGray,
child: ListView(
children: [
Container(
margin: EdgeInsets.only(
left: 16.h,
right: 20.h,
bottom: 10.h,
),
padding: EdgeInsets.all(12.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CustomSearchView(
controller: state.searchController,
hintText: "Поиск",
),
SizedBox(height: 20.v),
_buildChatList(context, state),
],
),
)
resizeToAvoidBottomInset: false,
body: Container(
width: double.maxFinite,
decoration: AppDecoration.fillGray,
child: ListView(
children: [
SizedBox(height: 30.v),
Container(
margin: EdgeInsets.only(
left: 16.h,
right: 20.h,
bottom: 10.h,
),
padding: EdgeInsets.all(12.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
),
), bottomNavigationBar: _buildBottomBar(context));
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CustomSearchView(
controller: state.searchController,
hintText: "Поиск",
),
SizedBox(height: 20.v),
_buildChatList(context, state),
],
),
)
],
),
),
);
}
}
@@ -104,7 +105,3 @@ _buildChatList(BuildContext context, ChatSelectLoadedState state) {
},
);
}
_buildBottomBar(BuildContext context) {
return CustomBottomBar(onChanged: (BottomBarEnum type) {});
}

View File

@@ -5,6 +5,7 @@ import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:eatvio_app/core/app_export.dart';
import 'package:eatvio_app/presentation/chat/chat_select_screen/models/chat_entry.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:go_router/go_router.dart';
class ChatEntryWidget extends StatelessWidget {
ChatEntryWidget(this.chatEntryObj, {Key? key})
@@ -17,10 +18,9 @@ class ChatEntryWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => Navigator.pushNamed(
context,
AppRoutes.chatScreen,
arguments: chatEntryObj.id.toString(),
onTap: () => GoRouter.of(context).goNamed(
'chatScreen',
extra: chatEntryObj.id.toString(),
),
child: Container(
padding: EdgeInsets.all(8.h),
@@ -28,23 +28,23 @@ class ChatEntryWidget extends StatelessWidget {
borderRadius: BorderRadiusStyle.circleBorder8,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CachedNetworkImage(
imageUrl: dotenv.env['STORAGE_URL']! + chatEntryObj.userImage!,
imageBuilder: (context, imageProvider) => CircleAvatar(
backgroundImage: imageProvider,
radius: 25,
),
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => CircleAvatar(
child: Icon(Icons.person),
backgroundColor: Colors.white,
radius: 25,
),
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CachedNetworkImage(
imageUrl: dotenv.env['STORAGE_URL']! + chatEntryObj.userImage!,
imageBuilder: (context, imageProvider) => CircleAvatar(
backgroundImage: imageProvider,
radius: 25,
),
/*CustomImageView(
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => CircleAvatar(
child: Icon(Icons.person),
backgroundColor: Colors.white,
radius: 25,
),
),
/*CustomImageView(
imagePath:
dotenv.env['STORAGE_URL']! + chatEntryObj.userImage!,
height: 60.adaptSize,
@@ -53,57 +53,55 @@ class ChatEntryWidget extends StatelessWidget {
30.h,
),
),*/
Expanded(
child: Padding(
padding: EdgeInsets.only(
left: 12.h,
top: 2.v,
bottom: 11.v,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 227.h,
child: Row(
children: [
Text(
chatEntryObj.username!.length <= 15
? chatEntryObj.username!
: chatEntryObj.username!.substring(0, 15) +
'...',
style: theme.textTheme.titleMedium,
),
Spacer(),
CustomImageView(
imagePath: ImageConstant.imageNotFound,
height: 9.v,
width: 16.h,
margin: EdgeInsets.only(
top: 4.v,
bottom: 6.v,
),
),
Padding(
padding: EdgeInsets.only(left: 6.h),
child: Text(
chatEntryObj.dateText!,
style: theme.textTheme.bodyMedium,
),
)
],
),
),
SizedBox(height: 6.v),
Text(
chatEntryObj.greetingText!,
style: CustomTextStyles.bodyMediumBluegray800,
)
],
),
Expanded(
child: Padding(
padding: EdgeInsets.only(
left: 12.h,
top: 2.v,
bottom: 11.v,
),
)
]),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 227.h,
child: Row(
children: [
Text(
chatEntryObj.username!.length <= 15 ? chatEntryObj.username! : chatEntryObj.username!.substring(0, 15) + '...',
style: theme.textTheme.titleMedium,
),
Spacer(),
CustomImageView(
imagePath: ImageConstant.imageNotFound,
height: 9.v,
width: 16.h,
margin: EdgeInsets.only(
top: 4.v,
bottom: 6.v,
),
),
Padding(
padding: EdgeInsets.only(left: 6.h),
child: Text(
chatEntryObj.dateText!,
style: theme.textTheme.bodyMedium,
),
)
],
),
),
SizedBox(height: 6.v),
Text(
chatEntryObj.greetingText!,
style: CustomTextStyles.bodyMediumBluegray800,
)
],
),
),
)
],
),
),
);
}

View File

@@ -1,5 +1,6 @@
// ignore_for_file: unused_local_variable
import 'bloc/diary_edit_bloc.dart';
import 'models/diary_edit_model.dart';
import 'package:flutter/material.dart';
@@ -13,22 +14,15 @@ import 'package:eatvio_app/widgets/custom_edit_text_form_field.dart';
import 'package:eatvio_app/widgets/custom_edit_drop_down.dart';
import 'package:eatvio_app/widgets/custom_outlined_button.dart';
import 'package:eatvio_app/widgets/custom_scale_element.dart';
import 'package:eatvio_app/widgets/custom_bottom_bar.dart';
import 'package:carousel_slider/carousel_slider.dart';
// ignore: must_be_immutable
class DiaryEditScreen extends StatelessWidget {
final void Function() onInitialize;
DiaryEditScreen({Key? key, required this.onInitialize}) : super(key: key);
DiaryEditScreen({Key? key}) : super(key: key);
static Widget builder(BuildContext context) {
return BlocProvider<DiaryEditBloc>(
create: (context) =>
DiaryEditBloc(DiaryEditState(diaryEditModelObj: DiaryEditModel()))
..add(DiaryEditInitialEvent()),
child: DiaryEditScreen(
onInitialize: () {},
));
create: (context) => DiaryEditBloc(DiaryEditState(diaryEditModelObj: DiaryEditModel()))..add(DiaryEditInitialEvent()), child: DiaryEditScreen());
}
Widget build(BuildContext context) {
@@ -53,56 +47,52 @@ class DiaryEditScreen extends StatelessWidget {
);
}
Widget _buildDiaryEditScreen(
BuildContext context, DiaryEditLoadedState state) {
mediaQueryData = MediaQuery.of(context);
Widget _buildDiaryEditScreen(BuildContext context, DiaryEditLoadedState state) {
return RefreshIndicator(
onRefresh: refreshPage(context),
child: Stack(
alignment: Alignment.bottomCenter,
children: [
FormBuilder(
child: Scaffold(
backgroundColor: Color.fromARGB(255, 240, 242, 245),
resizeToAvoidBottomInset: true,
body: SafeArea(
child: SizedBox(
width: double.maxFinite,
child: Column(
children: [
Expanded(
child: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.only(bottom: 5.v),
child: Column(
children: [
_buildHeaderRow(context, state),
SizedBox(height: 30.v),
_buildWakupRetire(context, state),
SizedBox(height: 30.v),
_buildMeal(context, state),
SizedBox(height: 20.v),
_buildMood(context, state),
SizedBox(height: 30.v),
_buildNutritionChart(context, state),
SizedBox(height: 30.v),
_buildComment(context, state),
],
),
appBar: AppBar(
toolbarHeight: 0,
automaticallyImplyLeading: false,
),
backgroundColor: Color.fromARGB(255, 240, 242, 245),
resizeToAvoidBottomInset: true,
body: SizedBox(
width: double.maxFinite,
child: Column(
children: [
Expanded(
child: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.only(bottom: 5.v),
child: Column(
children: [
_buildHeaderRow(context, state),
SizedBox(height: 30.v),
_buildWakupRetire(context, state),
SizedBox(height: 30.v),
_buildMeal(context, state),
SizedBox(height: 20.v),
_buildMood(context, state),
SizedBox(height: 30.v),
_buildNutritionChart(context, state),
SizedBox(height: 30.v),
_buildComment(context, state),
],
),
),
),
],
),
),
],
),
),
bottomNavigationBar: _buildBottomBar(context) ),
),
Positioned(
left: 16.h,
right: 16.h,
bottom: 50.v, // Adjusted position to lift the button higher
child: _buildEndDay(context),
),
_buildEndDay(context),
],
),
);
@@ -114,7 +104,7 @@ class DiaryEditScreen extends StatelessWidget {
margin: EdgeInsets.only(
left: 16.h,
right: 16.h,
bottom: 41.v,
bottom: 16.v,
),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
decoration: BoxDecoration(
@@ -153,10 +143,8 @@ class DiaryEditScreen extends StatelessWidget {
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment: MainAxisAlignment.center,
children: [
CustomImageView(
imagePath: ImageConstant.imgArrowLeft, onTap: onTapBack()),
Padding(
padding: EdgeInsets.only(top: 1.v),
child: Column(
@@ -181,16 +169,6 @@ class DiaryEditScreen extends StatelessWidget {
);
}
onTapBack() {
return () {
NavigatorService.goBack();
};
}
_buildBottomBar(BuildContext context) {
return CustomBottomBar(onChanged: (BottomBarEnum type) {});
}
/// Section Widget
_buildAddProduct(BuildContext context, DiaryEditLoadedState state) {
return CustomOutlinedButton(
@@ -259,9 +237,7 @@ class DiaryEditScreen extends StatelessWidget {
borderRadius: BorderRadiusStyle.roundedBorder5,
),
child: Center(
child: CircularProgressIndicator(
color: appTheme
.blueGray300), // Замените на свой прелоадер при необходимости
child: CircularProgressIndicator(color: appTheme.blueGray300), // Замените на свой прелоадер при необходимости
),
),
],
@@ -285,8 +261,7 @@ class DiaryEditScreen extends StatelessWidget {
_buildMeal(BuildContext context, DiaryEditLoadedState state) {
final digitsOnly = FilteringTextInputFormatter.digitsOnly;
List<String> _options =
state.autocompleteList!.map((dynamic item) => item as String).toList();
List<String> _options = state.autocompleteList!.map((dynamic item) => item as String).toList();
//print(_options);
FocusNode focusNodeName = FocusNode();
return Container(
@@ -331,9 +306,7 @@ class DiaryEditScreen extends StatelessWidget {
onTap: () => _selectMealTime(context, state),
child: AbsorbPointer(
child: CustomTextFormField(
hintText: state.mealTimeController
?.format(context)
.toString(),
hintText: state.mealTimeController?.format(context).toString(),
autofocus: false,
),
),
@@ -347,8 +320,7 @@ class DiaryEditScreen extends StatelessWidget {
SizedBox(height: 20.v),
Padding(
padding: EdgeInsets.symmetric(horizontal: 10.h),
child: BlocSelector<DiaryEditBloc, DiaryEditState,
DiaryEditModel?>(
child: BlocSelector<DiaryEditBloc, DiaryEditState, DiaryEditModel?>(
selector: (state) => state.diaryEditModelObj,
builder: (context, diaryEditModelObj) {
return CustomDropDown(
@@ -440,8 +412,7 @@ class DiaryEditScreen extends StatelessWidget {
),
Padding(
padding: EdgeInsets.only(left: 30.h),
child: BlocSelector<DiaryEditBloc, DiaryEditState,
DiaryEditModel?>(
child: BlocSelector<DiaryEditBloc, DiaryEditState, DiaryEditModel?>(
selector: (state) => state.diaryEditModelObj,
builder: (context, diaryEditModelObj) {
return CustomDropDown(
@@ -459,9 +430,7 @@ class DiaryEditScreen extends StatelessWidget {
SizedBox(height: 30.v),
_buildAddProduct(context, state),
SizedBox(height: 30.v),
...state.portions!
.map((portion) => _buildProduct(context, portion))
.toList(),
...state.portions!.map((portion) => _buildProduct(context, portion)).toList(),
SizedBox(height: 30.v),
/*...state.photos != null
? state.photos!.isEmpty
@@ -574,14 +543,8 @@ class DiaryEditScreen extends StatelessWidget {
child: Text('Отмена'),
),
TextButton(
onPressed: triggerModeration(
context,
state.productNameController!.text,
state.caloriesController!.text,
state.proteinsController!.text,
state.fatsController!.text,
state.carbsController!.text,
dialogContext),
onPressed: triggerModeration(context, state.productNameController!.text, state.caloriesController!.text, state.proteinsController!.text,
state.fatsController!.text, state.carbsController!.text, dialogContext),
child: Text('Отправить'),
),
],
@@ -590,30 +553,19 @@ class DiaryEditScreen extends StatelessWidget {
);
}
triggerModeration(
BuildContext context,
String name,
String energyOn100gr,
String proteinsOn100gr,
String fatsOn100gr,
String carbohydratesOn100gr,
triggerModeration(BuildContext context, String name, String energyOn100gr, String proteinsOn100gr, String fatsOn100gr, String carbohydratesOn100gr,
BuildContext dialogContext) {
return () {
// Call function to send moderation with the entered values
context.read<DiaryEditBloc>().add(SendModeration(
name: name,
energyOn100gr: energyOn100gr,
proteinsOn100gr: proteinsOn100gr,
fatsOn100gr: fatsOn100gr,
carbohydratesOn100gr: carbohydratesOn100gr));
name: name, energyOn100gr: energyOn100gr, proteinsOn100gr: proteinsOn100gr, fatsOn100gr: fatsOn100gr, carbohydratesOn100gr: carbohydratesOn100gr));
Navigator.of(dialogContext).pop(); // Dismiss the dialog
};
}
popDialog(BuildContext dialogContext) {
return () {
Navigator.of(dialogContext)
.pop(); // Dismiss the dialog on Cancel button press
Navigator.of(dialogContext).pop(); // Dismiss the dialog on Cancel button press
};
}
@@ -623,8 +575,7 @@ class DiaryEditScreen extends StatelessWidget {
};
}
selectProduct(AutocompleteOnSelected<String> onSelected, String option,
DiaryEditLoadedState state) {
selectProduct(AutocompleteOnSelected<String> onSelected, String option, DiaryEditLoadedState state) {
state.productNameController?.text = option;
return () {
onSelected(option);
@@ -652,9 +603,7 @@ class DiaryEditScreen extends StatelessWidget {
changeMeal(BuildContext context, DiaryEditLoadedState state) {
return (value) {
context
.read<DiaryEditBloc>()
.add(ChangeMeal(meals: state.meals, value: value));
context.read<DiaryEditBloc>().add(ChangeMeal(meals: state.meals, value: value));
};
}
@@ -671,8 +620,7 @@ class DiaryEditScreen extends StatelessWidget {
return CarouselSlider(
options: CarouselOptions(height: 340.0),
items: state.photos!.map<Widget>((photo) {
return _buildPhoto(
context, photo); // Assuming photos are stored as strings
return _buildPhoto(context, photo); // Assuming photos are stored as strings
}).toList(),
);
}
@@ -698,9 +646,7 @@ class DiaryEditScreen extends StatelessWidget {
top: 5.v,
),
child: Text(
portion['product_name'].length > 8
? portion['product_name'].substring(0, 8)
: portion['product_name'],
portion['product_name'].length > 8 ? portion['product_name'].substring(0, 8) : portion['product_name'],
style: CustomTextStyles.titleMedium16,
)),
Padding(
@@ -710,9 +656,7 @@ class DiaryEditScreen extends StatelessWidget {
bottom: 3.v,
),
child: Text(
portion['quantity'].toString() +
' ' +
portion['portion'].toString().tr,
portion['quantity'].toString() + ' ' + portion['portion'].toString().tr,
),
),
Spacer(),
@@ -772,15 +716,12 @@ class DiaryEditScreen extends StatelessWidget {
removePhoto(BuildContext context, Map<String, dynamic> photo) {
return () {
context
.read<DiaryEditBloc>()
.add(RemoveImageEvent(photo['id'].toString(), photo['path']));
context.read<DiaryEditBloc>().add(RemoveImageEvent(photo['id'].toString(), photo['path']));
};
}
_buildNutritionChart(BuildContext context, DiaryEditLoadedState state) {
final List<int> recommendedValues = state
.recommendedChartValues; // Рекомендуемые значения: калории, жиры, белки, углеводы
final List<int> recommendedValues = state.recommendedChartValues; // Рекомендуемые значения: калории, жиры, белки, углеводы
final List<int> consumedValues = state.consumedChartValues; //
return Container(
margin: EdgeInsets.symmetric(horizontal: 16.h),
@@ -842,37 +783,25 @@ class DiaryEditScreen extends StatelessWidget {
BarChartGroupData(x: 0, barRods: [
BarChartRodData(
toY: consumedValues[0] / recommendedValues[0] * 100,
color: checkConsumedValues(
[recommendedValues[0]], [consumedValues[1]])
? Colors.red
: Colors.blue,
color: checkConsumedValues([recommendedValues[0]], [consumedValues[1]]) ? Colors.red : Colors.blue,
)
]),
BarChartGroupData(x: 1, barRods: [
BarChartRodData(
toY: consumedValues[1] / recommendedValues[1] * 100,
color: checkConsumedValues(
[recommendedValues[1]], [consumedValues[1]])
? Colors.red
: Colors.blue,
color: checkConsumedValues([recommendedValues[1]], [consumedValues[1]]) ? Colors.red : Colors.blue,
)
]),
BarChartGroupData(x: 2, barRods: [
BarChartRodData(
toY: consumedValues[2] / recommendedValues[2] * 100,
color: checkConsumedValues(
[recommendedValues[2]], [consumedValues[2]])
? Colors.red
: Colors.blue,
color: checkConsumedValues([recommendedValues[2]], [consumedValues[2]]) ? Colors.red : Colors.blue,
)
]),
BarChartGroupData(x: 3, barRods: [
BarChartRodData(
toY: consumedValues[3] / recommendedValues[3] * 100,
color: checkConsumedValues(
[recommendedValues[3]], [consumedValues[3]])
? Colors.red
: Colors.blue,
color: checkConsumedValues([recommendedValues[3]], [consumedValues[3]]) ? Colors.red : Colors.blue,
)
]),
],
@@ -1062,9 +991,7 @@ class DiaryEditScreen extends StatelessWidget {
r"Подъем \ отход ко сну",
style: CustomTextStyles.bodyMediumBlack900,
),
Icon(state.wakeupExpanded
? Icons.arrow_drop_up
: Icons.arrow_drop_down),
Icon(state.wakeupExpanded ? Icons.arrow_drop_up : Icons.arrow_drop_down),
],
),
),
@@ -1078,8 +1005,7 @@ class DiaryEditScreen extends StatelessWidget {
onTap: () => _selectWakeupTime(context, state),
child: AbsorbPointer(
child: CustomTextFormField(
hintText:
state.wakeupController?.format(context).toString(),
hintText: state.wakeupController?.format(context).toString(),
prefix: Icon(Icons.sunny),
autofocus: false,
),
@@ -1090,8 +1016,7 @@ class DiaryEditScreen extends StatelessWidget {
onTap: () => _selectRetireTime(context, state),
child: AbsorbPointer(
child: CustomTextFormField(
hintText:
state.retireController?.format(context).toString(),
hintText: state.retireController?.format(context).toString(),
prefix: Icon(Icons.bed),
autofocus: false,
),
@@ -1138,8 +1063,7 @@ class DiaryEditScreen extends StatelessWidget {
};
}
Function() sendFinishDayData(
BuildContext dialogContext, BuildContext context) {
Function() sendFinishDayData(BuildContext dialogContext, BuildContext context) {
return () {
closeDialog(dialogContext);
context.read<DiaryEditBloc>().add(FinishDay());
@@ -1181,9 +1105,7 @@ class DiaryEditScreen extends StatelessWidget {
"Ваше настроение после приема пищи",
style: CustomTextStyles.bodyMediumBlack900,
),
Icon(state.moodExpanded
? Icons.arrow_drop_up
: Icons.arrow_drop_down),
Icon(state.moodExpanded ? Icons.arrow_drop_up : Icons.arrow_drop_down),
],
),
),
@@ -1241,8 +1163,7 @@ class DiaryEditScreen extends StatelessWidget {
};
}
Future<void> _selectWakeupTime(
BuildContext context, DiaryEditLoadedState state) async {
Future<void> _selectWakeupTime(BuildContext context, DiaryEditLoadedState state) async {
final TimeOfDay? pickedTime = await showTimePicker(
context: context,
initialTime: state.wakeupController ?? TimeOfDay.now(),
@@ -1253,8 +1174,7 @@ class DiaryEditScreen extends StatelessWidget {
}
}
Future<void> _selectRetireTime(
BuildContext context, DiaryEditLoadedState state) async {
Future<void> _selectRetireTime(BuildContext context, DiaryEditLoadedState state) async {
final TimeOfDay? pickedTime = await showTimePicker(
context: context,
initialTime: state.retireController ?? TimeOfDay.now(),
@@ -1265,8 +1185,7 @@ class DiaryEditScreen extends StatelessWidget {
}
}
Future<void> _selectMealTime(
BuildContext context, DiaryEditLoadedState state) async {
Future<void> _selectMealTime(BuildContext context, DiaryEditLoadedState state) async {
final TimeOfDay? pickedTime = await showTimePicker(
context: context,
initialTime: state.mealTimeController ?? TimeOfDay.now(),
@@ -1278,8 +1197,7 @@ class DiaryEditScreen extends StatelessWidget {
}
}
bool checkConsumedValues(
List<int> recommendedValues, List<int> consumedValues) {
bool checkConsumedValues(List<int> recommendedValues, List<int> consumedValues) {
for (int i = 0; i < recommendedValues.length; i++) {
if (consumedValues[i] > recommendedValues[i]) {
// Если хотя бы одно потребленное значение превышает рекомендованное

View File

@@ -1,13 +1,16 @@
import 'dart:async';
import 'package:eatvio_app/app_router.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:go_router/go_router.dart';
import 'package:hive/hive.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import '/core/app_export.dart';
import 'package:eatvio_app/presentation/diets/diet_create_screen/models/diet_create_model.dart';
part 'diet_create_event.dart';
part 'diet_create_state.dart';
@@ -25,9 +28,7 @@ class DietCreateBloc extends Bloc<DietCreateEvent, DietCreateState> {
final box = await Hive.openBox('token');
final token = box.get('token');
if (token == null) {
NavigatorService.popAndPushNamed(
AppRoutes.authScreen,
);
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
if (state is DietCreateLoadedState) {
var currentState = state as DietCreateLoadedState;
@@ -55,7 +56,7 @@ class DietCreateBloc extends Bloc<DietCreateEvent, DietCreateState> {
);
if (response.statusCode == 200) {
NavigatorService.pushNamed(AppRoutes.dietSelectScreen);
AppRouter.navigatorKey.currentContext?.goNamed('dietSelectScreen');
} else {
print(
'Failed to create blog post. Status Code: ${response.statusCode} ${response.body}');

View File

@@ -2,16 +2,14 @@ import 'bloc/diet_create_bloc.dart';
import 'models/diet_create_model.dart';
import 'package:eatvio_app/core/app_export.dart';
import 'package:flutter/material.dart';
import 'package:eatvio_app/widgets/custom_bottom_bar.dart';
class DietCreateScreen extends StatelessWidget {
const DietCreateScreen({Key? key}) : super(key: key);
static Widget builder(BuildContext context) {
final dietID = ModalRoute.of(context)!.settings.arguments;
return BlocProvider<DietCreateBloc>(
create: (context) => DietCreateBloc(DietCreateState(
dietModelObj: DietCreateModel(dietID: dietID.toString())))
..add(DietCreateInitialEvent()),
create: (context) => DietCreateBloc(DietCreateState(dietModelObj: DietCreateModel(dietID: dietID.toString())))..add(DietCreateInitialEvent()),
child: DietCreateScreen());
}
@@ -39,58 +37,55 @@ class DietCreateScreen extends StatelessWidget {
_buildDietCreate(BuildContext context, DietCreateLoadedState state) {
mediaQueryData = MediaQuery.of(context);
return Scaffold(
resizeToAvoidBottomInset: false,
body: SafeArea(
child: Container(
width: double.maxFinite,
decoration: AppDecoration.fillGray,
child: ListView(children: [
Container(
margin: EdgeInsets.only(
left: 16.h,
right: 20.h,
bottom: 10.h,
return SafeArea(
child: Scaffold(
resizeToAvoidBottomInset: false,
body: Container(
width: double.maxFinite,
decoration: AppDecoration.fillGray,
child: ListView(children: [
SizedBox(height: 30.v),
Container(
margin: EdgeInsets.only(
left: 16.h,
right: 20.h,
bottom: 10.h,
),
padding: EdgeInsets.all(12.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
padding: EdgeInsets.all(12.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: Column(mainAxisSize: MainAxisSize.min, children: [
SizedBox(height: 16.0),
TextFormField(
controller: state.name,
decoration: InputDecoration(labelText: 'Заголовок'),
),
SizedBox(height: 16.0),
TextFormField(
controller: state.details,
decoration: InputDecoration(labelText: 'Описание'),
),
SizedBox(height: 16.0),
// Здесь нужно добавить виджет для загрузки картинки
ElevatedButton(
onPressed: () =>
context.read<DietCreateBloc>().add(CreateDiet()),
child: Text('Создать'),
)
]),
],
),
child: Column(mainAxisSize: MainAxisSize.min, children: [
SizedBox(height: 16.0),
TextFormField(
controller: state.name,
decoration: InputDecoration(labelText: 'Заголовок'),
),
SizedBox(height: 16.0),
TextFormField(
controller: state.details,
decoration: InputDecoration(labelText: 'Описание'),
),
SizedBox(height: 16.0),
// Здесь нужно добавить виджет для загрузки картинки
ElevatedButton(
onPressed: () => context.read<DietCreateBloc>().add(CreateDiet()),
child: Text('Создать'),
)
]),
),
), bottomNavigationBar: _buildBottomBar(context));
)
]),
),
),
);
}
}
_buildBottomBar(BuildContext context) {
return CustomBottomBar(onChanged: (BottomBarEnum type) {});
}

View File

@@ -1,14 +1,18 @@
import 'dart:async';
import 'package:eatvio_app/app_router.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:go_router/go_router.dart';
import 'package:hive/hive.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import '/core/app_export.dart';
import 'package:eatvio_app/presentation/diets/diet_edit_screen/models/diet_edit_model.dart';
part 'diet_edit_event.dart';
part 'diet_edit_state.dart';
/// A bloc that manages the state of a DietEdit according to the event that is dispatched to it.
@@ -26,9 +30,7 @@ class DietEditBloc extends Bloc<DietEditEvent, DietEditState> {
final box = await Hive.openBox('token');
final token = box.get('token');
if (token == null) {
NavigatorService.popAndPushNamed(
AppRoutes.authScreen,
);
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final String? dietID = state.dietModelObj?.dietID;
final response = await http.get(
@@ -74,15 +76,15 @@ class DietEditBloc extends Bloc<DietEditEvent, DietEditState> {
var currentState = state as DietEditLoadedState;
int duration = int.parse(currentState.duration!.text) * 7;
print(duration);
var response = await sendDiet(currentState.diet, currentState.name?.text,
currentState.details?.text, duration.toString());
var response = await sendDiet(currentState.diet, currentState.name?.text, currentState.details?.text, duration.toString());
if (response.statusCode == 200) {
NavigatorService.pushNamed(AppRoutes.dietScreen,
arguments: currentState.diet?['id'].toString());
AppRouter.navigatorKey.currentContext?.goNamed(
'dietScreen',
extra: currentState.diet?['id'].toString(),
);
} else {
print(
'Failed to edit blog post. Status Code: ${response.statusCode} ${response.body}');
print('Failed to edit blog post. Status Code: ${response.statusCode} ${response.body}');
}
}
}

View File

@@ -2,18 +2,16 @@ import 'bloc/diet_edit_bloc.dart';
import 'models/diet_edit_model.dart';
import 'package:eatvio_app/core/app_export.dart';
import 'package:flutter/material.dart';
import 'package:eatvio_app/widgets/custom_bottom_bar.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
class DietEditScreen extends StatelessWidget {
const DietEditScreen({Key? key}) : super(key: key);
static Widget builder(BuildContext context) {
final dietID = ModalRoute.of(context)!.settings.arguments;
return BlocProvider<DietEditBloc>(
create: (context) => DietEditBloc(DietEditState(
dietModelObj: DietEditModel(dietID: dietID.toString())))
..add(DietEditInitialEvent()),
create: (context) => DietEditBloc(DietEditState(dietModelObj: DietEditModel(dietID: dietID.toString())))..add(DietEditInitialEvent()),
child: DietEditScreen());
}
@@ -41,42 +39,44 @@ class DietEditScreen extends StatelessWidget {
_buildDietEdit(BuildContext context, DietEditLoadedState state) {
mediaQueryData = MediaQuery.of(context);
return Scaffold(
resizeToAvoidBottomInset: false,
body: SafeArea(
child: Container(
width: double.maxFinite,
decoration: AppDecoration.fillGray,
child: ListView(children: [
Container(
margin: EdgeInsets.only(
left: 16.h,
right: 20.h,
bottom: 10.h,
),
padding: EdgeInsets.all(12.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: Column(mainAxisSize: MainAxisSize.min, children: [
return SafeArea(
child: Scaffold(
resizeToAvoidBottomInset: false,
body: Container(
width: double.maxFinite,
decoration: AppDecoration.fillGray,
child: ListView(
children: [
SizedBox(height: 30.v),
Container(
margin: EdgeInsets.only(
left: 16.h,
right: 20.h,
bottom: 10.h,
),
padding: EdgeInsets.all(12.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CachedNetworkImage(
imageUrl: dotenv.env['STORAGE_URL']! +
(state.diet?['image'] ?? ''),
imageUrl: dotenv.env['STORAGE_URL']! + (state.diet?['image'] ?? ''),
imageBuilder: (context, imageProvider) => CircleAvatar(
backgroundImage: imageProvider,
radius: 25,
),
placeholder: (context, url) =>
CircularProgressIndicator(),
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => CircleAvatar(
child: Icon(Icons.person),
backgroundColor: Colors.white,
@@ -96,18 +96,16 @@ class DietEditScreen extends StatelessWidget {
SizedBox(height: 16.0),
// Здесь нужно добавить виджет для загрузки картинки
ElevatedButton(
onPressed: () =>
context.read<DietEditBloc>().add(EditDiet()),
onPressed: () => context.read<DietEditBloc>().add(EditDiet()),
child: Text('Сохранить'),
)
]),
)
]),
),
), bottomNavigationBar: _buildBottomBar(context));
],
),
)
],
),
),
),
);
}
}
_buildBottomBar(BuildContext context) {
return CustomBottomBar(onChanged: (BottomBarEnum type) {});
}

View File

@@ -1,6 +1,7 @@
//import 'package:eatvio_app/presentation/auth/auth_screen/auth_screen.dart';
import 'dart:async';
import 'package:eatvio_app/app_router.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import '/core/app_export.dart';
@@ -10,6 +11,7 @@ import 'dart:convert';
import 'package:hive/hive.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:image_picker/image_picker.dart';
import 'package:go_router/go_router.dart';
part 'diet_select_event.dart';
part 'diet_select_state.dart';
@@ -28,9 +30,7 @@ class DietSelectBloc extends Bloc<DietSelectEvent, DietSelectState> {
final box = await Hive.openBox('token');
final token = box.get('token');
if (token == null) {
NavigatorService.popAndPushNamed(
AppRoutes.authScreen,
); // Если токен есть, запускаем приложение
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final response = await http.get(
Uri.parse(dotenv.env['SERVER_ROOT']! + '/diets/index'),

View File

@@ -1,10 +1,11 @@
import 'package:go_router/go_router.dart';
import 'bloc/diet_select_bloc.dart';
import 'models/diet_select_model.dart';
import 'package:eatvio_app/core/app_export.dart';
import 'package:eatvio_app/widgets/app_bar/appbar_subtitle.dart';
import 'package:eatvio_app/widgets/app_bar/appbar_leading_image.dart';
import 'package:eatvio_app/widgets/app_bar/custom_app_bar.dart';
import 'package:eatvio_app/widgets/custom_bottom_bar.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter/material.dart';
@@ -12,10 +13,7 @@ import 'package:flutter/material.dart';
class DietSelectScreen extends StatelessWidget {
static Widget builder(BuildContext context) {
return BlocProvider<DietSelectBloc>(
create: (context) => DietSelectBloc(
DietSelectState(myProfileModelObj: DietSelectModel()))
..add(DietSelectInitialEvent()),
child: DietSelectScreen());
create: (context) => DietSelectBloc(DietSelectState(myProfileModelObj: DietSelectModel()))..add(DietSelectInitialEvent()), child: DietSelectScreen());
}
Widget build(BuildContext context) {
@@ -23,87 +21,89 @@ class DietSelectScreen extends StatelessWidget {
builder: (context, state) {
if (state is DietSelectInitialState) {
// Это начальное состояние, данные ещё не загружены.
return CircularProgressIndicator(
color: Colors.white,
backgroundColor: Colors.white,
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
} else if (state is DietSelectLoadedState) {
// Данные загружены, можно строить экран.
return _buildSetingsScreen(context, state);
} else {
// Другие состояния, если они есть.
return Container(); // Пустой контейнер для необработанных состояний.
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
},
);
}
_buildSetingsScreen(BuildContext context, DietSelectLoadedState state) {
mediaQueryData = MediaQuery.of(context);
return Scaffold(
backgroundColor: Colors.grey[200],
resizeToAvoidBottomInset: true,
body: SafeArea(
child: SingleChildScrollView(
child: Container(
width: double.maxFinite,
decoration: BoxDecoration(color: Colors.grey[200]),
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(height: 10),
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: CustomAppBar(
height: 54.v,
leadingWidth: 28.h,
leading: AppbarLeadingImage(
imagePath: ImageConstant.imgArrowLeft,
margin: EdgeInsets.only(
left: 16.h, top: 15.v, bottom: 15.v),
onTap: onTapBack(context),
return Scaffold(
appBar: AppBar(
toolbarHeight: 0,
automaticallyImplyLeading: false,
),
backgroundColor: Colors.grey[200],
resizeToAvoidBottomInset: true,
body: SingleChildScrollView(
child: Container(
width: double.maxFinite,
decoration: BoxDecoration(color: Colors.grey[200]),
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(height: 10),
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
title: Padding(
padding: EdgeInsets.only(left: 20.h),
child: Row(
children: [
AppbarSubtitle(
text: "Диеты",
margin: EdgeInsets.only(
left: 80.h, top: 12.v, bottom: 9.v),
],
),
child: CustomAppBar(
height: 54.v,
leadingWidth: 28.h,
leading: AppbarLeadingImage(
imagePath: ImageConstant.imgArrowLeft,
margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v),
onTap: () => GoRouter.of(context).pop(),
),
title: Padding(
padding: EdgeInsets.only(left: 20.h),
child: Row(
children: [
AppbarSubtitle(
text: "Диеты",
margin: EdgeInsets.only(left: 80.h, top: 12.v, bottom: 9.v),
),
Spacer(),
Padding(
padding: const EdgeInsets.only(right: 20.0),
child: GestureDetector(
onTap: () => GoRouter.of(context).goNamed('dietCreateScreen'),
child: Icon(Icons.create_outlined),
),
Spacer(),
Padding(
padding: const EdgeInsets.only(right: 20.0),
child: GestureDetector(
onTap: () => Navigator.pushNamed(
context,
AppRoutes.dietCreateScreen,
),
child: Icon(Icons.create_outlined)),
)
],
),
)
],
),
),
),
SizedBox(height: 20),
_buildDietList(context, state)
],
),
),
SizedBox(height: 20),
_buildDietList(context, state)
],
),
),
),
@@ -151,18 +151,17 @@ class DietSelectScreen extends StatelessWidget {
style: CustomTextStyles.titleMediumSemiBold,
),
subtitle: Text(post?['details'] ?? ''),
onTap: () => Navigator.pushNamed(
context,
AppRoutes.dietScreen,
arguments: post?['id'].toString(),
onTap: () => GoRouter.of(context).goNamed(
'dietScreen',
extra: post?['id'].toString(),
),
trailing: GestureDetector(
onTap: () => Navigator.pushNamed(
context,
AppRoutes.dietEditScreen,
arguments: post?['id'].toString(),
),
child: Icon(Icons.edit)),
onTap: () => GoRouter.of(context).goNamed(
'dietEditScreen',
extra: post?['id'].toString(),
),
child: Icon(Icons.edit),
),
));
},
);
@@ -174,11 +173,7 @@ class DietSelectScreen extends StatelessWidget {
};
}
_buildBottomBar(BuildContext context) {
return CustomBottomBar(onChanged: (BottomBarEnum type) {});
}
onTapArrowLeft(BuildContext context) {
NavigatorService.goBack();
GoRouter.of(context).pop();
}
}

View File

@@ -1,5 +1,6 @@
import 'dart:async';
import 'package:eatvio_app/app_router.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
@@ -8,7 +9,10 @@ import 'package:http/http.dart' as http;
import 'dart:convert';
import '/core/app_export.dart';
import 'package:eatvio_app/presentation/diets/diet_show_screen/models/diet_show_model.dart';
import 'package:go_router/go_router.dart';
part 'diet_show_event.dart';
part 'diet_show_state.dart';
/// A bloc that manages the state of a Diet according to the event that is dispatched to it.
@@ -26,9 +30,7 @@ class DietBloc extends Bloc<DietEvent, DietState> {
final box = await Hive.openBox('token');
final token = box.get('token');
if (token == null) {
NavigatorService.popAndPushNamed(
AppRoutes.authScreen,
);
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final String? dietID = state.dietModelObj?.dietID;
final response = await http.get(
@@ -42,9 +44,7 @@ class DietBloc extends Bloc<DietEvent, DietState> {
if (data is Map<String, dynamic>) {
if (state is DietLoadedState) {
var currentState = state as DietLoadedState;
emit(currentState.copyWith(
dietModelObj: currentState.dietModelObj,
diet: currentState.diet));
emit(currentState.copyWith(dietModelObj: currentState.dietModelObj, diet: currentState.diet));
} else {
emit(DietLoadedState(diet: data));
}
@@ -62,14 +62,11 @@ class DietBloc extends Bloc<DietEvent, DietState> {
}
}
FutureOr<void> _deleteEntry(
DeleteEntry event, Emitter<DietState> emit) async {
FutureOr<void> _deleteEntry(DeleteEntry event, Emitter<DietState> emit) async {
final box = await Hive.openBox('token');
final token = box.get('token');
if (token == null) {
NavigatorService.popAndPushNamed(
AppRoutes.authScreen,
);
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final int id = event.id;
final response = await http.post(
@@ -79,7 +76,7 @@ class DietBloc extends Bloc<DietEvent, DietState> {
},
);
if (response.statusCode == 200) {
NavigatorService.pushNamed(AppRoutes.dietSelectScreen);
AppRouter.navigatorKey.currentContext?.goNamed('dietSelectScreen');
} else {
// Статус ответа не равен 200, обрабатываем ошибку
emit(DietErrorState('Ошибка: ${response.statusCode}'));

View File

@@ -1,20 +1,19 @@
import 'package:go_router/go_router.dart';
import 'bloc/diet_show_bloc.dart';
import 'models/diet_show_model.dart';
import 'package:eatvio_app/core/app_export.dart';
import 'package:flutter/material.dart';
import 'package:eatvio_app/widgets/custom_bottom_bar.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
class DietScreen extends StatelessWidget {
const DietScreen({Key? key}) : super(key: key);
static Widget builder(BuildContext context) {
final dietID = ModalRoute.of(context)!.settings.arguments;
return BlocProvider<DietBloc>(
create: (context) => DietBloc(
DietState(dietModelObj: DietModel(dietID: dietID.toString())))
..add(DietInitialEvent()),
child: DietScreen());
create: (context) => DietBloc(DietState(dietModelObj: DietModel(dietID: dietID.toString())))..add(DietInitialEvent()), child: DietScreen());
}
Widget build(BuildContext context) {
@@ -41,93 +40,78 @@ class DietScreen extends StatelessWidget {
_buildDiet(BuildContext context, DietLoadedState state) {
mediaQueryData = MediaQuery.of(context);
return Scaffold(
resizeToAvoidBottomInset: false,
body: SafeArea(
child: Container(
width: double.maxFinite,
decoration: AppDecoration.fillGray,
child: ListView(
children: [
SizedBox(height: 30.v),
Container(
margin: EdgeInsets.only(
left: 16.h,
right: 20.h,
bottom: 10.h,
return SafeArea(
child: Scaffold(
resizeToAvoidBottomInset: false,
body: Container(
width: double.maxFinite,
decoration: AppDecoration.fillGray,
child: ListView(
children: [
SizedBox(height: 30.v),
Container(
margin: EdgeInsets.only(
left: 16.h,
right: 20.h,
bottom: 10.h,
),
padding: EdgeInsets.all(12.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
padding: EdgeInsets.all(12.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
],
),
child: Column(mainAxisSize: MainAxisSize.min, children: [
CachedNetworkImage(
imageUrl: dotenv.env['STORAGE_URL']! + (state.diet?['image'] ?? ''),
imageBuilder: (context, imageProvider) => CircleAvatar(
backgroundImage: imageProvider,
radius: 25,
),
child: Column(mainAxisSize: MainAxisSize.min, children: [
CachedNetworkImage(
imageUrl: dotenv.env['STORAGE_URL']! +
(state.diet?['image'] ?? ''),
imageBuilder: (context, imageProvider) => CircleAvatar(
backgroundImage: imageProvider,
radius: 25,
),
placeholder: (context, url) =>
CircularProgressIndicator(),
errorWidget: (context, url, error) => CircleAvatar(
child: Icon(Icons.person),
backgroundColor: Colors.white,
radius: 25,
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => CircleAvatar(
child: Icon(Icons.person),
backgroundColor: Colors.white,
radius: 25,
),
),
SizedBox(height: 16.0),
Text(state.diet?['name'], style: CustomTextStyles.titleMediumBlack),
SizedBox(height: 8.0),
Text(
state.diet?['details'],
),
SizedBox(height: 16.0),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
ElevatedButton(
onPressed: () => GoRouter.of(context).goNamed(
'dietEditScreen',
extra: state.diet?['id'].toString(),
),
child: Text('Редактировать'),
),
SizedBox(height: 16.0),
Text(state.diet?['name'],
style: CustomTextStyles.titleMediumBlack),
SizedBox(height: 8.0),
Text(
state.diet?['details'],
ElevatedButton(
onPressed: () => context.read<DietBloc>().add(DeleteEntry(state.diet?['id'])),
child: Text('Удалить'),
),
SizedBox(height: 16.0),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
ElevatedButton(
onPressed: () => Navigator.pushNamed(
context,
AppRoutes.dietEditScreen,
arguments: state.diet?['id'].toString(),
),
child: Text('Редактировать'),
),
ElevatedButton(
onPressed: () => context
.read<DietBloc>()
.add(DeleteEntry(state.diet?['id'])),
child: Text('Удалить'),
),
],
)
]),
],
)
],
),
),
), bottomNavigationBar: _buildBottomBar(context));
}
///Handling route based on bottom click actions
String getCurrentRoute(BottomBarEnum type) {
switch (type) {
case BottomBarEnum.tf:
return AppRoutes.dietScreen;
default:
return "/";
}
]),
)
],
),
),
),
);
}
///Handling page based on route
@@ -136,19 +120,15 @@ class DietScreen extends StatelessWidget {
String currentRoute,
) {
switch (currentRoute) {
case AppRoutes.dietScreen:
case '/diet':
return DietScreen.builder(context);
default:
return DietScreen();
}
}
_buildBottomBar(BuildContext context) {
return CustomBottomBar(onChanged: (BottomBarEnum type) {});
}
/// Navigates to the previous screen.
onTapArrowleftone(BuildContext context) {
NavigatorService.goBack();
GoRouter.of(context).pop();
}
}

View File

@@ -1,5 +1,6 @@
import 'dart:async';
import 'package:eatvio_app/app_router.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
@@ -8,6 +9,7 @@ import 'package:http/http.dart' as http;
import 'dart:convert';
import '/core/app_export.dart';
import 'package:eatvio_app/presentation/feed/blog_post_create_screen/models/blog_post_create_model.dart';
import 'package:go_router/go_router.dart';
part 'blog_post_create_event.dart';
part 'blog_post_create_state.dart';
@@ -26,9 +28,7 @@ class BlogPostCreateBloc
final box = await Hive.openBox('token');
final token = box.get('token');
if (token == null) {
NavigatorService.popAndPushNamed(
AppRoutes.authScreen,
);
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
if (state is BlogPostCreateLoadedState) {
var currentState = state as BlogPostCreateLoadedState;
@@ -59,7 +59,7 @@ class BlogPostCreateBloc
);
if (response.statusCode == 200) {
NavigatorService.pushNamed(AppRoutes.feedScreen);
AppRouter.navigatorKey.currentContext?.goNamed('feedScreen');
} else {
print(
'Failed to create blog post. Status Code: ${response.statusCode} ${response.body}');

View File

@@ -2,18 +2,16 @@ import 'bloc/blog_post_create_bloc.dart';
import 'models/blog_post_create_model.dart';
import 'package:eatvio_app/core/app_export.dart';
import 'package:flutter/material.dart';
import 'package:eatvio_app/widgets/custom_bottom_bar.dart';
import 'package:eatvio_app/widgets/custom_text_form_field.dart';
import 'package:eatvio_app/widgets/custom_outlined_button.dart';
class BlogPostCreateScreen extends StatelessWidget {
const BlogPostCreateScreen({Key? key}) : super(key: key);
static Widget builder(BuildContext context) {
final blog_postID = ModalRoute.of(context)!.settings.arguments;
return BlocProvider<BlogPostCreateBloc>(
create: (context) => BlogPostCreateBloc(BlogPostCreateState(
blog_postModelObj:
BlogPostCreateModel(blog_postID: blog_postID.toString())))
create: (context) => BlogPostCreateBloc(BlogPostCreateState(blog_postModelObj: BlogPostCreateModel(blog_postID: blog_postID.toString())))
..add(BlogPostCreateInitialEvent()),
child: BlogPostCreateScreen());
}
@@ -41,92 +39,85 @@ class BlogPostCreateScreen extends StatelessWidget {
}
_buildBlogPostCreate(BuildContext context, BlogPostCreateLoadedState state) {
mediaQueryData = MediaQuery.of(context);
return SafeArea(
child: Scaffold(
resizeToAvoidBottomInset: false,
body: Container(
width: double.maxFinite,
decoration: AppDecoration.fillGray,
child: ListView(children: [
SizedBox(height: 30.v),
Container(
margin: EdgeInsets.only(
left: 16.h,
right: 20.h,
bottom: 10.h,
return Scaffold(
resizeToAvoidBottomInset: false,
body: Container(
width: double.maxFinite,
decoration: AppDecoration.fillGray,
child: ListView(
children: [
SizedBox(height: 30.v),
Container(
margin: EdgeInsets.only(
left: 16.h,
right: 20.h,
bottom: 10.h,
),
padding: EdgeInsets.all(12.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Column(
children: <Widget>[
Text("Заголовок новости", style: theme.textTheme.bodyLarge),
CustomTextFormField(
controller: state.title,
validator: _validateTitle,
),
padding: EdgeInsets.all(12.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius:
const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
SizedBox(height: 8.0),
Text("Ссылка на YouTube", style: theme.textTheme.bodyLarge),
CustomTextFormField(
controller: state.youtube_url,
validator: _validateYouTubeLink,
),
child: Column(mainAxisSize: MainAxisSize.min, children: [
Column(
children: <Widget>[
Text("Заголовок новости",
style: theme.textTheme.bodyLarge),
CustomTextFormField(
controller: state.title,
validator: _validateTitle,
),
SizedBox(height: 8.0),
Text("Ссылка на YouTube",
style: theme.textTheme.bodyLarge),
CustomTextFormField(
controller: state.youtube_url,
validator: _validateYouTubeLink,
),
SizedBox(height: 8.0),
Text("Описание", style: theme.textTheme.bodyLarge),
CustomTextFormField(
controller: state.description,
validator: _validateDescription,
maxLines: 6,
),
SizedBox(height: 16.0),
// Add widget for image upload here
CustomOutlinedButton(
onPressed: () => _submitForm(
context,
state.title!.text,
state.description!.text,
state.youtube_url!.text),
buttonStyle:
CustomButtonStyles.outlinePrimaryTL15,
text: 'Создать',
),
],
),
]))
])),
bottomNavigationBar: _buildBottomBar(context)));
SizedBox(height: 8.0),
Text("Описание", style: theme.textTheme.bodyLarge),
CustomTextFormField(
controller: state.description,
validator: _validateDescription,
maxLines: 6,
),
SizedBox(height: 16.0),
// Add widget for image upload here
CustomOutlinedButton(
onPressed: () => _submitForm(context, state.title!.text, state.description!.text, state.youtube_url!.text),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
text: 'Создать',
),
],
),
],
),
)
],
),
),
);
}
}
_submitForm(
BuildContext context, String title, String description, String youtubeURL) {
_submitForm(BuildContext context, String title, String description, String youtubeURL) {
String? titleError = _validateTitle(title);
String? descriptionError = _validateDescription(description);
String? youtubeURLError = _validateYouTubeLink(youtubeURL);
if (titleError != null ||
descriptionError != null ||
youtubeURLError != null) {
if (titleError != null || descriptionError != null || youtubeURLError != null) {
// Display error messages or handle invalid input as needed
// For example, show a snackbar with the error message
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(titleError ?? descriptionError ?? youtubeURLError!)),
SnackBar(content: Text(titleError ?? descriptionError ?? youtubeURLError!)),
);
} else {
// Proceed with form submission
@@ -158,11 +149,6 @@ String? _validateYouTubeLink(value) {
}
bool _isValidUrl(String url) {
final RegExp urlRegExp =
new RegExp(r"^(http|https):\/\/([\w-]+\.)+[\w-]+(\/[\w-./?%&=]*)?$");
final RegExp urlRegExp = new RegExp(r"^(http|https):\/\/([\w-]+\.)+[\w-]+(\/[\w-./?%&=]*)?$");
return urlRegExp.hasMatch(url);
}
_buildBottomBar(BuildContext context) {
return CustomBottomBar(onChanged: (BottomBarEnum type) {});
}

View File

@@ -1,5 +1,6 @@
import 'dart:async';
import 'package:eatvio_app/app_router.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
@@ -8,7 +9,10 @@ import 'package:http/http.dart' as http;
import 'dart:convert';
import '/core/app_export.dart';
import 'package:eatvio_app/presentation/feed/blog_post_edit_screen/models/blog_post_edit_model.dart';
import 'package:go_router/go_router.dart';
part 'blog_post_edit_event.dart';
part 'blog_post_edit_state.dart';
/// A bloc that manages the state of a BlogPostEdit according to the event that is dispatched to it.
@@ -26,9 +30,7 @@ class BlogPostEditBloc extends Bloc<BlogPostEditEvent, BlogPostEditState> {
final box = await Hive.openBox('token');
final token = box.get('token');
if (token == null) {
NavigatorService.popAndPushNamed(
AppRoutes.authScreen,
);
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final String? blog_postID = state.blog_postModelObj?.blog_postID;
final response = await http.get(
@@ -57,8 +59,7 @@ class BlogPostEditBloc extends Bloc<BlogPostEditEvent, BlogPostEditState> {
}
} else {
// Один или несколько ключевых полей отсутствуют, обрабатываем ошибку
emit(BlogPostEditErrorState(
'Некоторые необходимые данные отсутствуют.'));
emit(BlogPostEditErrorState('Некоторые необходимые данные отсутствуют.'));
}
} else {
// Статус ответа не равен 200, обрабатываем ошибку
@@ -70,8 +71,7 @@ class BlogPostEditBloc extends Bloc<BlogPostEditEvent, BlogPostEditState> {
}
}
FutureOr<void> _editBlogPost(
EditBlogPost event, Emitter<BlogPostEditState> emit) async {
FutureOr<void> _editBlogPost(EditBlogPost event, Emitter<BlogPostEditState> emit) async {
if (state is BlogPostEditLoadedState) {
var currentState = state as BlogPostEditLoadedState;
@@ -83,11 +83,9 @@ class BlogPostEditBloc extends Bloc<BlogPostEditEvent, BlogPostEditState> {
);
if (response.statusCode == 200) {
NavigatorService.pushNamed(AppRoutes.blogPostScreen,
arguments: currentState.blog_post?['id'].toString());
AppRouter.navigatorKey.currentContext?.goNamed('blogPostScreen', extra: currentState.blog_post?['id'].toString());
} else {
print(
'Failed to edit blog post. Status Code: ${response.statusCode} ${response.body}');
print('Failed to edit blog post. Status Code: ${response.statusCode} ${response.body}');
}
}
}

View File

@@ -2,19 +2,16 @@ import 'bloc/blog_post_edit_bloc.dart';
import 'models/blog_post_edit_model.dart';
import 'package:eatvio_app/core/app_export.dart';
import 'package:flutter/material.dart';
import 'package:eatvio_app/widgets/custom_bottom_bar.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
class BlogPostEditScreen extends StatelessWidget {
const BlogPostEditScreen({Key? key}) : super(key: key);
static Widget builder(BuildContext context) {
final blog_postID = ModalRoute.of(context)!.settings.arguments;
static Widget builder(BuildContext context, blog_postID) {
return BlocProvider<BlogPostEditBloc>(
create: (context) => BlogPostEditBloc(BlogPostEditState(
blog_postModelObj:
BlogPostEditModel(blog_postID: blog_postID.toString())))
..add(BlogPostEditInitialEvent()),
create: (context) =>
BlogPostEditBloc(BlogPostEditState(blog_postModelObj: BlogPostEditModel(blog_postID: blog_postID.toString())))..add(BlogPostEditInitialEvent()),
child: BlogPostEditScreen());
}
@@ -22,9 +19,10 @@ class BlogPostEditScreen extends StatelessWidget {
return BlocBuilder<BlogPostEditBloc, BlogPostEditState>(
builder: (context, state) {
if (state is BlogPostEditInitialState) {
// Это начальное состояние, данные ещё не загружены.
return Container(
color: Colors.white,
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
} else if (state is BlogPostEditLoadedState) {
// Данные загружены, можно строить экран.
@@ -33,8 +31,11 @@ class BlogPostEditScreen extends StatelessWidget {
// Произошла ошибка при загрузке данных.
return Center(child: Text(state.errorMessage));
} else {
// Другие состояния, если они есть.
return Container(); // Пустой контейнер для необработанных состояний.
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
},
);
@@ -43,77 +44,75 @@ class BlogPostEditScreen extends StatelessWidget {
_buildBlogPostEdit(BuildContext context, BlogPostEditLoadedState state) {
mediaQueryData = MediaQuery.of(context);
return Scaffold(
resizeToAvoidBottomInset: false,
body: SafeArea(
child: Container(
width: double.maxFinite,
decoration: AppDecoration.fillGray,
child: ListView(children: [
Container(
margin: EdgeInsets.only(
left: 16.h,
right: 20.h,
bottom: 10.h,
resizeToAvoidBottomInset: false,
body: Container(
width: double.maxFinite,
decoration: AppDecoration.fillGray,
child: ListView(
children: [
SizedBox(height: 30.v),
Container(
margin: EdgeInsets.only(
left: 16.h,
right: 20.h,
bottom: 10.h,
),
padding: EdgeInsets.all(12.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
padding: EdgeInsets.all(12.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CachedNetworkImage(
imageUrl: dotenv.env['STORAGE_URL']! + (state.blog_post?['image'] ?? ''),
imageBuilder: (context, imageProvider) => CircleAvatar(
backgroundImage: imageProvider,
radius: 25,
),
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => CircleAvatar(
child: Icon(Icons.person),
backgroundColor: Colors.white,
radius: 25,
),
),
child: Column(mainAxisSize: MainAxisSize.min, children: [
CachedNetworkImage(
imageUrl: dotenv.env['STORAGE_URL']! +
(state.blog_post?['image'] ?? ''),
imageBuilder: (context, imageProvider) => CircleAvatar(
backgroundImage: imageProvider,
radius: 25,
),
placeholder: (context, url) =>
CircularProgressIndicator(),
errorWidget: (context, url, error) => CircleAvatar(
child: Icon(Icons.person),
backgroundColor: Colors.white,
radius: 25,
),
),
SizedBox(height: 16.0),
TextFormField(
controller: state.title,
decoration: InputDecoration(labelText: 'Заголовок'),
),
SizedBox(height: 16.0),
TextFormField(
controller: state.description,
decoration: InputDecoration(labelText: 'Описание'),
),
SizedBox(height: 16.0),
TextFormField(
controller: state.youtube_url,
decoration: InputDecoration(labelText: 'Ссылка'),
),
SizedBox(height: 16.0),
// Здесь нужно добавить виджет для загрузки картинки
ElevatedButton(
onPressed: () =>
context.read<BlogPostEditBloc>().add(EditBlogPost()),
child: Text('Сохранить'),
)
]),
)
]),
),
), bottomNavigationBar: _buildBottomBar(context));
SizedBox(height: 16.0),
TextFormField(
controller: state.title,
decoration: InputDecoration(labelText: 'Заголовок'),
),
SizedBox(height: 16.0),
TextFormField(
controller: state.description,
decoration: InputDecoration(labelText: 'Описание'),
),
SizedBox(height: 16.0),
TextFormField(
controller: state.youtube_url,
decoration: InputDecoration(labelText: 'Ссылка'),
),
SizedBox(height: 16.0),
// Здесь нужно добавить виджет для загрузки картинки
ElevatedButton(
onPressed: () => context.read<BlogPostEditBloc>().add(EditBlogPost()),
child: Text('Сохранить'),
)
],
),
)
],
),
),
);
}
}
_buildBottomBar(BuildContext context) {
return CustomBottomBar(onChanged: (BottomBarEnum type) {});
}

View File

@@ -1,5 +1,6 @@
import 'dart:async';
import 'package:eatvio_app/app_router.dart';
import 'package:eatvio_app/presentation/feed/blog_post_screen/models/comment_model.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
@@ -10,6 +11,7 @@ import 'package:intl/intl.dart';
import 'dart:convert';
import '/core/app_export.dart';
import 'package:eatvio_app/presentation/feed/blog_post_screen/models/blog_post_model.dart';
import 'package:go_router/go_router.dart';
part 'blog_post_event.dart';
part 'blog_post_state.dart';
@@ -29,9 +31,7 @@ class BlogPostBloc extends Bloc<BlogPostEvent, BlogPostState> {
final box = await Hive.openBox('token');
final token = box.get('token');
if (token == null) {
NavigatorService.popAndPushNamed(
AppRoutes.authScreen,
);
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final String? blog_postID = state.blog_postModelObj?.blog_postID;
final response = await http.get(
@@ -98,9 +98,7 @@ class BlogPostBloc extends Bloc<BlogPostEvent, BlogPostState> {
final box = await Hive.openBox('token');
final token = box.get('token');
if (token == null) {
NavigatorService.popAndPushNamed(
AppRoutes.authScreen,
);
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final int id = event.id;
final response = await http.post(
@@ -110,7 +108,7 @@ class BlogPostBloc extends Bloc<BlogPostEvent, BlogPostState> {
},
);
if (response.statusCode == 200) {
NavigatorService.pushNamed(AppRoutes.feedScreen);
AppRouter.navigatorKey.currentContext?.goNamed('feedScreen');
} else {
// Статус ответа не равен 200, обрабатываем ошибку
emit(BlogPostErrorState('Ошибка: ${response.statusCode}'));
@@ -122,9 +120,7 @@ class BlogPostBloc extends Bloc<BlogPostEvent, BlogPostState> {
final token = box.get('token');
if (token == null) {
NavigatorService.popAndPushNamed(
AppRoutes.authScreen,
);
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final response = await http.get(
@@ -141,9 +137,7 @@ class BlogPostBloc extends Bloc<BlogPostEvent, BlogPostState> {
final box = await Hive.openBox('token');
final token = box.get('token');
if (token == null) {
NavigatorService.popAndPushNamed(
AppRoutes.authScreen,
);
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final response = await http.get(
Uri.parse(dotenv.env['SERVER_ROOT']! + '/feed/getComments/$id'),

View File

@@ -1,10 +1,11 @@
import 'package:go_router/go_router.dart';
import 'bloc/blog_post_bloc.dart';
import 'models/blog_post_model.dart';
import 'widgets/comment_widget.dart';
import 'package:eatvio_app/core/app_export.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:eatvio_app/widgets/custom_bottom_bar.dart';
import 'package:eatvio_app/widgets/custom_edit_text_form_field.dart';
import 'package:eatvio_app/widgets/custom_outlined_button.dart';
import 'package:cached_network_image/cached_network_image.dart';
@@ -12,59 +13,73 @@ import 'package:flutter_dotenv/flutter_dotenv.dart';
class BlogPostScreen extends StatelessWidget {
const BlogPostScreen({Key? key}) : super(key: key);
static Widget builder(BuildContext context) {
final blog_postID = ModalRoute.of(context)!.settings.arguments;
static Widget builder(BuildContext context, String blog_postID) {
return BlocProvider<BlogPostBloc>(
create: (context) => BlogPostBloc(BlogPostState(
blog_postModelObj:
BlogPostModel(blog_postID: blog_postID.toString())))
..add(BlogPostInitialEvent()),
child: BlogPostScreen());
create: (context) => BlogPostBloc(
BlogPostState(
blog_postModelObj: BlogPostModel(
blog_postID: blog_postID.toString(),
),
),
)..add(
BlogPostInitialEvent(),
),
child: BlogPostScreen(),
);
}
Widget build(BuildContext context) {
return BlocBuilder<BlogPostBloc, BlogPostState>(
builder: (context, state) {
if (state is BlogPostInitialState) {
// Это начальное состояние, данные ещё не загружены.
return Container(
color: Colors.white,
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
} else if (state is BlogPostLoadedState) {
// Данные загружены, можно строить экран.
return _buildBlogPost(context, state);
} else if (state is BlogPostErrorState) {
// Произошла ошибка при загрузке данных.
return Center(child: Text(state.errorMessage));
return Center(
child: Text(state.errorMessage),
);
} else {
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
// Другие состояния, если они есть.
return Container(); // Пустой контейнер для необработанных состояний.
}
},
);
}
_buildBlogPost(BuildContext context, BlogPostLoadedState state) {
mediaQueryData = MediaQuery.of(context);
return Scaffold(
resizeToAvoidBottomInset: false,
body: SafeArea(
child: Container(
width: double.maxFinite,
decoration: AppDecoration.fillGray,
child: ListView(
children: [
_buildHeaderRow(context, state),
SizedBox(height: 15.v),
_buildBlogPostData(context, state),
SizedBox(height: 15.v),
_buildTextField(context, state),
SizedBox(height: 15.v),
_buildCommentSection(context, state),
],
),
),
),bottomNavigationBar: _buildBottomBar(context));
return SafeArea(
child: Scaffold(
resizeToAvoidBottomInset: false,
body: Container(
width: double.maxFinite,
decoration: AppDecoration.fillGray,
child: ListView(
children: [
SizedBox(height: 15.v),
_buildHeaderRow(context, state),
SizedBox(height: 15.v),
_buildBlogPostData(context, state),
SizedBox(height: 15.v),
_buildTextField(context, state),
SizedBox(height: 15.v),
_buildCommentSection(context, state),
],
),
),
),
);
}
_buildHeaderRow(BuildContext context, BlogPostLoadedState state) {
@@ -91,8 +106,9 @@ class BlogPostScreen extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
CustomImageView(
imagePath: ImageConstant.imgArrowLeft,
onTap: () => NavigatorService.goBack()),
imagePath: ImageConstant.imgArrowLeft,
onTap: () => GoRouter.of(context).pop(),
),
Padding(
padding: EdgeInsets.only(top: 1.v),
child: Row(
@@ -105,8 +121,7 @@ class BlogPostScreen extends StatelessWidget {
),
),
CachedNetworkImage(
imageUrl: dotenv.env['STORAGE_URL']! +
(state.author?['profile_photo_path'] ?? ''),
imageUrl: dotenv.env['STORAGE_URL']! + (state.author?['profile_photo_path'] ?? ''),
imageBuilder: (context, imageProvider) => CircleAvatar(
backgroundImage: imageProvider,
radius: 25,
@@ -180,8 +195,7 @@ class BlogPostScreen extends StatelessWidget {
Align(
alignment: Alignment.topLeft,
child: Text(
DateFormat('dd.MM.yyyy')
.format(DateTime.parse(state.blog_post?['updated_at'])),
DateFormat('dd.MM.yyyy').format(DateTime.parse(state.blog_post?['updated_at'])),
style: theme.textTheme.bodyMedium,
)),
SizedBox(height: 16.0),
@@ -294,10 +308,9 @@ class BlogPostScreen extends StatelessWidget {
void tapMenu(String value, BuildContext context, BlogPostLoadedState state) {
if (value == 'edit') {
Navigator.pushNamed(
context,
AppRoutes.blogPostEditScreen,
arguments: state.blog_post?['id'].toString(),
GoRouter.of(context).goNamed(
'blogPostEditScreen',
extra: state.blog_post?['id'].toString(),
);
} else if (value == 'delete') {
context.read<BlogPostBloc>().add(DeleteEntry(state.blog_post?['id']));
@@ -341,9 +354,7 @@ class BlogPostScreen extends StatelessWidget {
decoration: BoxDecoration(
color: Colors.transparent,
),
onPressed: () => context
.read<BlogPostBloc>()
.add(CreateComment(state.commentController!.text)),
onPressed: () => context.read<BlogPostBloc>().add(CreateComment(state.commentController!.text)),
// Установите прозрачный фон
)
],
@@ -376,12 +387,8 @@ class BlogPostScreen extends StatelessWidget {
}
}
_buildBottomBar(BuildContext context) {
return CustomBottomBar(onChanged: (BottomBarEnum type) {});
}
/// Navigates to the previous screen.
onTapArrowleftone(BuildContext context) {
NavigatorService.goBack();
GoRouter.of(context).pop();
}
}

View File

@@ -1,6 +1,7 @@
//import 'package:eatvio_app/presentation/auth/auth_screen/auth_screen.dart';
import 'dart:async';
import 'package:eatvio_app/app_router.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import '/core/app_export.dart';
@@ -10,6 +11,7 @@ import 'dart:convert';
import 'package:hive/hive.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:image_picker/image_picker.dart';
import 'package:go_router/go_router.dart';
part 'feed_event.dart';
part 'feed_state.dart';
@@ -28,9 +30,7 @@ class FeedBloc extends Bloc<FeedEvent, FeedState> {
final box = await Hive.openBox('token');
final token = box.get('token');
if (token == null) {
NavigatorService.popAndPushNamed(
AppRoutes.authScreen,
); // Если токен есть, запускаем приложение
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final response = await http.get(
Uri.parse(dotenv.env['SERVER_ROOT']! + '/feed/index'),

View File

@@ -1,9 +1,10 @@
import 'package:go_router/go_router.dart';
import 'bloc/feed_bloc.dart';
import 'models/feed_model.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/presentation/feed/feed_screen/widgets/feed_search_view.dart';
import 'package:eatvio_app/presentation/feed/feed_screen/widgets/feed_scroll_item.dart';
import 'package:eatvio_app/widgets/custom_bottom_bar.dart';
import 'package:eatvio_app/widgets/custom_outlined_button.dart';
import 'package:eatvio_app/core/app_export.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
@@ -12,10 +13,7 @@ import 'package:intl/intl.dart';
class FeedScreen extends StatelessWidget {
static Widget builder(BuildContext context) {
return BlocProvider<FeedBloc>(
create: (context) => FeedBloc(FeedState(myProfileModelObj: FeedModel()))
..add(FeedInitialEvent()),
child: FeedScreen());
return BlocProvider<FeedBloc>(create: (context) => FeedBloc(FeedState(myProfileModelObj: FeedModel()))..add(FeedInitialEvent()), child: FeedScreen());
}
Widget build(BuildContext context) {
@@ -64,7 +62,6 @@ class FeedScreen extends StatelessWidget {
),
),
),
bottomNavigationBar: _buildBottomBar(context),
),
);
}
@@ -106,10 +103,7 @@ class FeedScreen extends StatelessWidget {
flex: 1,
child: CustomOutlinedButton(
text: "+",
onPressed: () => Navigator.pushNamed(
context,
AppRoutes.blogPostCreateScreen,
),
onPressed: () => GoRouter.of(context).goNamed('blogPostCreateScreen'),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
),
),
@@ -154,8 +148,7 @@ class FeedScreen extends StatelessWidget {
child: Column(children: [
ListTile(
leading: CachedNetworkImage(
imageUrl: dotenv.env['STORAGE_URL']! +
(post["author"]['profile_photo_path'] ?? ''),
imageUrl: dotenv.env['STORAGE_URL']! + (post["author"]['profile_photo_path'] ?? ''),
imageBuilder: (context, imageProvider) => CircleAvatar(
backgroundImage: imageProvider,
radius: 25,
@@ -172,14 +165,12 @@ class FeedScreen extends StatelessWidget {
style: CustomTextStyles.titleMediumSemiBold,
),
subtitle: Text(
DateFormat('dd.MM.yyyy')
.format(DateTime.parse(post?['post']['updated_at'])),
DateFormat('dd.MM.yyyy').format(DateTime.parse(post?['post']['updated_at'])),
style: theme.textTheme.bodyMedium,
),
onTap: () => Navigator.pushNamed(
context,
AppRoutes.blogPostScreen,
arguments: post?['post']['id'].toString(),
onTap: () => GoRouter.of(context).goNamed(
'blogPostScreen',
extra: post?['post']['id'].toString(),
),
trailing: PopupMenuButton<String>(
onSelected: (value) => print("tap"),
@@ -220,13 +211,11 @@ class FeedScreen extends StatelessWidget {
),
SizedBox(height: 16.0),
CachedNetworkImage(
imageUrl: dotenv.env['STORAGE_URL']! +
(post?['post']['image'] ?? ''),
imageUrl: dotenv.env['STORAGE_URL']! + (post?['post']['image'] ?? ''),
imageBuilder: (context, imageProvider) => Stack(
children: [
ClipRRect(
borderRadius:
const BorderRadius.all(Radius.circular(8.0)),
borderRadius: const BorderRadius.all(Radius.circular(8.0)),
child: Image(
image: imageProvider,
),
@@ -277,12 +266,8 @@ class FeedScreen extends StatelessWidget {
};
}
_buildBottomBar(BuildContext context) {
return CustomBottomBar(onChanged: (BottomBarEnum type) {});
}
onTapArrowLeft(BuildContext context) {
NavigatorService.goBack();
GoRouter.of(context).pop();
}
_buildMenu(BuildContext context, FeedLoadedState state) {

View File

@@ -1,5 +1,6 @@
import 'dart:async';
import 'package:eatvio_app/app_router.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
@@ -8,6 +9,7 @@ import 'package:http/http.dart' as http;
import 'dart:convert';
import '/core/app_export.dart';
import 'package:eatvio_app/presentation/glossary/glossary_screen/models/glossary_screen_model.dart';
import 'package:go_router/go_router.dart';
part 'glossary_screen_event.dart';
part 'glossary_screen_state.dart';
@@ -26,9 +28,7 @@ class GlossaryBloc extends Bloc<GlossaryEvent, GlossaryState> {
final box = await Hive.openBox('token');
final token = box.get('token');
if (token == null) {
NavigatorService.popAndPushNamed(
AppRoutes.authScreen,
);
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final responseProductGroups = await http.get(
Uri.parse(dotenv.env['SERVER_ROOT']! + '/glossary/getProductGroups'),

View File

@@ -4,7 +4,6 @@ import 'package:eatvio_app/core/app_export.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:eatvio_app/widgets/custom_bottom_bar.dart';
import 'package:eatvio_app/widgets/custom_outlined_button.dart';
import 'package:eatvio_app/widgets/custom_drop_down.dart';
import 'package:eatvio_app/presentation/glossary/glossary_screen/widgets/glossary_search_widget.dart';
@@ -12,12 +11,10 @@ import 'package:eatvio_app/presentation/glossary/glossary_screen/widgets/glossar
class GlossaryScreen extends StatelessWidget {
const GlossaryScreen({Key? key}) : super(key: key);
static Widget builder(BuildContext context) {
return BlocProvider<GlossaryBloc>(
create: (context) =>
GlossaryBloc(GlossaryState(glossaryModelObj: GlossaryModel()))
..add(GlossaryInitialEvent()),
child: GlossaryScreen());
create: (context) => GlossaryBloc(GlossaryState(glossaryModelObj: GlossaryModel()))..add(GlossaryInitialEvent()), child: GlossaryScreen());
}
Widget build(BuildContext context) {
@@ -25,8 +22,10 @@ class GlossaryScreen extends StatelessWidget {
builder: (context, state) {
if (state is GlossaryInitialState) {
// Это начальное состояние, данные ещё не загружены.
return Container(
color: Colors.white,
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
} else if (state is GlossaryLoadedState) {
// Данные загружены, можно строить экран.
@@ -36,39 +35,43 @@ class GlossaryScreen extends StatelessWidget {
return Center(child: Text(state.errorMessage));
} else {
// Другие состояния, если они есть.
return Container(); // Пустой контейнер для необработанных состояний.
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
},
);
}
_buildGlossary(BuildContext context, GlossaryLoadedState state) {
mediaQueryData = MediaQuery.of(context);
return SafeArea(
child: Scaffold(
backgroundColor: Colors.grey[200],
resizeToAvoidBottomInset: true,
body: SingleChildScrollView(
child: Container(
width: double.maxFinite,
decoration: BoxDecoration(color: Colors.grey[200]),
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(height: 20),
_buildSearch(context),
SizedBox(height: 20),
_buildMenu(context),
SizedBox(height: 20),
_buildProducts(context, state)
],
),
return Scaffold(
appBar: AppBar(
toolbarHeight: 0,
automaticallyImplyLeading: false,
),
backgroundColor: Colors.grey[200],
resizeToAvoidBottomInset: true,
body: SingleChildScrollView(
child: Container(
width: double.maxFinite,
decoration: BoxDecoration(color: Colors.grey[200]),
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(height: 20),
_buildSearch(context),
SizedBox(height: 20),
_buildMenu(context),
SizedBox(height: 20),
_buildProducts(context, state)
],
),
),
),
bottomNavigationBar: _buildBottomBar(context),
),
);
}
@@ -184,16 +187,12 @@ class GlossaryScreen extends StatelessWidget {
vertical: 9,
),
onChanged: _changeProductGroup(context, state),
width: MediaQuery.of(context)
.size
.width, // Set a percentage value as per your layout
width: MediaQuery.of(context).size.width, // Set a percentage value as per your layout
),
SizedBox(
height: 8,
),
Align(
alignment: Alignment.centerLeft,
child: Text("*КБЖУ указано за 100гр")),
Align(alignment: Alignment.centerLeft, child: Text("*КБЖУ указано за 100гр")),
SizedBox(
height: 8,
),
@@ -268,8 +267,7 @@ class GlossaryScreen extends StatelessWidget {
child: Column(children: [
Text("Жиры - ${product['fats_on_100gr'].toString()}"),
SizedBox(height: 8),
Text(
"Углеводы - ${product['carbohydrates_on_100gr'].toString()}")
Text("Углеводы - ${product['carbohydrates_on_100gr'].toString()}")
]),
)
])
@@ -298,33 +296,19 @@ class GlossaryScreen extends StatelessWidget {
return result;
}
///Handling route based on bottom click actions
String getCurrentRoute(BottomBarEnum type) {
switch (type) {
case BottomBarEnum.tf:
return AppRoutes.glossaryScreen;
default:
return "/";
}
}
///Handling page based on route
Widget getCurrentPage(
BuildContext context,
String currentRoute,
) {
switch (currentRoute) {
case AppRoutes.glossaryScreen:
case '/glossary':
return GlossaryScreen.builder(context);
default:
return GlossaryScreen();
}
}
_buildBottomBar(BuildContext context) {
return CustomBottomBar(onChanged: (BottomBarEnum type) {});
}
_changeProductGroup(BuildContext context, GlossaryLoadedState state) {
return (value) {
context.read<GlossaryBloc>().add(ChangeProductGroup(value.id));

View File

@@ -1,5 +1,6 @@
import 'dart:async';
import 'package:eatvio_app/app_router.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
@@ -8,6 +9,7 @@ import 'package:http/http.dart' as http;
import 'dart:convert';
import '/core/app_export.dart';
import 'package:eatvio_app/presentation/plans/plan_create_screen/models/plan_create_model.dart';
import 'package:go_router/go_router.dart';
part 'plan_create_event.dart';
part 'plan_create_state.dart';
@@ -25,9 +27,7 @@ class PlanCreateBloc extends Bloc<PlanCreateEvent, PlanCreateState> {
final box = await Hive.openBox('token');
final token = box.get('token');
if (token == null) {
NavigatorService.popAndPushNamed(
AppRoutes.authScreen,
);
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
if (state is PlanCreateLoadedState) {
var currentState = state as PlanCreateLoadedState;
@@ -55,7 +55,7 @@ class PlanCreateBloc extends Bloc<PlanCreateEvent, PlanCreateState> {
);
if (response.statusCode == 200) {
NavigatorService.pushNamed(AppRoutes.planSelectScreen);
AppRouter.navigatorKey.currentContext?.goNamed('planSelectScreen');
} else {
print(
'Failed to create blog post. Status Code: ${response.statusCode} ${response.body}');

View File

@@ -2,16 +2,14 @@ import 'bloc/plan_create_bloc.dart';
import 'models/plan_create_model.dart';
import 'package:eatvio_app/core/app_export.dart';
import 'package:flutter/material.dart';
import 'package:eatvio_app/widgets/custom_bottom_bar.dart';
class PlanCreateScreen extends StatelessWidget {
const PlanCreateScreen({Key? key}) : super(key: key);
static Widget builder(BuildContext context) {
final planID = ModalRoute.of(context)!.settings.arguments;
return BlocProvider<PlanCreateBloc>(
create: (context) => PlanCreateBloc(PlanCreateState(
planModelObj: PlanCreateModel(planID: planID.toString())))
..add(PlanCreateInitialEvent()),
create: (context) => PlanCreateBloc(PlanCreateState(planModelObj: PlanCreateModel(planID: planID.toString())))..add(PlanCreateInitialEvent()),
child: PlanCreateScreen());
}
@@ -39,58 +37,57 @@ class PlanCreateScreen extends StatelessWidget {
_buildPlanCreate(BuildContext context, PlanCreateLoadedState state) {
mediaQueryData = MediaQuery.of(context);
return Scaffold(
resizeToAvoidBottomInset: false,
body: SafeArea(
child: Container(
width: double.maxFinite,
decoration: AppDecoration.fillGray,
child: ListView(children: [
Container(
margin: EdgeInsets.only(
left: 16.h,
right: 20.h,
bottom: 10.h,
),
padding: EdgeInsets.all(12.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: Column(mainAxisSize: MainAxisSize.min, children: [
SizedBox(height: 16.0),
TextFormField(
controller: state.name,
decoration: InputDecoration(labelText: 'Заголовок'),
return SafeArea(
child: Scaffold(
resizeToAvoidBottomInset: false,
body: Container(
width: double.maxFinite,
decoration: AppDecoration.fillGray,
child: ListView(
children: [
SizedBox(height: 30.v),
Container(
margin: EdgeInsets.only(
left: 16.h,
right: 20.h,
bottom: 10.h,
),
padding: EdgeInsets.all(12.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
SizedBox(height: 16.0),
TextFormField(
controller: state.details,
decoration: InputDecoration(labelText: 'Описание'),
),
SizedBox(height: 16.0),
// Здесь нужно добавить виджет для загрузки картинки
ElevatedButton(
onPressed: () =>
context.read<PlanCreateBloc>().add(CreatePlan()),
child: Text('Создать'),
)
]),
)
]),
),
), bottomNavigationBar: _buildBottomBar(context));
],
),
child: Column(mainAxisSize: MainAxisSize.min, children: [
SizedBox(height: 16.0),
TextFormField(
controller: state.name,
decoration: InputDecoration(labelText: 'Заголовок'),
),
SizedBox(height: 16.0),
TextFormField(
controller: state.details,
decoration: InputDecoration(labelText: 'Описание'),
),
SizedBox(height: 16.0),
// Здесь нужно добавить виджет для загрузки картинки
ElevatedButton(
onPressed: () => context.read<PlanCreateBloc>().add(CreatePlan()),
child: Text('Создать'),
)
]),
)
],
),
),
),
);
}
}
_buildBottomBar(BuildContext context) {
return CustomBottomBar(onChanged: (BottomBarEnum type) {});
}

View File

@@ -1,5 +1,6 @@
import 'dart:async';
import 'package:eatvio_app/app_router.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
@@ -8,7 +9,10 @@ import 'package:http/http.dart' as http;
import 'dart:convert';
import '/core/app_export.dart';
import 'package:eatvio_app/presentation/plans/plan_edit_screen/models/plan_edit_model.dart';
import 'package:go_router/go_router.dart';
part 'plan_edit_event.dart';
part 'plan_edit_state.dart';
/// A bloc that manages the state of a PlanEdit according to the event that is dispatched to it.
@@ -26,9 +30,7 @@ class PlanEditBloc extends Bloc<PlanEditEvent, PlanEditState> {
final box = await Hive.openBox('token');
final token = box.get('token');
if (token == null) {
NavigatorService.popAndPushNamed(
AppRoutes.authScreen,
);
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final String? planID = state.planModelObj?.planID;
final response = await http.get(
@@ -53,8 +55,7 @@ class PlanEditBloc extends Bloc<PlanEditEvent, PlanEditState> {
plan: data,
name: TextEditingController(text: data['name']),
details: TextEditingController(text: data['details']),
duration:
TextEditingController(text: data['duration'].toString())));
duration: TextEditingController(text: data['duration'].toString())));
}
} else {
// Один или несколько ключевых полей отсутствуют, обрабатываем ошибку
@@ -75,15 +76,15 @@ class PlanEditBloc extends Bloc<PlanEditEvent, PlanEditState> {
var currentState = state as PlanEditLoadedState;
int duration = int.parse(currentState.duration!.text) * 7;
print(duration);
var response = await sendPlan(currentState.plan, currentState.name?.text,
currentState.details?.text, duration.toString());
var response = await sendPlan(currentState.plan, currentState.name?.text, currentState.details?.text, duration.toString());
if (response.statusCode == 200) {
NavigatorService.pushNamed(AppRoutes.planScreen,
arguments: currentState.plan?['id'].toString());
AppRouter.navigatorKey.currentContext?.goNamed(
'planScreen',
extra: currentState.plan?['id'].toString(),
);
} else {
print(
'Failed to edit blog post. Status Code: ${response.statusCode} ${response.body}');
print('Failed to edit blog post. Status Code: ${response.statusCode} ${response.body}');
}
}
}

View File

@@ -2,18 +2,16 @@ import 'bloc/plan_edit_bloc.dart';
import 'models/plan_edit_model.dart';
import 'package:eatvio_app/core/app_export.dart';
import 'package:flutter/material.dart';
import 'package:eatvio_app/widgets/custom_bottom_bar.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
class PlanEditScreen extends StatelessWidget {
const PlanEditScreen({Key? key}) : super(key: key);
static Widget builder(BuildContext context) {
final planID = ModalRoute.of(context)!.settings.arguments;
return BlocProvider<PlanEditBloc>(
create: (context) => PlanEditBloc(PlanEditState(
planModelObj: PlanEditModel(planID: planID.toString())))
..add(PlanEditInitialEvent()),
create: (context) => PlanEditBloc(PlanEditState(planModelObj: PlanEditModel(planID: planID.toString())))..add(PlanEditInitialEvent()),
child: PlanEditScreen());
}
@@ -41,42 +39,44 @@ class PlanEditScreen extends StatelessWidget {
_buildPlanEdit(BuildContext context, PlanEditLoadedState state) {
mediaQueryData = MediaQuery.of(context);
return Scaffold(
resizeToAvoidBottomInset: false,
body: SafeArea(
child: Container(
width: double.maxFinite,
decoration: AppDecoration.fillGray,
child: ListView(children: [
Container(
margin: EdgeInsets.only(
left: 16.h,
right: 20.h,
bottom: 10.h,
),
padding: EdgeInsets.all(12.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: Column(mainAxisSize: MainAxisSize.min, children: [
return SafeArea(
child: Scaffold(
resizeToAvoidBottomInset: false,
body: Container(
width: double.maxFinite,
decoration: AppDecoration.fillGray,
child: ListView(
children: [
SizedBox(height: 30.v),
Container(
margin: EdgeInsets.only(
left: 16.h,
right: 20.h,
bottom: 10.h,
),
padding: EdgeInsets.all(12.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CachedNetworkImage(
imageUrl: dotenv.env['STORAGE_URL']! +
(state.plan?['image'] ?? ''),
imageUrl: dotenv.env['STORAGE_URL']! + (state.plan?['image'] ?? ''),
imageBuilder: (context, imageProvider) => CircleAvatar(
backgroundImage: imageProvider,
radius: 25,
),
placeholder: (context, url) =>
CircularProgressIndicator(),
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => CircleAvatar(
child: Icon(Icons.person),
backgroundColor: Colors.white,
@@ -96,18 +96,16 @@ class PlanEditScreen extends StatelessWidget {
SizedBox(height: 16.0),
// Здесь нужно добавить виджет для загрузки картинки
ElevatedButton(
onPressed: () =>
context.read<PlanEditBloc>().add(EditPlan()),
onPressed: () => context.read<PlanEditBloc>().add(EditPlan()),
child: Text('Сохранить'),
)
]),
)
]),
),
), bottomNavigationBar: _buildBottomBar(context));
],
),
)
],
),
),
),
);
}
}
_buildBottomBar(BuildContext context) {
return CustomBottomBar(onChanged: (BottomBarEnum type) {});
}

View File

@@ -1,6 +1,7 @@
//import 'package:eatvio_app/presentation/auth/auth_screen/auth_screen.dart';
import 'dart:async';
import 'package:eatvio_app/app_router.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import '/core/app_export.dart';
@@ -10,6 +11,7 @@ import 'dart:convert';
import 'package:hive/hive.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:image_picker/image_picker.dart';
import 'package:go_router/go_router.dart';
part 'plan_select_event.dart';
part 'plan_select_state.dart';
@@ -28,9 +30,7 @@ class PlanSelectBloc extends Bloc<PlanSelectEvent, PlanSelectState> {
final box = await Hive.openBox('token');
final token = box.get('token');
if (token == null) {
NavigatorService.popAndPushNamed(
AppRoutes.authScreen,
); // Если токен есть, запускаем приложение
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final response = await http.get(
Uri.parse(dotenv.env['SERVER_ROOT']! + '/plans/index'),

View File

@@ -1,10 +1,11 @@
import 'package:go_router/go_router.dart';
import 'bloc/plan_select_bloc.dart';
import 'models/plan_select_model.dart';
import 'package:eatvio_app/core/app_export.dart';
import 'package:eatvio_app/widgets/app_bar/appbar_subtitle.dart';
import 'package:eatvio_app/widgets/app_bar/appbar_leading_image.dart';
import 'package:eatvio_app/widgets/app_bar/custom_app_bar.dart';
import 'package:eatvio_app/widgets/custom_bottom_bar.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter/material.dart';
@@ -12,10 +13,7 @@ import 'package:flutter/material.dart';
class PlanSelectScreen extends StatelessWidget {
static Widget builder(BuildContext context) {
return BlocProvider<PlanSelectBloc>(
create: (context) => PlanSelectBloc(
PlanSelectState(myProfileModelObj: PlanSelectModel()))
..add(PlanSelectInitialEvent()),
child: PlanSelectScreen());
create: (context) => PlanSelectBloc(PlanSelectState(myProfileModelObj: PlanSelectModel()))..add(PlanSelectInitialEvent()), child: PlanSelectScreen());
}
Widget build(BuildContext context) {
@@ -23,90 +21,92 @@ class PlanSelectScreen extends StatelessWidget {
builder: (context, state) {
if (state is PlanSelectInitialState) {
// Это начальное состояние, данные ещё не загружены.
return CircularProgressIndicator(
color: Colors.white,
backgroundColor: Colors.white,
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
} else if (state is PlanSelectLoadedState) {
// Данные загружены, можно строить экран.
return _buildSetingsScreen(context, state);
} else {
// Другие состояния, если они есть.
return Container(); // Пустой контейнер для необработанных состояний.
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
},
);
}
_buildSetingsScreen(BuildContext context, PlanSelectLoadedState state) {
mediaQueryData = MediaQuery.of(context);
return Scaffold(
backgroundColor: Colors.grey[200],
resizeToAvoidBottomInset: true,
body: SafeArea(
child: SingleChildScrollView(
child: Container(
width: double.maxFinite,
decoration: BoxDecoration(color: Colors.grey[200]),
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(height: 10),
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: CustomAppBar(
height: 54.v,
leadingWidth: 28.h,
leading: AppbarLeadingImage(
imagePath: ImageConstant.imgArrowLeft,
margin: EdgeInsets.only(
left: 16.h, top: 15.v, bottom: 15.v),
onTap: onTapBack(context),
appBar: AppBar(
toolbarHeight: 0,
automaticallyImplyLeading: false,
),
backgroundColor: Colors.grey[200],
resizeToAvoidBottomInset: true,
body: SingleChildScrollView(
child: Container(
width: double.maxFinite,
decoration: BoxDecoration(color: Colors.grey[200]),
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(height: 10),
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
title: Padding(
padding: EdgeInsets.only(left: 20.h),
child: Row(
children: [
AppbarSubtitle(
text: "Планы",
margin: EdgeInsets.only(
left: 80.h, top: 12.v, bottom: 9.v),
],
),
child: CustomAppBar(
height: 54.v,
leadingWidth: 28.h,
leading: AppbarLeadingImage(
imagePath: ImageConstant.imgArrowLeft,
margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v),
onTap: () => GoRouter.of(context).pop(),
),
title: Padding(
padding: EdgeInsets.only(left: 20.h),
child: Row(
children: [
AppbarSubtitle(
text: "Планы",
margin: EdgeInsets.only(left: 80.h, top: 12.v, bottom: 9.v),
),
Spacer(),
Padding(
padding: const EdgeInsets.only(right: 20.0),
child: GestureDetector(
onTap: () => GoRouter.of(context).goNamed('planCreateScreen'),
child: Icon(Icons.create_outlined),
),
Spacer(),
Padding(
padding: const EdgeInsets.only(right: 20.0),
child: GestureDetector(
onTap: () => Navigator.pushNamed(
context,
AppRoutes.planCreateScreen,
),
child: Icon(Icons.create_outlined)),
)
],
),
)
],
),
),
),
SizedBox(height: 20),
_buildPlanList(context, state)
],
),
),
SizedBox(height: 20),
_buildPlanList(context, state)
],
),
),
),
),
),
bottomNavigationBar: _buildBottomBar(context),
);
@@ -119,51 +119,51 @@ class PlanSelectScreen extends StatelessWidget {
itemBuilder: (context, index) {
final post = state.posts?[index];
return Container(
margin: EdgeInsets.only(bottom: 10),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
margin: EdgeInsets.only(bottom: 10),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: ListTile(
leading: CachedNetworkImage(
imageUrl: dotenv.env['STORAGE_URL']! + post['image'],
imageBuilder: (context, imageProvider) => CircleAvatar(
backgroundImage: imageProvider,
radius: 25,
),
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => CircleAvatar(
child: Icon(Icons.person),
backgroundColor: Colors.white,
radius: 25,
),
),
child: ListTile(
leading: CachedNetworkImage(
imageUrl: dotenv.env['STORAGE_URL']! + post['image'],
imageBuilder: (context, imageProvider) => CircleAvatar(
backgroundImage: imageProvider,
radius: 25,
),
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => CircleAvatar(
child: Icon(Icons.person),
backgroundColor: Colors.white,
radius: 25,
),
title: Text(
post?['name'] ?? '',
style: CustomTextStyles.titleMediumSemiBold,
),
subtitle: Text(post?['details'] ?? ''),
onTap: () => GoRouter.of(context).goNamed(
'planScreen',
extra: post?['id'].toString(),
),
trailing: GestureDetector(
onTap: () => GoRouter.of(context).goNamed(
'planEditScreen',
extra: post?['id'].toString(),
),
title: Text(
post?['name'] ?? '',
style: CustomTextStyles.titleMediumSemiBold,
),
subtitle: Text(post?['details'] ?? ''),
onTap: () => Navigator.pushNamed(
context,
AppRoutes.planScreen,
arguments: post?['id'].toString(),
),
trailing: GestureDetector(
onTap: () => Navigator.pushNamed(
context,
AppRoutes.planEditScreen,
arguments: post?['id'].toString(),
),
child: Icon(Icons.edit)),
));
child: Icon(Icons.edit),
),
),
);
},
);
}
@@ -174,11 +174,7 @@ class PlanSelectScreen extends StatelessWidget {
};
}
_buildBottomBar(BuildContext context) {
return CustomBottomBar(onChanged: (BottomBarEnum type) {});
}
onTapArrowLeft(BuildContext context) {
NavigatorService.goBack();
GoRouter.of(context).pop();
}
}

View File

@@ -1,5 +1,6 @@
import 'dart:async';
import 'package:eatvio_app/app_router.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
@@ -8,7 +9,10 @@ import 'package:http/http.dart' as http;
import 'dart:convert';
import '/core/app_export.dart';
import 'package:eatvio_app/presentation/plans/plan_show_screen/models/plan_show_model.dart';
import 'package:go_router/go_router.dart';
part 'plan_show_event.dart';
part 'plan_show_state.dart';
/// A bloc that manages the state of a Plan according to the event that is dispatched to it.
@@ -26,9 +30,7 @@ class PlanBloc extends Bloc<PlanEvent, PlanState> {
final box = await Hive.openBox('token');
final token = box.get('token');
if (token == null) {
NavigatorService.popAndPushNamed(
AppRoutes.authScreen,
);
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final String? planID = state.planModelObj?.planID;
final response = await http.get(
@@ -42,9 +44,7 @@ class PlanBloc extends Bloc<PlanEvent, PlanState> {
if (data is Map<String, dynamic>) {
if (state is PlanLoadedState) {
var currentState = state as PlanLoadedState;
emit(currentState.copyWith(
planModelObj: currentState.planModelObj,
plan: currentState.plan));
emit(currentState.copyWith(planModelObj: currentState.planModelObj, plan: currentState.plan));
} else {
emit(PlanLoadedState(plan: data));
}
@@ -62,14 +62,11 @@ class PlanBloc extends Bloc<PlanEvent, PlanState> {
}
}
FutureOr<void> _deleteEntry(
DeleteEntry event, Emitter<PlanState> emit) async {
FutureOr<void> _deleteEntry(DeleteEntry event, Emitter<PlanState> emit) async {
final box = await Hive.openBox('token');
final token = box.get('token');
if (token == null) {
NavigatorService.popAndPushNamed(
AppRoutes.authScreen,
);
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final int id = event.id;
final response = await http.post(
@@ -79,7 +76,7 @@ class PlanBloc extends Bloc<PlanEvent, PlanState> {
},
);
if (response.statusCode == 200) {
NavigatorService.pushNamed(AppRoutes.planSelectScreen);
AppRouter.navigatorKey.currentContext?.goNamed('planSelectScreen');
} else {
// Статус ответа не равен 200, обрабатываем ошибку
emit(PlanErrorState('Ошибка: ${response.statusCode}'));

View File

@@ -1,20 +1,20 @@
import 'package:go_router/go_router.dart';
import 'bloc/plan_show_bloc.dart';
import 'models/plan_show_model.dart';
import 'package:eatvio_app/core/app_export.dart';
import 'package:flutter/material.dart';
import 'package:eatvio_app/widgets/custom_bottom_bar.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
class PlanScreen extends StatelessWidget {
const PlanScreen({Key? key}) : super(key: key);
static Widget builder(BuildContext context) {
final planID = ModalRoute.of(context)!.settings.arguments;
return BlocProvider<PlanBloc>(
create: (context) => PlanBloc(
PlanState(planModelObj: PlanModel(planID: planID.toString())))
..add(PlanInitialEvent()),
child: PlanScreen());
create: (context) => PlanBloc(PlanState(planModelObj: PlanModel(planID: planID.toString())))..add(PlanInitialEvent()), child: PlanScreen());
}
Widget build(BuildContext context) {
@@ -41,92 +41,77 @@ class PlanScreen extends StatelessWidget {
_buildPlan(BuildContext context, PlanLoadedState state) {
mediaQueryData = MediaQuery.of(context);
return Scaffold(
resizeToAvoidBottomInset: false,
body: SafeArea(
child: Container(
width: double.maxFinite,
decoration: AppDecoration.fillGray,
child: ListView(
children: [
SizedBox(height: 30.v),
Container(
margin: EdgeInsets.only(
left: 16.h,
right: 20.h,
bottom: 10.h,
return SafeArea(
child: Scaffold(
resizeToAvoidBottomInset: false,
body: Container(
width: double.maxFinite,
decoration: AppDecoration.fillGray,
child: ListView(
children: [
SizedBox(height: 30.v),
Container(
margin: EdgeInsets.only(
left: 16.h,
right: 20.h,
bottom: 10.h,
),
padding: EdgeInsets.all(12.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
padding: EdgeInsets.all(12.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
],
),
child: Column(mainAxisSize: MainAxisSize.min, children: [
CachedNetworkImage(
imageUrl: dotenv.env['STORAGE_URL']! + (state.plan?['image'] ?? ''),
imageBuilder: (context, imageProvider) => CircleAvatar(
backgroundImage: imageProvider,
radius: 25,
),
child: Column(mainAxisSize: MainAxisSize.min, children: [
CachedNetworkImage(
imageUrl: dotenv.env['STORAGE_URL']! +
(state.plan?['image'] ?? ''),
imageBuilder: (context, imageProvider) => CircleAvatar(
backgroundImage: imageProvider,
radius: 25,
),
placeholder: (context, url) =>
CircularProgressIndicator(),
errorWidget: (context, url, error) => CircleAvatar(
child: Icon(Icons.person),
backgroundColor: Colors.white,
radius: 25,
),
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => CircleAvatar(
child: Icon(Icons.person),
backgroundColor: Colors.white,
radius: 25,
),
),
SizedBox(height: 16.0),
Text(state.plan?['name'], style: CustomTextStyles.titleMediumBlack),
SizedBox(height: 8.0),
Text(
state.plan?['details'],
),
SizedBox(height: 16.0),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
ElevatedButton(
onPressed: () {
// Действие для редактирования
},
child: Text('Редактировать'),
),
SizedBox(height: 16.0),
Text(state.plan?['name'],
style: CustomTextStyles.titleMediumBlack),
SizedBox(height: 8.0),
Text(
state.plan?['details'],
ElevatedButton(
onPressed: () => context.read<PlanBloc>().add(DeleteEntry(state.plan?['id'])),
child: Text('Удалить'),
),
SizedBox(height: 16.0),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
ElevatedButton(
onPressed: () {
// Действие для редактирования
},
child: Text('Редактировать'),
),
ElevatedButton(
onPressed: () => context
.read<PlanBloc>()
.add(DeleteEntry(state.plan?['id'])),
child: Text('Удалить'),
),
],
)
]),
],
)
],
),
),
), bottomNavigationBar: _buildBottomBar(context));
}
///Handling route based on bottom click actions
String getCurrentRoute(BottomBarEnum type) {
switch (type) {
case BottomBarEnum.tf:
return AppRoutes.planScreen;
default:
return "/";
}
]),
)
],
),
),
),
);
}
///Handling page based on route
@@ -135,19 +120,15 @@ class PlanScreen extends StatelessWidget {
String currentRoute,
) {
switch (currentRoute) {
case AppRoutes.planScreen:
case '/plan':
return PlanScreen.builder(context);
default:
return PlanScreen();
}
}
_buildBottomBar(BuildContext context) {
return CustomBottomBar(onChanged: (BottomBarEnum type) {});
}
/// Navigates to the previous screen.
onTapArrowleftone(BuildContext context) {
NavigatorService.goBack();
GoRouter.of(context).pop();
}
}

View File

@@ -1,6 +1,7 @@
//import 'package:eatvio_app/presentation/auth/auth_screen/auth_screen.dart';
import 'dart:async';
import 'package:eatvio_app/app_router.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import '/core/app_export.dart';
@@ -10,6 +11,7 @@ import 'dart:convert';
import 'package:hive/hive.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:image_picker/image_picker.dart';
import 'package:go_router/go_router.dart';
part 'my_profile_event.dart';
part 'my_profile_state.dart';
@@ -29,9 +31,7 @@ class MyProfileBloc extends Bloc<MyProfileEvent, MyProfileState> {
final box = await Hive.openBox('token');
final token = box.get('token');
if (token == null) {
NavigatorService.popAndPushNamed(
AppRoutes.authScreen,
); // Если токен есть, запускаем приложение
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
//final token =
// "9|laravel_sanctum_KFXbccDcWEvTPfJmV99EQv1ne AggmpR2TidVGDPN34fa201a";

View File

@@ -1,8 +1,9 @@
import 'package:go_router/go_router.dart';
import 'bloc/my_profile_bloc.dart';
import 'models/my_profile_model.dart';
import 'package:eatvio_app/core/app_export.dart';
import 'package:eatvio_app/widgets/app_bar/appbar_subtitle.dart';
import 'package:eatvio_app/widgets/custom_bottom_bar.dart';
import 'package:eatvio_app/widgets/custom_text_form_field.dart';
import 'package:carousel_slider/carousel_slider.dart';
import 'package:image_picker/image_picker.dart';
@@ -19,10 +20,13 @@ import 'dart:convert';
class MyProfileScreen extends StatelessWidget {
static Widget builder(BuildContext context) {
return BlocProvider<MyProfileBloc>(
create: (context) =>
MyProfileBloc(MyProfileState(myProfileModelObj: MyProfileModel()))
..add(MyProfileInitialEvent()),
child: MyProfileScreen());
create: (context) => MyProfileBloc(
MyProfileState(
myProfileModelObj: MyProfileModel(),
),
)..add(MyProfileInitialEvent()),
child: MyProfileScreen(),
);
}
Widget build(BuildContext context) {
@@ -30,9 +34,10 @@ class MyProfileScreen extends StatelessWidget {
builder: (context, state) {
if (state is MyProfileInitialState) {
// Это начальное состояние, данные ещё не загружены.
return CircularProgressIndicator(
color: Colors.white,
backgroundColor: Colors.white,
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
} else if (state is MyProfileLoadedState) {
// Данные загружены, можно строить экран.
@@ -42,7 +47,11 @@ class MyProfileScreen extends StatelessWidget {
return Center(child: Text(state.errorMessage));
} else {
// Другие состояния, если они есть.
return Container(); // Пустой контейнер для необработанных состояний.
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
},
);
@@ -51,89 +60,91 @@ class MyProfileScreen extends StatelessWidget {
_buildMyProfileScreen(BuildContext context, MyProfileLoadedState state) {
mediaQueryData = MediaQuery.of(context);
return Scaffold(
backgroundColor: Colors.grey[200],
resizeToAvoidBottomInset: true,
body: SafeArea(
child: SingleChildScrollView(
child: Container(
width: double.maxFinite,
decoration: BoxDecoration(color: Colors.grey[200]),
child: Padding(
padding: EdgeInsets.all(16),
child: Column(children: [
Container(
padding: EdgeInsets.symmetric(
horizontal: 24.h,
vertical: 12.v,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius:
const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: _buildInfo(context, state)),
SizedBox(height: 15.v),
Container(
padding: EdgeInsets.symmetric(
horizontal: 24.h,
vertical: 12.v,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius:
const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: _buildCarousel(context, state)),
SizedBox(height: 15.v),
if (!state.plans!.isEmpty)
Container(
padding: EdgeInsets.symmetric(
horizontal: 24.h,
vertical: 12.v,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(
Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: _buildPlans(
context,
state.plans
?.map((plan) => plan['name'])
.toList(),
state.plans
?.map((plan) => plan['image'])
.toList(),
state.plans
?.map((plan) => plan['state'])
.toList(),
)),
SizedBox(height: 5.v),
]))))), bottomNavigationBar: _buildBottomBar(context));
appBar: AppBar(
toolbarHeight: 0,
automaticallyImplyLeading: false,
),
backgroundColor: Colors.grey[200],
resizeToAvoidBottomInset: true,
body: SingleChildScrollView(
child: Container(
width: double.maxFinite,
decoration: BoxDecoration(color: Colors.grey[200]),
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
SizedBox(height: 30.v),
Container(
padding: EdgeInsets.symmetric(
horizontal: 24.h,
vertical: 12.v,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: _buildInfo(context, state),
),
SizedBox(height: 15.v),
Container(
padding: EdgeInsets.symmetric(
horizontal: 24.h,
vertical: 12.v,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: _buildCarousel(context, state)),
SizedBox(height: 15.v),
if (!state.plans!.isEmpty)
Container(
padding: EdgeInsets.symmetric(
horizontal: 24.h,
vertical: 12.v,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: _buildPlans(
context,
state.plans?.map((plan) => plan['name']).toList(),
state.plans?.map((plan) => plan['image']).toList(),
state.plans?.map((plan) => plan['state']).toList(),
)),
SizedBox(height: 5.v),
],
),
),
),
),
);
}
/// Section Widget
@@ -143,8 +154,7 @@ class MyProfileScreen extends StatelessWidget {
GestureDetector(
onTap: () => addImage(context),
child: CachedNetworkImage(
imageUrl: dotenv.env['STORAGE_URL']! +
(state.user?['profile_photo_path'] ?? ''),
imageUrl: dotenv.env['STORAGE_URL']! + (state.user?['profile_photo_path'] ?? ''),
imageBuilder: (context, imageProvider) => CircleAvatar(
backgroundImage: imageProvider,
radius: 60,
@@ -176,14 +186,10 @@ class MyProfileScreen extends StatelessWidget {
onTapSettings(BuildContext context) {
return () {
Navigator.pushNamed(context, AppRoutes.mySettings);
GoRouter.of(context).goNamed('mySettingsScreen');
};
}
_buildBottomBar(BuildContext context) {
return CustomBottomBar(onChanged: (BottomBarEnum type) {});
}
_buildCarousel(BuildContext contex, MyProfileLoadedState state) {
return CarouselSlider(
options: CarouselOptions(height: 320.0),
@@ -204,12 +210,7 @@ class MyProfileScreen extends StatelessWidget {
SizedBox(height: 50.v),
_buildChart(
context,
state.chartPoints?.values
.map<int>((e) => e['weight'])
.toList()
.map((i) => i.toDouble())
.toList() ??
[0.0, 0.0, 0.0, 0.0],
state.chartPoints?.values.map<int>((e) => e['weight']).toList().map((i) => i.toDouble()).toList() ?? [0.0, 0.0, 0.0, 0.0],
state.chartDates,
)
]));
@@ -224,13 +225,7 @@ class MyProfileScreen extends StatelessWidget {
SizedBox(height: 50.v),
_buildChart(
context,
_bodyMassIndex(
state.chartPoints?.values
.map<int>((e) => e['weight'])
.toList()
.map((i) => i.toDouble())
.toList() ??
[0.0, 0.0, 0.0, 0.0],
_bodyMassIndex(state.chartPoints?.values.map<int>((e) => e['weight']).toList().map((i) => i.toDouble()).toList() ?? [0.0, 0.0, 0.0, 0.0],
state.userDetails?['height'].toInt() ?? 160),
state.chartDates),
]);
@@ -245,8 +240,7 @@ class MyProfileScreen extends StatelessWidget {
),
),
SizedBox(height: 30.v),
_buildCircumferenceChart(
context, state.circumferences, state.chartDates),
_buildCircumferenceChart(context, state.circumferences, state.chartDates),
_buildChartLegend()
]));
default:
@@ -290,8 +284,7 @@ class MyProfileScreen extends StatelessWidget {
);
}
_buildChart(
BuildContext context, List<double> chartPoints, List<String>? dates) {
_buildChart(BuildContext context, List<double> chartPoints, List<String>? dates) {
List<FlSpot> spots = [];
for (int i = 0; i < 4; i++) {
spots.add(
@@ -299,8 +292,7 @@ class MyProfileScreen extends StatelessWidget {
);
}
Widget bottomTitleWidgets(
double value, TitleMeta meta, double chartWidth, List<String>? dates) {
Widget bottomTitleWidgets(double value, TitleMeta meta, double chartWidth, List<String>? dates) {
if (value % 1 != 0) {
return Container();
}
@@ -312,8 +304,7 @@ class MyProfileScreen extends StatelessWidget {
int index = value.toInt();
if (index >= 0 && index < dates!.length) {
// Проверка на допустимый индекс в массиве dates
String formattedDate =
DateFormat('dd.MM').format(DateTime.parse(dates[index]));
String formattedDate = DateFormat('dd.MM').format(DateTime.parse(dates[index]));
return SideTitleWidget(
axisSide: meta.axisSide,
space: 16,
@@ -370,18 +361,15 @@ class MyProfileScreen extends StatelessWidget {
dotData: const FlDotData(show: false),
),
],
minY: chartPoints.reduce(
(value, element) => value < element ? value : element),
maxY: chartPoints.reduce(
(value, element) => value > element ? value : element),
minY: chartPoints.reduce((value, element) => value < element ? value : element),
maxY: chartPoints.reduce((value, element) => value > element ? value : element),
minX: 1,
maxX: 4,
titlesData: FlTitlesData(
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) =>
leftTitleWidgets(value, meta, constraints.maxWidth),
getTitlesWidget: (value, meta) => leftTitleWidgets(value, meta, constraints.maxWidth),
reservedSize: 56,
),
drawBelowEverything: true,
@@ -392,8 +380,7 @@ class MyProfileScreen extends StatelessWidget {
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) => bottomTitleWidgets(
value, meta, constraints.maxWidth, dates),
getTitlesWidget: (value, meta) => bottomTitleWidgets(value, meta, constraints.maxWidth, dates),
reservedSize: 36,
interval: 1,
),
@@ -453,11 +440,9 @@ class MyProfileScreen extends StatelessWidget {
};
}
_buildCircumferenceChart(
BuildContext context, chartPoints, List<String>? dates) {
_buildCircumferenceChart(BuildContext context, chartPoints, List<String>? dates) {
// Определение осей графика
final chestCircumferences =
chartPoints.values.map((e) => e['chest_circumference']).toList();
final chestCircumferences = chartPoints.values.map((e) => e['chest_circumference']).toList();
List<FlSpot> spotsChest = [];
for (int i = 0; i < 4; i++) {
chestCircumferences[i] = chestCircumferences[i] * 10 / 10;
@@ -465,8 +450,7 @@ class MyProfileScreen extends StatelessWidget {
FlSpot((i * 10 / 10) + 1, chestCircumferences[i] * 10 / 10),
);
}
final waistCircumferences =
chartPoints.values.map((e) => e['waist_circumference']).toList();
final waistCircumferences = chartPoints.values.map((e) => e['waist_circumference']).toList();
List<FlSpot> spotsWaist = [];
for (int i = 0; i < 4; i++) {
waistCircumferences[i] = waistCircumferences[i] * 10 / 10;
@@ -474,8 +458,7 @@ class MyProfileScreen extends StatelessWidget {
FlSpot((i * 10 / 10) + 1, waistCircumferences[i] * 10 / 10),
);
}
final hipCircumferences =
chartPoints.values.map((e) => e['hip_circumference']).toList();
final hipCircumferences = chartPoints.values.map((e) => e['hip_circumference']).toList();
List<FlSpot> spotsHip = [];
for (int i = 0; i < 4; i++) {
hipCircumferences[i] = hipCircumferences[i] * 10 / 10;
@@ -484,14 +467,9 @@ class MyProfileScreen extends StatelessWidget {
);
}
final allCircumferences = [
...chestCircumferences,
...hipCircumferences,
...waistCircumferences
];
final allCircumferences = [...chestCircumferences, ...hipCircumferences, ...waistCircumferences];
Widget bottomTitleWidgets(
double value, TitleMeta meta, double chartWidth, List<String>? dates) {
Widget bottomTitleWidgets(double value, TitleMeta meta, double chartWidth, List<String>? dates) {
if (value % 1 != 0) {
return Container();
}
@@ -503,8 +481,7 @@ class MyProfileScreen extends StatelessWidget {
int index = value.toInt();
if (index >= 0 && index < dates!.length) {
// Проверка на допустимый индекс в массиве dates
String formattedDate =
DateFormat('dd.MM').format(DateTime.parse(dates[index]));
String formattedDate = DateFormat('dd.MM').format(DateTime.parse(dates[index]));
return SideTitleWidget(
axisSide: meta.axisSide,
space: 16,
@@ -583,18 +560,15 @@ class MyProfileScreen extends StatelessWidget {
dotData: const FlDotData(show: false),
),
],
minY: allCircumferences.reduce(
(value, element) => value < element ? value : element),
maxY: allCircumferences.reduce(
(value, element) => value > element ? value : element),
minY: allCircumferences.reduce((value, element) => value < element ? value : element),
maxY: allCircumferences.reduce((value, element) => value > element ? value : element),
minX: 1,
maxX: 4,
titlesData: FlTitlesData(
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) =>
leftTitleWidgets(value, meta, constraints.maxWidth),
getTitlesWidget: (value, meta) => leftTitleWidgets(value, meta, constraints.maxWidth),
reservedSize: 56,
),
drawBelowEverything: true,
@@ -605,8 +579,7 @@ class MyProfileScreen extends StatelessWidget {
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) => bottomTitleWidgets(
value, meta, constraints.maxWidth, dates),
getTitlesWidget: (value, meta) => bottomTitleWidgets(value, meta, constraints.maxWidth, dates),
reservedSize: 36,
interval: 1,
),
@@ -661,125 +634,80 @@ class MyProfileScreen extends StatelessWidget {
}
/// Section Widget
_buildPlans(BuildContext context, List<dynamic>? plans,
List<dynamic>? imagePaths, List<dynamic>? states) {
_buildPlans(BuildContext context, List<dynamic>? plans, List<dynamic>? imagePaths, List<dynamic>? states) {
return Padding(
padding: EdgeInsets.only(left: 7.h),
child: Column(children: [
Padding(
padding: EdgeInsets.only(left: 15.h),
child: Text("plans_complete".tr,
style: CustomTextStyles.titleMediumBlack
.copyWith(color: appTheme.black900))),
child: Text("plans_complete".tr, style: CustomTextStyles.titleMediumBlack.copyWith(color: appTheme.black900))),
SizedBox(height: 5.v),
Container(
decoration: AppDecoration.outlineBlueGray
.copyWith(borderRadius: BorderRadiusStyle.roundedBorder5),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 105.v,
width: 343.h,
child:
Stack(alignment: Alignment.bottomCenter, children: [
Align(
alignment: Alignment.topCenter,
child: ListView.builder(
itemCount: plans?.length ?? 0,
itemBuilder: (context, i) {
String state = states?[i];
var boxColor = appTheme.greenA700;
switch (states?[i]) {
case 'active':
boxColor = appTheme.greenA700;
break;
case 'archived': // График ИМТ
boxColor = appTheme.gray400;
break;
case 'draft':
boxColor = appTheme.deepOrange900;
break;
case 'in_progress': //График обхватов
boxColor = appTheme.lightGreen300;
break;
}
return SizedBox(
height: 40.v,
width: 343.h,
child: Stack(
alignment: Alignment.bottomRight,
children: [
Align(
alignment:
Alignment.topCenter,
child: Container(
margin: EdgeInsets.only(
bottom: 11.v),
padding:
EdgeInsets.symmetric(
horizontal: 6.h,
vertical: 4.v),
decoration: AppDecoration
.fillGray
.copyWith(
borderRadius:
BorderRadiusStyle
.roundedBorder5),
child: Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
Padding(
padding: EdgeInsets
.only(
bottom: 1
.v),
child: Text(
plans![i],
style: CustomTextStyles
.titleSmallBluegray800Bold)),
Container(
height: 19.v,
width: 100.h,
decoration: BoxDecoration(
color:
boxColor,
borderRadius:
BorderRadius.circular(
5.h)))
]))),
Align(
alignment: Alignment.center,
child: Container(
margin: EdgeInsets.only(
bottom: 12.h),
child: Image.network(
dotenv.env[
'STORAGE_URL']! +
imagePaths?[i],
height: 25.adaptSize,
width: 25.adaptSize,
))),
Align(
alignment:
Alignment.bottomRight,
child: Container(
width: 70.h,
margin: EdgeInsets.only(
bottom: 14.h,
right: 22.h),
child: Text("$state".tr,
maxLines: 3,
overflow: TextOverflow
.ellipsis,
style: theme.textTheme
.titleSmall)))
]));
})),
]))
]))
decoration: AppDecoration.outlineBlueGray.copyWith(borderRadius: BorderRadiusStyle.roundedBorder5),
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
SizedBox(
height: 105.v,
width: 343.h,
child: Stack(alignment: Alignment.bottomCenter, children: [
Align(
alignment: Alignment.topCenter,
child: ListView.builder(
itemCount: plans?.length ?? 0,
itemBuilder: (context, i) {
String state = states?[i];
var boxColor = appTheme.greenA700;
switch (states?[i]) {
case 'active':
boxColor = appTheme.greenA700;
break;
case 'archived': // График ИМТ
boxColor = appTheme.gray400;
break;
case 'draft':
boxColor = appTheme.deepOrange900;
break;
case 'in_progress': //График обхватов
boxColor = appTheme.lightGreen300;
break;
}
return SizedBox(
height: 40.v,
width: 343.h,
child: Stack(alignment: Alignment.bottomRight, children: [
Align(
alignment: Alignment.topCenter,
child: Container(
margin: EdgeInsets.only(bottom: 11.v),
padding: EdgeInsets.symmetric(horizontal: 6.h, vertical: 4.v),
decoration: AppDecoration.fillGray.copyWith(borderRadius: BorderRadiusStyle.roundedBorder5),
child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Padding(
padding: EdgeInsets.only(bottom: 1.v),
child: Text(plans![i], style: CustomTextStyles.titleSmallBluegray800Bold)),
Container(
height: 19.v,
width: 100.h,
decoration: BoxDecoration(color: boxColor, borderRadius: BorderRadius.circular(5.h)))
]))),
Align(
alignment: Alignment.center,
child: Container(
margin: EdgeInsets.only(bottom: 12.h),
child: Image.network(
dotenv.env['STORAGE_URL']! + imagePaths?[i],
height: 25.adaptSize,
width: 25.adaptSize,
))),
Align(
alignment: Alignment.bottomRight,
child: Container(
width: 70.h,
margin: EdgeInsets.only(bottom: 14.h, right: 22.h),
child: Text("$state".tr, maxLines: 3, overflow: TextOverflow.ellipsis, style: theme.textTheme.titleSmall)))
]));
})),
]))
]))
]));
}
@@ -807,44 +735,38 @@ class MyProfileScreen extends StatelessWidget {
children: [
Padding(
padding: EdgeInsets.symmetric(vertical: 5.v),
child: Text("chest_circumference".tr,
style: theme.textTheme.bodyLarge),
child: Text("chest_circumference".tr, style: theme.textTheme.bodyLarge),
),
SizedBox(height: 5.v),
CustomTextFormField(
controller: state.chestCircumferenceController,
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter
.digitsOnly, // Ограничение ввода только цифрами
FilteringTextInputFormatter.digitsOnly, // Ограничение ввода только цифрами
],
),
Padding(
padding: EdgeInsets.symmetric(vertical: 15.v),
child: Text("waist_circumference".tr,
style: theme.textTheme.bodyLarge),
child: Text("waist_circumference".tr, style: theme.textTheme.bodyLarge),
),
SizedBox(height: 5.v),
CustomTextFormField(
controller: state.waistCircumferenceController,
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter
.digitsOnly, // Ограничение ввода только цифрами
FilteringTextInputFormatter.digitsOnly, // Ограничение ввода только цифрами
],
),
Padding(
padding: EdgeInsets.symmetric(vertical: 15.v),
child: Text("hip_circumference".tr,
style: theme.textTheme.bodyLarge),
child: Text("hip_circumference".tr, style: theme.textTheme.bodyLarge),
),
SizedBox(height: 5.v),
CustomTextFormField(
controller: state.hipCircumferenceController,
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter
.digitsOnly, // Ограничение ввода только цифрами
FilteringTextInputFormatter.digitsOnly, // Ограничение ввода только цифрами
],
),
],
@@ -863,8 +785,7 @@ class MyProfileScreen extends StatelessWidget {
),
],
backgroundColor: Colors.white,
titlePadding: EdgeInsets.all(
20.0), // Можете настроить отступы по вашему желанию
titlePadding: EdgeInsets.all(20.0), // Можете настроить отступы по вашему желанию
);
},
);
@@ -894,14 +815,10 @@ class MyProfileScreen extends StatelessWidget {
);
if (response.statusCode == 201) {
Navigator.pushNamed(
context,
AppRoutes.myProfile,
);
GoRouter.of(context).go('/my-profile-screen');
} else {
// Ошибка от сервера
print(
"Ошибка при отправке данных. Код ответа: ${response.statusCode}");
print("Ошибка при отправке данных. Код ответа: ${response.statusCode}");
}
} catch (e) {
// Обработка ошибок при отправке запроса
@@ -987,14 +904,10 @@ class MyProfileScreen extends StatelessWidget {
);
if (response.statusCode == 201) {
Navigator.pushNamed(
context,
AppRoutes.myProfile,
);
GoRouter.of(context).go('/my-profile-screen');
} else {
// Ошибка от сервера
print(
"Ошибка при отправке данных. Код ответа: ${response.statusCode}");
print("Ошибка при отправке данных. Код ответа: ${response.statusCode}");
}
} catch (e) {
// Обработка ошибок при отправке запроса

View File

@@ -1,10 +1,11 @@
import 'package:go_router/go_router.dart';
import 'bloc/my_profile_bloc.dart';
import 'models/my_profile_model.dart';
import 'package:eatvio_app/core/app_export.dart';
import 'package:eatvio_app/widgets/app_bar/appbar_subtitle.dart';
import 'package:eatvio_app/widgets/app_bar/appbar_leading_image.dart';
import 'package:eatvio_app/widgets/app_bar/custom_app_bar.dart';
import 'package:eatvio_app/widgets/custom_bottom_bar.dart';
import 'package:eatvio_app/widgets/custom_about_list.dart';
import 'package:eatvio_app/widgets/custom_expandable_list.dart';
import 'package:eatvio_app/widgets/custom_notification_screen.dart';
@@ -15,10 +16,7 @@ import 'package:flutter/material.dart';
class MySettingsScreen extends StatelessWidget {
static Widget builder(BuildContext context) {
return BlocProvider<MyProfileBloc>(
create: (context) =>
MyProfileBloc(MyProfileState(myProfileModelObj: MyProfileModel()))
..add(MyProfileInitialEvent()),
child: MySettingsScreen());
create: (context) => MyProfileBloc(MyProfileState(myProfileModelObj: MyProfileModel()))..add(MyProfileInitialEvent()), child: MySettingsScreen());
}
Widget build(BuildContext context) {
@@ -26,151 +24,151 @@ class MySettingsScreen extends StatelessWidget {
builder: (context, state) {
if (state is MyProfileInitialState) {
// Это начальное состояние, данные ещё не загружены.
return CircularProgressIndicator(
color: Colors.white,
backgroundColor: Colors.white,
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
} else if (state is MyProfileLoadedState) {
// Данные загружены, можно строить экран.
return _buildSetingsScreen(context, state);
} else {
// Другие состояния, если они есть.
return Container(); // Пустой контейнер для необработанных состояний.
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
},
);
}
_buildSetingsScreen(BuildContext context, MyProfileLoadedState state) {
mediaQueryData = MediaQuery.of(context);
return Scaffold(
backgroundColor: Colors.grey[200],
resizeToAvoidBottomInset: true,
body: SafeArea(
child: SingleChildScrollView(
child: Container(
width: double.maxFinite,
decoration: BoxDecoration(color: Colors.grey[200]),
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(height: 30),
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius:
const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
return Scaffold(
appBar: AppBar(
toolbarHeight: 0,
automaticallyImplyLeading: false,
),
backgroundColor: Colors.grey[200],
resizeToAvoidBottomInset: true,
body: SingleChildScrollView(
child: Container(
width: double.maxFinite,
decoration: BoxDecoration(color: Colors.grey[200]),
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(height: 30),
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: CustomAppBar(
height: 54.v,
leadingWidth: 28.h,
leading: AppbarLeadingImage(
imagePath: ImageConstant.imgArrowLeft,
margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v),
onTap: () => GoRouter.of(context).pop(),
),
child: CustomAppBar(
height: 54.v,
leadingWidth: 28.h,
leading: AppbarLeadingImage(
imagePath: ImageConstant.imgArrowLeft,
margin: EdgeInsets.only(
left: 16.h, top: 15.v, bottom: 15.v),
onTap: onTapBack(context)),
title: Padding(
padding: EdgeInsets.only(left: 20.h),
child: Row(children: [
AppbarSubtitle(
text: state.user?['name'],
margin: EdgeInsets.only(
left: 80.h, top: 12.v, bottom: 9.v))
])),
)),
SizedBox(height: 20),
Container(
padding: EdgeInsets.symmetric(
horizontal: 24.h,
vertical: 12.v,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: Column(
children: [
SectionTitle('Язык'),
LanguageDropdown(),
],
),
title: Padding(
padding: EdgeInsets.only(left: 20.h),
child: Row(children: [AppbarSubtitle(text: state.user?['name'], margin: EdgeInsets.only(left: 80.h, top: 12.v, bottom: 9.v))])),
)),
SizedBox(height: 20),
Container(
padding: EdgeInsets.symmetric(
horizontal: 24.h,
vertical: 12.v,
),
SizedBox(height: 20),
Container(
padding: EdgeInsets.symmetric(
horizontal: 24.h,
vertical: 12.v,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: Column(
children: [
SectionTitle('Уведомления'),
NotificationSwitch('Уведомление об обновлениях'),
NotificationSwitch('Напоминания о заполнении'),
],
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
SizedBox(height: 20),
Container(
padding: EdgeInsets.symmetric(
horizontal: 24.h,
vertical: 12.v,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: Column(
children: [
SectionTitle('Дополнительная информация'),
ExpandableList('Персональные данные'),
AboutList('О нас', ['ООО Итвио', '2023-н.в.']),
],
),
child: Column(
children: [
SectionTitle('Язык'),
LanguageDropdown(),
],
),
],
),
),
SizedBox(height: 20),
Container(
padding: EdgeInsets.symmetric(
horizontal: 24.h,
vertical: 12.v,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: Column(
children: [
SectionTitle('Уведомления'),
NotificationSwitch('Уведомление об обновлениях'),
NotificationSwitch('Напоминания о заполнении'),
],
),
),
SizedBox(height: 20),
Container(
padding: EdgeInsets.symmetric(
horizontal: 24.h,
vertical: 12.v,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: Column(
children: [
SectionTitle('Дополнительная информация'),
ExpandableList('Персональные данные'),
AboutList('О нас', ['ООО Итвио', '2023-н.в.']),
],
),
),
],
),
),
),
),
),
bottomNavigationBar: _buildBottomBar(context),
);
@@ -182,11 +180,7 @@ class MySettingsScreen extends StatelessWidget {
};
}
_buildBottomBar(BuildContext context) {
return CustomBottomBar(onChanged: (BottomBarEnum type) {});
}
onTapArrowLeft(BuildContext context) {
NavigatorService.goBack();
GoRouter.of(context).pop();
}
}

View File

@@ -1,5 +1,6 @@
import 'dart:async';
import 'package:eatvio_app/app_router.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
@@ -8,6 +9,7 @@ import 'package:http/http.dart' as http;
import 'dart:convert';
import '/core/app_export.dart';
import 'package:eatvio_app/presentation/recipe/recipe_create_screen/models/recipe_create_model.dart';
import 'package:go_router/go_router.dart';
part 'recipe_create_event.dart';
part 'recipe_create_state.dart';
@@ -16,6 +18,9 @@ class RecipeCreateBloc extends Bloc<RecipeCreateEvent, RecipeCreateState> {
RecipeCreateBloc(RecipeCreateState initialState) : super(initialState) {
on<RecipeCreateInitialEvent>(_onInitialize);
on<CreateRecipe>(_createRecipe);
on<AddStep>(_addStep);
on<RemoveStep>(_removeStep);
on<ReloadScreen>(_reloadScreen);
}
_onInitialize(
@@ -25,31 +30,72 @@ class RecipeCreateBloc extends Bloc<RecipeCreateEvent, RecipeCreateState> {
final box = await Hive.openBox('token');
final token = box.get('token');
if (token == null) {
NavigatorService.popAndPushNamed(
AppRoutes.authScreen,
);
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
if (state is RecipeCreateLoadedState) {
var currentState = state as RecipeCreateLoadedState;
emit(currentState.copyWith(
recipeModelObj: currentState.recipeModelObj,
title: currentState.title,
token: currentState.token,
description: currentState.description,
video_url: currentState.video_url,
steps: currentState.steps));
steps: currentState.steps,
stepDescriptions: currentState.stepDescriptions,
tags: currentState.tags));
} else {
final Map<String, dynamic> step = {
'description': '',
'image': '',
};
emit(RecipeCreateLoadedState(
token: token,
title: TextEditingController(),
description: TextEditingController(),
video_url: TextEditingController(),
tags: TextEditingController(),
stepDescriptions: [TextEditingController()],
steps: [step]));
}
}
FutureOr<void> _addStep(
AddStep event, Emitter<RecipeCreateState> emit) async {
if (state is RecipeCreateLoadedState) {
var currentState = state as RecipeCreateLoadedState;
List<dynamic>? updatedSteps = currentState.steps;
List<TextEditingController>? updatedStepDescriptions =
currentState.stepDescriptions;
updatedSteps!.add({
'description': '',
'image': '',
});
updatedStepDescriptions!.add(TextEditingController());
emit(currentState.copyWith(
steps: updatedSteps, stepDescriptions: updatedStepDescriptions));
await _reloadScreen(event, emit);
}
}
FutureOr<void> _removeStep(
RemoveStep event, Emitter<RecipeCreateState> emit) async {
if (state is RecipeCreateLoadedState) {
var currentState = state as RecipeCreateLoadedState;
List<dynamic>? updatedSteps = currentState.steps;
List<TextEditingController>? updatedStepDescriptions =
currentState.stepDescriptions;
updatedSteps!.removeAt(event.id);
updatedStepDescriptions!.removeAt(event.id);
emit(currentState.copyWith(
steps: updatedSteps, stepDescriptions: updatedStepDescriptions));
await _reloadScreen(event, emit);
}
}
FutureOr<void> _createRecipe(
CreateRecipe event, Emitter<RecipeCreateState> emit) async {
if (state is RecipeCreateLoadedState) {
@@ -59,10 +105,11 @@ class RecipeCreateBloc extends Bloc<RecipeCreateEvent, RecipeCreateState> {
currentState.title?.text,
currentState.video_url?.text,
currentState.description?.text,
currentState.steps,
);
if (response.statusCode == 200) {
NavigatorService.pushNamed(AppRoutes.recipeSelectScreen);
AppRouter.navigatorKey.currentContext?.goNamed('recipeSelectScreen');
} else {
print(
'Failed to create blog post. Status Code: ${response.statusCode} ${response.body}');
@@ -70,17 +117,14 @@ class RecipeCreateBloc extends Bloc<RecipeCreateEvent, RecipeCreateState> {
}
}
FutureOr<http.Response> sendRecipe(
String? title,
String? video_url,
String? description,
) async {
FutureOr<http.Response> sendRecipe(String? title, String? video_url,
String? description, List<dynamic>? steps) async {
String url = '${dotenv.env['SERVER_ROOT']!}/recipe/create';
final Map<String, dynamic> credentials = {
'title': title,
'description': description,
'video_url': video_url,
'steps': steps,
'status': 'active',
};
@@ -95,4 +139,26 @@ class RecipeCreateBloc extends Bloc<RecipeCreateEvent, RecipeCreateState> {
return response;
}
FutureOr<void> _reloadScreen(
RecipeCreateEvent? event, Emitter<RecipeCreateState> emit) async {
print('Reloading..');
final box = await Hive.openBox('token');
final token = box.get('token');
if (token == null) {
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
if (state is RecipeCreateLoadedState) {
var currentState = state as RecipeCreateLoadedState;
emit(currentState.copyWith(
recipeModelObj: currentState.recipeModelObj,
title: currentState.title,
token: currentState.token,
description: currentState.description,
video_url: currentState.video_url,
steps: currentState.steps,
stepDescriptions: currentState.stepDescriptions,
tags: currentState.tags));
}
}
}

View File

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

View File

@@ -24,33 +24,52 @@ class RecipeCreateState extends Equatable {
class RecipeCreateLoadedState extends RecipeCreateState {
RecipeCreateLoadedState(
{this.recipeModelObj,
this.token,
this.title,
this.description,
this.video_url,
this.tags,
this.stepDescriptions,
this.steps});
RecipeCreateModel? recipeModelObj;
String? token;
TextEditingController? title;
TextEditingController? description;
TextEditingController? video_url;
TextEditingController? tags;
List<dynamic>? steps;
List<TextEditingController>? stepDescriptions;
@override
List<Object?> get props =>
[recipeModelObj, title, description, video_url, steps];
List<Object?> get props => [
recipeModelObj,
title,
token,
description,
video_url,
steps,
tags,
stepDescriptions
];
RecipeCreateLoadedState copyWith({
RecipeCreateModel? recipeModelObj,
String? token,
TextEditingController? title,
TextEditingController? description,
TextEditingController? video_url,
TextEditingController? tags,
List<dynamic>? steps,
List<TextEditingController>? stepDescriptions,
}) {
return RecipeCreateLoadedState(
recipeModelObj: recipeModelObj ?? this.recipeModelObj,
title: title ?? this.title,
description: description ?? this.description,
video_url: video_url ?? this.video_url,
steps: steps ?? this.steps,
);
recipeModelObj: recipeModelObj ?? this.recipeModelObj,
title: title ?? this.title,
description: description ?? this.description,
video_url: video_url ?? this.video_url,
token: token ?? this.token,
steps: steps ?? this.steps,
tags: tags ?? this.tags,
stepDescriptions: stepDescriptions ?? this.stepDescriptions);
}
}

View File

@@ -1,3 +1,5 @@
import 'package:go_router/go_router.dart';
import 'bloc/recipe_create_bloc.dart';
import 'models/recipe_create_model.dart';
import 'package:eatvio_app/core/app_export.dart';
@@ -5,18 +7,17 @@ import 'package:flutter/material.dart';
import 'package:eatvio_app/widgets/app_bar/custom_app_bar.dart';
import 'package:eatvio_app/widgets/app_bar/appbar_leading_image.dart';
import 'package:eatvio_app/widgets/app_bar/appbar_subtitle.dart';
import 'package:eatvio_app/widgets/custom_bottom_bar.dart';
import 'package:eatvio_app/widgets/custom_text_form_field.dart';
import 'package:eatvio_app/widgets/custom_outlined_button.dart';
class RecipeCreateScreen extends StatelessWidget {
const RecipeCreateScreen({Key? key}) : super(key: key);
static Widget builder(BuildContext context) {
final recipeID = ModalRoute.of(context)!.settings.arguments;
return BlocProvider<RecipeCreateBloc>(
create: (context) => RecipeCreateBloc(RecipeCreateState(
recipeModelObj: RecipeCreateModel(recipeID: recipeID.toString())))
..add(RecipeCreateInitialEvent()),
create: (context) =>
RecipeCreateBloc(RecipeCreateState(recipeModelObj: RecipeCreateModel(recipeID: recipeID.toString())))..add(RecipeCreateInitialEvent()),
child: RecipeCreateScreen());
}
@@ -51,8 +52,13 @@ class RecipeCreateScreen extends StatelessWidget {
}
}
return SafeArea(
return RefreshIndicator(
onRefresh: refreshPage(context),
child: Scaffold(
appBar: AppBar(
toolbarHeight: 0,
automaticallyImplyLeading: false,
),
resizeToAvoidBottomInset: false,
body: Container(
width: double.maxFinite,
@@ -69,8 +75,7 @@ class RecipeCreateScreen extends StatelessWidget {
SizedBox(height: 20.v),
_buildAdditional(context, state)
]),
),
bottomNavigationBar: _buildBottomBar(context)));
)));
}
_buldAppBar(BuildContext context) {
@@ -162,45 +167,50 @@ class RecipeCreateScreen extends StatelessWidget {
]),
);
}
}
_buildAddPhoto(BuildContext context, RecipeCreateLoadedState state) {
bool isLoading = false;
return GestureDetector(
child: Stack(
children: [
if (!isLoading)
Container(
padding: EdgeInsets.symmetric(
horizontal: 40.h,
vertical: 80.v,
),
decoration: AppDecoration.fillGray.copyWith(
borderRadius: BorderRadiusStyle.roundedBorder5,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: EdgeInsets.only(
left: 7.h,
top: 5.v,
refreshPage(BuildContext context) {
return () async {
context.read<RecipeCreateBloc>().add(ReloadScreen());
};
}
_buildAddPhoto(BuildContext context, RecipeCreateLoadedState state) {
bool isLoading = false;
return GestureDetector(
child: Stack(
children: [
if (!isLoading)
Container(
padding: EdgeInsets.symmetric(
horizontal: 40.h,
vertical: 80.v,
),
decoration: AppDecoration.fillGray.copyWith(
borderRadius: BorderRadiusStyle.roundedBorder5,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: EdgeInsets.only(
left: 7.h,
top: 5.v,
),
child: Text(
"Добавить фото",
selectionColor: appTheme.blueA400,
),
),
child: Text(
"Добавить фото",
selectionColor: appTheme.blueA400,
),
),
Spacer(),
Icon(
Icons.photo_library,
color: appTheme.blueGray300,
)
],
Spacer(),
Icon(
Icons.photo_library,
color: appTheme.blueGray300,
)
],
),
),
),
/*if (isLoading)
/*if (isLoading)
Container(
margin: EdgeInsets.symmetric(horizontal: 10.h),
padding: EdgeInsets.symmetric(
@@ -216,161 +226,166 @@ _buildAddPhoto(BuildContext context, RecipeCreateLoadedState state) {
.blueGray300), // Замените на свой прелоадер при необходимости
),
),*/
],
),
);
}
_buildStep(BuildContext context, RecipeCreateLoadedState state, int i) {
bool isLastStep = i == state.steps!.length - 1;
bool isFirstStep = i == 0;
return Container(
margin: EdgeInsets.only(
left: 16.h,
right: 16.h,
bottom: 10.h,
),
padding: EdgeInsets.all(12.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: Column(mainAxisSize: MainAxisSize.min, children: [
Align(
alignment: Alignment.centerLeft,
child: Text(
"Шаг ${i + 1}",
style: CustomTextStyles.titleMediumSemiBold,
),
],
),
SizedBox(height: 8.0),
CustomTextFormField(
controller: state.description,
hintText: "Введите описание шага",
maxLines: 4,
fillColor: appTheme.gray50,
borderDecoration: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.h),
borderSide: BorderSide(
color: appTheme.gray50,
width: 1,
);
}
_buildStep(BuildContext context, RecipeCreateLoadedState state, int i) {
bool isLastStep = i == state.steps!.length - 1;
bool isFirstStep = i == 0;
return Container(
margin: EdgeInsets.only(
left: 16.h,
right: 16.h,
bottom: 10.h,
),
padding: EdgeInsets.all(12.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
),
],
),
SizedBox(height: 16.0),
_buildAddPhoto(context, state),
if (!isFirstStep)
Row(
children: [
Flexible(
flex: 5,
child: CustomOutlinedButton(
text: "Добавить шаг",
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
),
),
SizedBox(width: 8), // Add some spacing between the buttons
Flexible(
flex: 1,
child: CustomOutlinedButton(
text: "х",
onPressed: () => Navigator.pushNamed(
context,
AppRoutes.blogPostCreateScreen,
),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
),
),
],
),
if (isLastStep) // Добавляем кнопку "Добавить шаг" только для последнего шага
SizedBox(height: 16.0),
CustomOutlinedButton(
text: "Добавить шаг",
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
),
]),
);
}
_buildAdditional(BuildContext context, RecipeCreateLoadedState state) {
return Container(
margin: EdgeInsets.only(
left: 16.h,
right: 16.h,
bottom: 10.h,
),
padding: EdgeInsets.all(12.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: Column(mainAxisSize: MainAxisSize.min, children: [
Align(alignment: Alignment.centerLeft, child: Text("Теги")),
CustomTextFormField(
controller: state.title,
hintText: "Введите теги, через запятую",
fillColor: appTheme.gray50,
borderDecoration: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.h),
borderSide: BorderSide(
color: appTheme.gray50,
width: 1,
),
),
),
SizedBox(height: 8.0),
Align(
child: Column(mainAxisSize: MainAxisSize.min, children: [
Align(
alignment: Alignment.centerLeft,
child: Text("Ссылка на видео (необязательно)")),
CustomTextFormField(
controller: state.description,
hintText: "YouTube",
fillColor: appTheme.gray50,
borderDecoration: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.h),
borderSide: BorderSide(
color: appTheme.gray50,
width: 1,
child: Text(
"Шаг ${i + 1}",
style: CustomTextStyles.titleMediumSemiBold,
),
),
SizedBox(height: 8.0),
CustomTextFormField(
controller: state.stepDescriptions?[i],
hintText: "Введите описание шага",
maxLines: 4,
fillColor: appTheme.gray50,
borderDecoration: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.h),
borderSide: BorderSide(
color: appTheme.gray50,
width: 1,
),
),
),
SizedBox(height: 16.0),
_buildAddPhoto(context, state),
SizedBox(height: 16.0),
if (isFirstStep && isLastStep)
CustomOutlinedButton(
onPressed: () => context.read<RecipeCreateBloc>().add(AddStep()),
text: "Добавить шаг",
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
),
if (isLastStep && !isFirstStep) // Добавляем кнопку "Добавить шаг" только для последнего шага
Column(
children: [
SizedBox(height: 16.0),
Row(
children: [
Flexible(
flex: 5,
child: CustomOutlinedButton(
onPressed: () => context.read<RecipeCreateBloc>().add(AddStep()),
text: "Добавить шаг",
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
),
),
SizedBox(width: 8), // Add some spacing between the buttons
Flexible(
flex: 1,
child: CustomOutlinedButton(
text: "х",
onPressed: () => context.read<RecipeCreateBloc>().add(RemoveStep(i)),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
),
),
],
),
],
),
if (!isLastStep && !isFirstStep)
CustomOutlinedButton(
onPressed: () => context.read<RecipeCreateBloc>().add(RemoveStep(i)),
text: "x",
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
),
]),
);
}
_buildAdditional(BuildContext context, RecipeCreateLoadedState state) {
return Container(
margin: EdgeInsets.only(
left: 16.h,
right: 16.h,
bottom: 10.h,
),
SizedBox(height: 16.0),
CustomOutlinedButton(
text: "Добавить рецепт",
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
padding: EdgeInsets.all(12.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
]),
);
}
child: Column(mainAxisSize: MainAxisSize.min, children: [
Align(alignment: Alignment.centerLeft, child: Text("Теги")),
CustomTextFormField(
controller: state.tags,
hintText: "Введите теги, через запятую",
fillColor: appTheme.gray50,
borderDecoration: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.h),
borderSide: BorderSide(
color: appTheme.gray50,
width: 1,
),
),
),
SizedBox(height: 8.0),
Align(alignment: Alignment.centerLeft, child: Text("Ссылка на видео (необязательно)")),
CustomTextFormField(
controller: state.video_url,
hintText: "YouTube",
fillColor: appTheme.gray50,
borderDecoration: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.h),
borderSide: BorderSide(
color: appTheme.gray50,
width: 1,
),
),
),
SizedBox(height: 16.0),
CustomOutlinedButton(
text: "Добавить рецепт",
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
),
]),
);
}
_buildBottomBar(BuildContext context) {
return CustomBottomBar(onChanged: (BottomBarEnum type) {});
}
Function onTapBack(BuildContext context) {
return () {
onTapArrowLeft(context);
};
}
Function onTapBack(BuildContext context) {
return () {
onTapArrowLeft(context);
};
}
onTapArrowLeft(BuildContext context) {
NavigatorService.goBack();
onTapArrowLeft(BuildContext context) {
GoRouter.of(context).pop();
}
}

View File

@@ -1,5 +1,6 @@
import 'dart:async';
import 'package:eatvio_app/app_router.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
@@ -8,7 +9,10 @@ import 'package:http/http.dart' as http;
import 'dart:convert';
import '/core/app_export.dart';
import 'package:eatvio_app/presentation/recipe/recipe_edit_screen/models/recipe_edit_model.dart';
import 'package:go_router/go_router.dart';
part 'recipe_edit_event.dart';
part 'recipe_edit_state.dart';
/// A bloc that manages the state of a RecipeEdit according to the event that is dispatched to it.
@@ -16,6 +20,9 @@ class RecipeEditBloc extends Bloc<RecipeEditEvent, RecipeEditState> {
RecipeEditBloc(RecipeEditState initialState) : super(initialState) {
on<RecipeEditInitialEvent>(_onInitialize);
on<EditRecipe>(_editRecipe);
on<AddStep>(_addStep);
on<RemoveStep>(_removeStep);
on<ReloadScreen>(_reloadScreen);
}
_onInitialize(
@@ -26,9 +33,7 @@ class RecipeEditBloc extends Bloc<RecipeEditEvent, RecipeEditState> {
final box = await Hive.openBox('token');
final token = box.get('token');
if (token == null) {
NavigatorService.popAndPushNamed(
AppRoutes.authScreen,
);
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final String? recipeID = state.recipeModelObj?.recipeID;
final response = await http.get(
@@ -44,21 +49,47 @@ class RecipeEditBloc extends Bloc<RecipeEditEvent, RecipeEditState> {
var currentState = state as RecipeEditLoadedState;
emit(currentState.copyWith(
recipeModelObj: currentState.recipeModelObj,
recipe: currentState.recipe,
title: currentState.title,
token: currentState.token,
recipe: currentState.recipe,
description: currentState.description,
video_url: currentState.video_url));
video_url: currentState.video_url,
steps: currentState.steps,
stepDescriptions: currentState.stepDescriptions,
tags: currentState.tags));
} else {
final Map<String, dynamic> defaultStep = {
'description': '',
'image': '',
};
List<dynamic> steps = data['steps'];
List<TextEditingController> stepDescriptions = [];
if (data['steps'] == null) {
stepDescriptions.add(TextEditingController());
data['steps'].add(defaultStep);
} else {
for (var step in steps) {
stepDescriptions.add(TextEditingController(text: step['description']));
}
}
emit(RecipeEditLoadedState(
recipe: data,
title: TextEditingController(text: data['title']),
description: TextEditingController(text: data['description']),
video_url: TextEditingController(text: data['video_url'])));
token: token,
recipe: data,
title: TextEditingController(text: data['title']),
description: TextEditingController(text: data['description']),
video_url: TextEditingController(text: data['video_url']),
tagsController: TextEditingController(),
tags: await _fetchTags(data['id']),
stepDescriptions: stepDescriptions,
steps: steps,
));
}
} else {
// Один или несколько ключевых полей отсутствуют, обрабатываем ошибку
emit(RecipeEditErrorState(
'Некоторые необходимые данные отсутствуют.'));
emit(RecipeEditErrorState('Некоторые необходимые данные отсутствуют.'));
}
} else {
// Статус ответа не равен 200, обрабатываем ошибку
@@ -70,8 +101,7 @@ class RecipeEditBloc extends Bloc<RecipeEditEvent, RecipeEditState> {
}
}
FutureOr<void> _editRecipe(
EditRecipe event, Emitter<RecipeEditState> emit) async {
FutureOr<void> _editRecipe(EditRecipe event, Emitter<RecipeEditState> emit) async {
if (state is RecipeEditLoadedState) {
var currentState = state as RecipeEditLoadedState;
@@ -83,15 +113,47 @@ class RecipeEditBloc extends Bloc<RecipeEditEvent, RecipeEditState> {
);
if (response.statusCode == 200) {
NavigatorService.pushNamed(AppRoutes.recipeScreen,
arguments: currentState.recipe?['id'].toString());
AppRouter.navigatorKey.currentContext?.goNamed(
'recipeScreen',
extra: currentState.recipe?['id'].toString(),
);
} else {
print(
'Failed to edit blog post. Status Code: ${response.statusCode} ${response.body}');
print('Failed to edit blog post. Status Code: ${response.statusCode} ${response.body}');
}
}
}
FutureOr<void> _addStep(AddStep event, Emitter<RecipeEditState> emit) async {
if (state is RecipeEditLoadedState) {
var currentState = state as RecipeEditLoadedState;
List<dynamic>? updatedSteps = currentState.steps;
List<TextEditingController>? updatedStepDescriptions = currentState.stepDescriptions;
updatedSteps!.add({
'description': '',
'image': '',
});
updatedStepDescriptions!.add(TextEditingController());
emit(currentState.copyWith(steps: updatedSteps, stepDescriptions: updatedStepDescriptions));
await _reloadScreen(event, emit);
}
}
FutureOr<void> _removeStep(RemoveStep event, Emitter<RecipeEditState> emit) async {
if (state is RecipeEditLoadedState) {
var currentState = state as RecipeEditLoadedState;
List<dynamic>? updatedSteps = currentState.steps;
List<TextEditingController>? updatedStepDescriptions = currentState.stepDescriptions;
updatedSteps!.removeAt(event.id);
updatedStepDescriptions!.removeAt(event.id);
emit(currentState.copyWith(steps: updatedSteps, stepDescriptions: updatedStepDescriptions));
await _reloadScreen(event, emit);
}
}
FutureOr<http.Response> sendRecipe(
Map<String, dynamic>? recipe,
String? title,
@@ -123,4 +185,44 @@ class RecipeEditBloc extends Bloc<RecipeEditEvent, RecipeEditState> {
return response;
}
_fetchTags(int id) async {
final box = await Hive.openBox('token');
final token = box.get('token');
if (token == null) {
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final response = await http.get(
Uri.parse(dotenv.env['SERVER_ROOT']! + '/recipe/getTags/$id'),
headers: {
'Authorization': 'Bearer $token',
},
);
final data = jsonDecode(response.body);
return data;
}
FutureOr<void> _reloadScreen(RecipeEditEvent? event, Emitter<RecipeEditState> emit) async {
print('Reloading..');
final box = await Hive.openBox('token');
final token = box.get('token');
if (token == null) {
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
if (state is RecipeEditLoadedState) {
var currentState = state as RecipeEditLoadedState;
emit(currentState.copyWith(
recipeModelObj: currentState.recipeModelObj,
recipe: currentState.recipe,
title: currentState.title,
token: currentState.token,
description: currentState.description,
video_url: currentState.video_url,
steps: currentState.steps,
stepDescriptions: currentState.stepDescriptions,
tags: currentState.tags));
}
}
}

View File

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

View File

@@ -24,32 +24,52 @@ class RecipeEditState extends Equatable {
class RecipeEditLoadedState extends RecipeEditState {
RecipeEditLoadedState(
{this.recipeModelObj,
this.token,
this.recipe,
this.title,
this.description,
this.video_url});
this.video_url,
this.tagsController,
this.stepDescriptions,
this.tags,
this.steps});
RecipeEditModel? recipeModelObj;
Map<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,
);
}
}

View File

@@ -1,10 +1,16 @@
import 'package:go_router/go_router.dart';
import 'bloc/recipe_edit_bloc.dart';
import 'models/recipe_edit_model.dart';
import 'package:eatvio_app/core/app_export.dart';
import 'package:flutter/material.dart';
import 'package:eatvio_app/widgets/custom_bottom_bar.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:eatvio_app/core/app_export.dart';
import 'package:eatvio_app/widgets/app_bar/custom_app_bar.dart';
import 'package:eatvio_app/widgets/app_bar/appbar_leading_image.dart';
import 'package:eatvio_app/widgets/app_bar/appbar_subtitle.dart';
import 'package:eatvio_app/widgets/custom_text_form_field.dart';
import 'package:eatvio_app/widgets/custom_outlined_button.dart';
class RecipeEditScreen extends StatelessWidget {
const RecipeEditScreen({Key? key}) : super(key: key);
@@ -41,78 +47,389 @@ class RecipeEditScreen extends StatelessWidget {
_buildRecipeEdit(BuildContext context, RecipeEditLoadedState state) {
mediaQueryData = MediaQuery.of(context);
return Scaffold(
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: AppDecoration.fillGray,
child: ListView(children: [
Container(
margin: EdgeInsets.only(
left: 16.h,
right: 20.h,
bottom: 10.h,
),
padding: EdgeInsets.all(12.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: Column(mainAxisSize: MainAxisSize.min, children: [
CachedNetworkImage(
imageUrl: dotenv.env['STORAGE_URL']! +
(state.recipe?['image'] ?? ''),
imageBuilder: (context, imageProvider) => CircleAvatar(
backgroundImage: imageProvider,
radius: 25,
),
placeholder: (context, url) =>
CircularProgressIndicator(),
errorWidget: (context, url, error) => CircleAvatar(
child: Icon(Icons.person),
backgroundColor: Colors.white,
radius: 25,
),
),
SizedBox(height: 16.0),
TextFormField(
controller: state.title,
decoration: InputDecoration(labelText: 'Заголовок'),
),
SizedBox(height: 16.0),
TextFormField(
controller: state.description,
decoration: InputDecoration(labelText: 'Описание'),
),
SizedBox(height: 16.0),
TextFormField(
controller: state.video_url,
decoration: InputDecoration(labelText: 'Ссылка'),
),
SizedBox(height: 16.0),
// Здесь нужно добавить виджет для загрузки картинки
ElevatedButton(
onPressed: () =>
context.read<RecipeEditBloc>().add(EditRecipe()),
child: Text('Сохранить'),
)
]),
)
decoration: BoxDecoration(color: Colors.grey[200]),
child: ListView(children: [
_buldAppBar(context),
SizedBox(height: 20.v),
_buildRecipeInfo(state, context),
SizedBox(height: 20.v),
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: steps,
),
SizedBox(height: 20.v),
_buildAdditional(context, state)
]),
)));
}
_buldAppBar(BuildContext context) {
return Container(
margin: EdgeInsets.all(16.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: CustomAppBar(
height: 54.v,
leadingWidth: 28.h,
leading: AppbarLeadingImage(
imagePath: ImageConstant.imgArrowLeft,
margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v),
onTap: onTapBack(context),
),
title: Align(
alignment: Alignment.center,
child: AppbarSubtitle(
text: "Редактирование рецепта",
)),
),
);
}
_buildRecipeInfo(RecipeEditLoadedState state, BuildContext context) {
return Container(
margin: EdgeInsets.only(
left: 16.h,
right: 16.h,
bottom: 10.h,
),
padding: EdgeInsets.all(12.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: Column(mainAxisSize: MainAxisSize.min, children: [
SizedBox(height: 16.0),
Align(alignment: Alignment.centerLeft, child: Text("Изображение")),
if (state.recipe?['image'] != null)
CachedNetworkImage(
imageUrl: dotenv.env['STORAGE_URL']! + (state.recipe?['image']),
width: double
.infinity, // Установите ширину равной ширине родительского элемента
height: 200.0,
fit: BoxFit.cover,
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
)
else
_buildAddPhoto(context, state),
SizedBox(height: 16.0),
Align(alignment: Alignment.centerLeft, child: Text("Название рецепта")),
CustomTextFormField(
controller: state.title,
hintText: "Введите название рецепта",
fillColor: appTheme.gray50,
borderDecoration: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.h),
borderSide: BorderSide(
color: appTheme.gray50,
width: 1,
),
), bottomNavigationBar: _buildBottomBar(context));
),
),
SizedBox(height: 16.0),
Align(alignment: Alignment.centerLeft, child: Text("Описание")),
CustomTextFormField(
controller: state.description,
hintText: "Введите описание рецепта",
maxLines: 4,
fillColor: appTheme.gray50,
borderDecoration: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.h),
borderSide: BorderSide(
color: appTheme.gray50,
width: 1,
),
),
),
SizedBox(height: 16.0),
]),
);
}
refreshPage(BuildContext context) {
return () async {
context.read<RecipeEditBloc>().add(ReloadScreen());
};
}
_buildAddPhoto(BuildContext context, RecipeEditLoadedState state) {
bool isLoading = false;
return GestureDetector(
child: Stack(
children: [
if (!isLoading)
Container(
padding: EdgeInsets.symmetric(
horizontal: 40.h,
vertical: 80.v,
),
decoration: AppDecoration.fillGray.copyWith(
borderRadius: BorderRadiusStyle.roundedBorder5,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: EdgeInsets.only(
left: 7.h,
top: 5.v,
),
child: Text(
"Добавить фото",
selectionColor: appTheme.blueA400,
),
),
Spacer(),
Icon(
Icons.photo_library,
color: appTheme.blueGray300,
)
],
),
),
/*if (isLoading)
Container(
margin: EdgeInsets.symmetric(horizontal: 10.h),
padding: EdgeInsets.symmetric(
horizontal: 40.h,
vertical: 80.v,
),
decoration: AppDecoration.fillGray.copyWith(
borderRadius: BorderRadiusStyle.roundedBorder5,
),
child: Center(
child: CircularProgressIndicator(
color: appTheme
.blueGray300), // Замените на свой прелоадер при необходимости
),
),*/
],
),
);
}
_buildStep(BuildContext context, RecipeEditLoadedState state, int i) {
bool isLastStep = i == state.steps!.length - 1;
bool isFirstStep = i == 0;
return Container(
margin: EdgeInsets.only(
left: 16.h,
right: 16.h,
bottom: 10.h,
),
padding: EdgeInsets.all(12.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: Column(mainAxisSize: MainAxisSize.min, children: [
Align(
alignment: Alignment.centerLeft,
child: Text(
"Шаг ${i + 1}",
style: CustomTextStyles.titleMediumSemiBold,
),
),
SizedBox(height: 8.0),
CustomTextFormField(
controller: state.stepDescriptions?[i],
hintText: "Введите описание шага",
maxLines: 4,
fillColor: appTheme.gray50,
borderDecoration: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.h),
borderSide: BorderSide(
color: appTheme.gray50,
width: 1,
),
),
),
SizedBox(height: 16.0),
if (state.steps?[i]['image'] != null)
CachedNetworkImage(
imageUrl: dotenv.env['STORAGE_URL']! + (state.steps?[i]['image']),
width: double
.infinity, // Установите ширину равной ширине родительского элемента
height: 200.0, // Задайте постоянную высоту элемента
fit: BoxFit.cover,
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
)
else
_buildAddPhoto(context, state),
SizedBox(height: 16.0),
if (isFirstStep && isLastStep)
CustomOutlinedButton(
onPressed: () => context.read<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.h,
right: 16.h,
bottom: 10.h,
),
padding: EdgeInsets.all(12.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: Column(mainAxisSize: MainAxisSize.min, children: [
Align(alignment: Alignment.centerLeft, child: Text("Теги")),
_buildEditableTags(state.tags),
SizedBox(height: 8.0),
Align(
alignment: Alignment.centerLeft,
child: Text("Ссылка на видео (необязательно)")),
CustomTextFormField(
controller: state.video_url,
hintText: "YouTube",
fillColor: appTheme.gray50,
borderDecoration: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.h),
borderSide: BorderSide(
color: appTheme.gray50,
width: 1,
),
),
),
SizedBox(height: 16.0),
CustomOutlinedButton(
text: "Добавить рецепт",
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
),
]),
);
}
_buildEditableTags(List<dynamic>? tags) {
String initialText = tags!.map((tag) => tag['name']).join(', ');
return CustomTextFormField(
initialValue: initialText,
fillColor: appTheme.gray50,
borderDecoration: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.h),
borderSide: BorderSide(
color: appTheme.gray50,
width: 1,
),
),
onChanged: _trimTags,
);
}
void _trimTags(newValue) {
List<String> newTagNames =
newValue!.split(',').map((tag) => tag.trim()).toList();
List<Map<String, dynamic>> newTags =
newTagNames.map((tagName) => {'name': tagName}).toList();
print(newTags);
}
Function onTapBack(BuildContext context) {
return () {
onTapArrowLeft(context);
};
}
onTapArrowLeft(BuildContext context) {
GoRouter.of(context).pop();
}
}
_buildBottomBar(BuildContext context) {
return CustomBottomBar(onChanged: (BottomBarEnum type) {});
}

View File

@@ -1,6 +1,6 @@
//import 'package:eatvio_app/presentation/auth/auth_screen/auth_screen.dart';
import 'dart:async';
import 'package:eatvio_app/app_router.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import '/core/app_export.dart';
@@ -10,6 +10,7 @@ import 'dart:convert';
import 'package:hive/hive.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:image_picker/image_picker.dart';
import 'package:go_router/go_router.dart';
part 'recipe_select_event.dart';
part 'recipe_select_state.dart';
@@ -28,9 +29,7 @@ class RecipeSelectBloc extends Bloc<RecipeSelectEvent, RecipeSelectState> {
final box = await Hive.openBox('token');
final token = box.get('token');
if (token == null) {
NavigatorService.popAndPushNamed(
AppRoutes.authScreen,
); // Если токен есть, запускаем приложение
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final response = await http.get(
Uri.parse(dotenv.env['SERVER_ROOT']! + '/recipe/index'),

View File

@@ -1,10 +1,11 @@
import 'package:go_router/go_router.dart';
import 'bloc/recipe_select_bloc.dart';
import 'models/recipe_select_model.dart';
import 'package:eatvio_app/core/app_export.dart';
import 'package:eatvio_app/widgets/app_bar/appbar_subtitle.dart';
import 'package:eatvio_app/widgets/app_bar/appbar_leading_image.dart';
import 'package:eatvio_app/widgets/app_bar/custom_app_bar.dart';
import 'package:eatvio_app/widgets/custom_bottom_bar.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter/material.dart';
@@ -12,9 +13,7 @@ import 'package:flutter/material.dart';
class RecipeSelectScreen extends StatelessWidget {
static Widget builder(BuildContext context) {
return BlocProvider<RecipeSelectBloc>(
create: (context) => RecipeSelectBloc(
RecipeSelectState(myProfileModelObj: RecipeSelectModel()))
..add(RecipeSelectInitialEvent()),
create: (context) => RecipeSelectBloc(RecipeSelectState(myProfileModelObj: RecipeSelectModel()))..add(RecipeSelectInitialEvent()),
child: RecipeSelectScreen());
}
@@ -22,91 +21,91 @@ class RecipeSelectScreen extends StatelessWidget {
return BlocBuilder<RecipeSelectBloc, RecipeSelectState>(
builder: (context, state) {
if (state is RecipeSelectInitialState) {
// Это начальное состояние, данные ещё не загружены.
return CircularProgressIndicator(
color: Colors.white,
backgroundColor: Colors.white,
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
} else if (state is RecipeSelectLoadedState) {
// Данные загружены, можно строить экран.
return _buildRecipeScreen(context, state);
} else {
// Другие состояния, если они есть.
return Container(); // Пустой контейнер для необработанных состояний.
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
},
);
}
_buildRecipeScreen(BuildContext context, RecipeSelectLoadedState state) {
mediaQueryData = MediaQuery.of(context);
return Scaffold(
backgroundColor: Colors.grey[200],
resizeToAvoidBottomInset: true,
body: SafeArea(
child: SingleChildScrollView(
child: Container(
width: double.maxFinite,
decoration: BoxDecoration(color: Colors.grey[200]),
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(height: 10),
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: CustomAppBar(
height: 54.v,
leadingWidth: 28.h,
leading: AppbarLeadingImage(
imagePath: ImageConstant.imgArrowLeft,
margin: EdgeInsets.only(
left: 16.h, top: 15.v, bottom: 15.v),
onTap: onTapBack(context),
return Scaffold(
appBar: AppBar(
toolbarHeight: 0,
automaticallyImplyLeading: false,
),
backgroundColor: Colors.grey[200],
resizeToAvoidBottomInset: true,
body: SingleChildScrollView(
child: Container(
width: double.maxFinite,
decoration: BoxDecoration(color: Colors.grey[200]),
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(height: 10),
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
title: Padding(
padding: EdgeInsets.only(left: 20.h),
child: Row(
children: [
AppbarSubtitle(
text: "Рецепты",
margin: EdgeInsets.only(
left: 80.h, top: 12.v, bottom: 9.v),
],
),
child: CustomAppBar(
height: 54.v,
leadingWidth: 28.h,
leading: AppbarLeadingImage(
imagePath: ImageConstant.imgArrowLeft,
margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v),
onTap: () => GoRouter.of(context).pop(),
),
title: Padding(
padding: EdgeInsets.only(left: 20.h),
child: Row(
children: [
AppbarSubtitle(
text: "Рецепты",
margin: EdgeInsets.only(left: 80.h, top: 12.v, bottom: 9.v),
),
Spacer(),
Padding(
padding: const EdgeInsets.only(right: 20.0),
child: GestureDetector(
onTap: () => GoRouter.of(context).goNamed('recipeCreateScreen'),
child: Icon(Icons.create_outlined),
),
Spacer(),
Padding(
padding: const EdgeInsets.only(right: 20.0),
child: GestureDetector(
onTap: () => Navigator.pushNamed(
context,
AppRoutes.recipeCreateScreen,
),
child: Icon(Icons.create_outlined)),
)
],
),
)
],
),
),
),
SizedBox(height: 20),
_buildRecipeList(context, state),
],
),
),
SizedBox(height: 20),
_buildRecipeList(context, state),
],
),
),
),
),
),
bottomNavigationBar: _buildBottomBar(context),
);
@@ -151,18 +150,17 @@ class RecipeSelectScreen extends StatelessWidget {
style: CustomTextStyles.titleMediumSemiBold,
),
subtitle: Text(post?['description'] ?? ''),
onTap: () => Navigator.pushNamed(
context,
AppRoutes.recipeScreen,
arguments: post?['id'].toString(),
onTap: () => GoRouter.of(context).goNamed(
'recipeScreen',
extra: post?['id'].toString(),
),
trailing: GestureDetector(
onTap: () => Navigator.pushNamed(
context,
AppRoutes.recipeEditScreen,
arguments: post?['id'].toString(),
),
child: Icon(Icons.edit)),
onTap: () => GoRouter.of(context).goNamed(
'recipeEditScreen',
extra: post?['id'].toString(),
),
child: Icon(Icons.edit),
),
));
},
);
@@ -174,11 +172,7 @@ class RecipeSelectScreen extends StatelessWidget {
};
}
_buildBottomBar(BuildContext context) {
return CustomBottomBar(onChanged: (BottomBarEnum type) {});
}
onTapArrowLeft(BuildContext context) {
NavigatorService.goBack();
GoRouter.of(context).pop();
}
}

View File

@@ -1,14 +1,20 @@
import 'dart:async';
import 'package:eatvio_app/app_router.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:hive/hive.dart';
import 'package:http/http.dart' as http;
import 'package:intl/intl.dart';
import 'dart:convert';
import '/core/app_export.dart';
import 'package:eatvio_app/presentation/recipe/recipe_show_screen/models/recipe_show_model.dart';
import 'package:eatvio_app/presentation/feed/blog_post_screen/models/comment_model.dart';
import 'package:go_router/go_router.dart';
part 'recipe_show_event.dart';
part 'recipe_show_state.dart';
/// A bloc that manages the state of a Recipe according to the event that is dispatched to it.
@@ -16,6 +22,7 @@ class RecipeBloc extends Bloc<RecipeEvent, RecipeState> {
RecipeBloc(RecipeState initialState) : super(initialState) {
on<RecipeInitialEvent>(_onInitialize);
on<DeleteEntry>(_deleteEntry);
on<CreateComment>(_createComment);
}
_onInitialize(
@@ -26,9 +33,7 @@ class RecipeBloc extends Bloc<RecipeEvent, RecipeState> {
final box = await Hive.openBox('token');
final token = box.get('token');
if (token == null) {
NavigatorService.popAndPushNamed(
AppRoutes.authScreen,
);
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final String? recipeID = state.recipeModelObj?.recipeID;
final response = await http.get(
@@ -44,9 +49,21 @@ class RecipeBloc extends Bloc<RecipeEvent, RecipeState> {
var currentState = state as RecipeLoadedState;
emit(currentState.copyWith(
recipeModelObj: currentState.recipeModelObj,
recipe: currentState.recipe));
recipe: currentState.recipe,
token: currentState.token,
commentController: currentState.commentController,
comments: currentState.comments,
steps: currentState.steps,
author: currentState.author));
} else {
emit(RecipeLoadedState(recipe: data));
emit(RecipeLoadedState(
token: token,
recipe: data,
steps: data['steps'],
author: await _fetchAuthor(data['id']),
comments: fillCommentList(await _fetchComments(data['id'])),
tags: await _fetchTags(data['id']),
commentController: TextEditingController()));
}
} else {
// Один или несколько ключевых полей отсутствуют, обрабатываем ошибку
@@ -62,14 +79,11 @@ class RecipeBloc extends Bloc<RecipeEvent, RecipeState> {
}
}
FutureOr<void> _deleteEntry(
DeleteEntry event, Emitter<RecipeState> emit) async {
FutureOr<void> _deleteEntry(DeleteEntry event, Emitter<RecipeState> emit) async {
final box = await Hive.openBox('token');
final token = box.get('token');
if (token == null) {
NavigatorService.popAndPushNamed(
AppRoutes.authScreen,
);
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final int id = event.id;
final response = await http.post(
@@ -79,10 +93,109 @@ class RecipeBloc extends Bloc<RecipeEvent, RecipeState> {
},
);
if (response.statusCode == 200) {
NavigatorService.pushNamed(AppRoutes.recipeSelectScreen);
AppRouter.navigatorKey.currentContext?.goNamed('recipeSelectScreen');
} else {
// Статус ответа не равен 200, обрабатываем ошибку
emit(RecipeErrorState('Ошибка: ${response.statusCode}'));
}
}
FutureOr<void> _createComment(CreateComment event, Emitter<RecipeState> emit) async {
if (state is RecipeLoadedState) {
var currentState = state as RecipeLoadedState;
// Отправляем данные по HTTP маршруту и ждем ответ
var response = await _sendCommentData(currentState.token, event.body, currentState.recipe?['id']);
if (response.statusCode == 200) {
var updatedComments = fillCommentList(await _fetchComments(currentState.recipe?['id']));
var updatedState = currentState.copyWith(
comments: updatedComments,
commentController: TextEditingController(), // Clear the comment input field
);
emit(updatedState); // Emit the updated state to trigger a screen update
}
}
}
_fetchAuthor(int id) async {
final box = await Hive.openBox('token');
final token = box.get('token');
if (token == null) {
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final response = await http.get(
Uri.parse(dotenv.env['SERVER_ROOT']! + '/recipe/getAuthor/$id'),
headers: {
'Authorization': 'Bearer $token',
},
);
final data = jsonDecode(response.body);
return data;
}
Future<List> _fetchComments(int id) async {
final box = await Hive.openBox('token');
final token = box.get('token');
if (token == null) {
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final response = await http.get(
Uri.parse(dotenv.env['SERVER_ROOT']! + '/recipe/getComments/$id'),
headers: {
'Authorization': 'Bearer $token',
},
);
final data = jsonDecode(response.body);
return data;
}
_fetchTags(int id) async {
final box = await Hive.openBox('token');
final token = box.get('token');
if (token == null) {
AppRouter.navigatorKey.currentContext?.goNamed('authScreen');
}
final response = await http.get(
Uri.parse(dotenv.env['SERVER_ROOT']! + '/recipe/getTags/$id'),
headers: {
'Authorization': 'Bearer $token',
},
);
final data = jsonDecode(response.body);
return data;
}
List<CommentModel> fillCommentList(List<dynamic> comments) {
List<CommentModel> list = [];
for (int i = 0; i < comments.length; i++) {
list.add(CommentModel(
id: comments[i]['id'].toString(),
userImage: comments[i]['profile_photo_path'],
username: comments[i]['name'],
dateText: DateFormat('dd.MM').format(DateTime.parse(comments[i]['updated_at'])),
greetingText: comments[i]['body']));
}
return list;
}
Future<http.Response> _sendCommentData(String? token, String body, int id) async {
String url = dotenv.env['SERVER_ROOT']! + '/recipe/createComment/$id';
final Map<String, dynamic> credentials = {
'id': id,
'body': body,
};
var response = await http.post(Uri.parse(url),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $token',
},
body: json.encode(credentials),
encoding: utf8);
return response;
}
}

View File

@@ -21,3 +21,10 @@ class DeleteEntry extends RecipeEvent {
@override
List<Object?> get props => [id];
}
class CreateComment extends RecipeEvent {
CreateComment(this.body);
final String body;
@override
List<Object?> get props => [body];
}

View File

@@ -22,17 +22,52 @@ class RecipeState extends Equatable {
}
class RecipeLoadedState extends RecipeState {
RecipeLoadedState({this.recipeModelObj, this.recipe});
RecipeLoadedState(
{this.recipeModelObj,
this.recipe,
this.author,
this.comments,
this.token,
this.commentController,
this.tags,
this.steps});
RecipeModel? recipeModelObj;
Map<String, dynamic>? recipe;
Map<String, dynamic>? author;
List<dynamic>? comments;
TextEditingController? commentController;
List<dynamic>? steps;
List<dynamic>? tags;
String? token;
@override
List<Object?> get props => [recipeModelObj, recipe];
List<Object?> get props => [
recipeModelObj,
recipe,
author,
comments,
token,
commentController,
steps
];
RecipeLoadedState copyWith(
{RecipeModel? recipeModelObj, Map<String, dynamic>? recipe}) {
{RecipeModel? recipeModelObj,
Map<String, dynamic>? recipe,
Map<String, dynamic>? author,
TextEditingController? commentController,
String? token,
List<dynamic>? tags,
List<dynamic>? steps,
List<dynamic>? comments}) {
return RecipeLoadedState(
recipeModelObj: recipeModelObj ?? this.recipeModelObj,
recipe: recipe ?? this.recipe,
author: author ?? this.author,
tags: tags ?? this.tags,
token: token ?? this.token,
commentController: commentController ?? this.commentController,
steps: steps ?? this.steps,
comments: comments ?? this.comments,
);
}
}

View File

@@ -1,19 +1,23 @@
import 'package:go_router/go_router.dart';
import 'bloc/recipe_show_bloc.dart';
import 'models/recipe_show_model.dart';
import 'package:eatvio_app/presentation/feed/blog_post_screen/widgets/comment_widget.dart';
import 'package:eatvio_app/core/app_export.dart';
import 'package:flutter/material.dart';
import 'package:eatvio_app/widgets/custom_bottom_bar.dart';
import 'package:intl/intl.dart';
import 'package:eatvio_app/widgets/custom_edit_text_form_field.dart';
import 'package:eatvio_app/widgets/custom_outlined_button.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
class RecipeScreen extends StatelessWidget {
const RecipeScreen({Key? key}) : super(key: key);
static Widget builder(BuildContext context) {
final recipeID = ModalRoute.of(context)!.settings.arguments;
return BlocProvider<RecipeBloc>(
create: (context) => RecipeBloc(RecipeState(
recipeModelObj: RecipeModel(recipeID: recipeID.toString())))
..add(RecipeInitialEvent()),
create: (context) => RecipeBloc(RecipeState(recipeModelObj: RecipeModel(recipeID: recipeID.toString())))..add(RecipeInitialEvent()),
child: RecipeScreen());
}
@@ -41,112 +45,414 @@ class RecipeScreen extends StatelessWidget {
_buildRecipe(BuildContext context, RecipeLoadedState state) {
mediaQueryData = MediaQuery.of(context);
List<Widget> steps = [];
if (state.steps != null) {
for (int i = 0; i < state.steps!.length; i++) {
steps.add(_buildStep(context, state, i));
}
}
return Scaffold(
resizeToAvoidBottomInset: false,
body: SafeArea(
appBar: AppBar(
toolbarHeight: 0,
automaticallyImplyLeading: false,
),
resizeToAvoidBottomInset: false,
body: Container(
width: double.maxFinite,
decoration: AppDecoration.fillGray,
child: ListView(
children: [
SizedBox(height: 15.v),
_buildHeaderRow(context, state),
SizedBox(height: 15.v),
_buildRecipeData(context, state),
SizedBox(height: 15.v),
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: steps,
),
SizedBox(height: 15.v),
_buildTextField(context, state),
SizedBox(height: 15.v),
_buildCommentSection(context, state),
],
),
));
}
_buildHeaderRow(BuildContext context, RecipeLoadedState state) {
final String name = state.author?['name'];
return Container(
margin: EdgeInsets.symmetric(horizontal: 16.h, vertical: 16.h),
padding: EdgeInsets.symmetric(
horizontal: 24.h,
vertical: 12.v,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
CustomImageView(
imagePath: ImageConstant.imgArrowLeft,
onTap: () => GoRouter.of(context).pop(),
),
Padding(
padding: EdgeInsets.only(top: 1.v),
child: Row(
children: [
Align(
alignment: Alignment.centerRight,
child: Text(
"$name",
style: CustomTextStyles.titleMediumBlack,
),
),
CachedNetworkImage(
imageUrl: dotenv.env['STORAGE_URL']! + (state.author?['profile_photo_path'] ?? ''),
imageBuilder: (context, imageProvider) => CircleAvatar(
backgroundImage: imageProvider,
radius: 25,
),
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => CircleAvatar(
child: Icon(Icons.person),
backgroundColor: Colors.white,
radius: 25,
),
),
],
),
),
],
),
);
}
_buildRecipeData(BuildContext context, RecipeLoadedState state) {
return Container(
margin: EdgeInsets.only(
left: 16.h,
right: 20.h,
bottom: 10.h,
),
padding: EdgeInsets.all(12.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: Column(mainAxisSize: MainAxisSize.min, children: [
Row(
children: [
Text(
state.recipe?['title'],
style: CustomTextStyles.titleMediumBlack,
),
Spacer(),
PopupMenuButton<String>(
onSelected: (value) => tapMenu(value, context, state),
itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
PopupMenuItem<String>(
value: 'edit',
child: Text('Редактировать'),
),
PopupMenuItem<String>(
value: 'delete',
child: Text('Удалить'),
),
],
),
],
),
SizedBox(height: 8.0),
_buildImage(state),
SizedBox(height: 16.0),
Text(
state.recipe?['description'],
style: CustomTextStyles.bodyMediumBluegray800,
),
SizedBox(height: 16.0),
Align(
alignment: Alignment.topLeft,
child: Text(
DateFormat('dd.MM.yyyy').format(DateTime.parse(state.recipe?['updated_at'])),
style: theme.textTheme.bodyMedium,
)),
SizedBox(height: 16.0),
_buildTags(state.tags),
SizedBox(height: 16.0),
Row(children: [
Padding(
padding: EdgeInsets.only(left: 24),
child: Row(children: [
Padding(
padding: EdgeInsets.only(right: 8),
child: Icon(Icons.favorite_outline),
),
Text(
"Нравится",
style: CustomTextStyles.titleMediumSemiBold,
)
]),
),
Spacer(),
Padding(
padding: EdgeInsets.only(right: 24),
child: Row(children: [
Padding(
padding: const EdgeInsets.only(right: 8),
child: Icon(Icons.share_outlined),
),
Text(
"Поделиться",
style: CustomTextStyles.titleMediumSemiBold,
)
]))
]),
]),
);
}
_buildTags(List<dynamic>? tags) {
return Wrap(
spacing: 8.0,
runSpacing: 4.0,
children: tags!.map((tag) {
return Chip(
label: Text(tag['name']),
);
}).toList(),
);
}
_buildImage(RecipeLoadedState state) {
return CachedNetworkImage(
imageUrl: dotenv.env['STORAGE_URL']! + (state.recipe?['image'] ?? ''),
imageBuilder: (context, imageProvider) => Stack(
children: [
ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8.0)),
child: Image(
image: imageProvider,
),
),
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Container(
width: double.maxFinite,
decoration: AppDecoration.fillGray,
child: ListView(
padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(height: 30.v),
Container(
margin: EdgeInsets.only(
left: 16.h,
right: 20.h,
bottom: 10.h,
),
padding: EdgeInsets.all(12.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
Padding(
padding: const EdgeInsets.only(left: 48.0),
child: Row(
children: [
Icon(
Icons.remove_red_eye,
color: Colors.white,
),
SizedBox(width: 4.0),
Text(
'24',
style: TextStyle(color: Colors.white),
),
],
),
child: Column(mainAxisSize: MainAxisSize.min, children: [
CachedNetworkImage(
imageUrl: dotenv.env['STORAGE_URL']! +
(state.recipe?['image'] ?? ''),
imageBuilder: (context, imageProvider) => CircleAvatar(
backgroundImage: imageProvider,
radius: 25,
),
placeholder: (context, url) =>
CircularProgressIndicator(),
errorWidget: (context, url, error) => CircleAvatar(
child: Icon(Icons.person),
backgroundColor: Colors.white,
radius: 25,
),
),
Row(
children: [
Icon(
Icons.favorite,
color: Colors.white,
),
SizedBox(height: 16.0),
Text(state.recipe?['title'],
style: CustomTextStyles.titleMediumBlack),
SizedBox(height: 8.0),
SizedBox(width: 4.0),
Text(
state.recipe?['description'],
'24',
style: TextStyle(color: Colors.white),
),
SizedBox(height: 16.0),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
ElevatedButton(
onPressed: () => Navigator.pushNamed(
context, AppRoutes.recipeEditScreen,
arguments: state.recipe?['id'].toString()),
child: Text('Редактировать'),
),
ElevatedButton(
onPressed: () => context
.read<RecipeBloc>()
.add(DeleteEntry(state.recipe?['id'])),
child: Text('Удалить'),
),
],
)
]),
)
],
),
Padding(
padding: const EdgeInsets.only(right: 48.0),
child: Row(
children: [
Icon(
Icons.comment_outlined,
color: Colors.white,
),
SizedBox(width: 4.0),
Text(
'${state.comments?.length}',
style: TextStyle(color: Colors.white),
),
],
),
),
],
),
),
), bottomNavigationBar: _buildBottomBar(context));
),
],
),
placeholder: (context, url) => CircularProgressIndicator(),
);
}
///Handling route based on bottom click actions
String getCurrentRoute(BottomBarEnum type) {
switch (type) {
case BottomBarEnum.tf:
return AppRoutes.recipeScreen;
default:
return "/";
void tapMenu(String value, BuildContext context, RecipeLoadedState state) {
if (value == 'edit') {
GoRouter.of(context).goNamed(
'recipeEditScreen',
extra: state.recipe?['id'].toString(),
);
} else if (value == 'delete') {
context.read<RecipeBloc>().add(DeleteEntry(state.recipe?['id']));
}
}
///Handling page based on route
Widget getCurrentPage(
BuildContext context,
String currentRoute,
) {
switch (currentRoute) {
case AppRoutes.recipeScreen:
return RecipeScreen.builder(context);
default:
return RecipeScreen();
}
_buildTextField(BuildContext context, RecipeLoadedState state) {
return Container(
margin: EdgeInsets.only(
left: 16.h,
right: 20.h,
bottom: 10.h,
),
padding: EdgeInsets.all(12.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Column(
children: [
CustomTextFormField(
controller: state.commentController,
maxLines: 4,
),
SizedBox(height: 15.h),
CustomOutlinedButton(
text: "Оставить отзыв",
margin: EdgeInsets.only(bottom: 5),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
decoration: BoxDecoration(
color: Colors.transparent,
),
onPressed: () => context.read<RecipeBloc>().add(CreateComment(state.commentController!.text)),
// Установите прозрачный фон
)
],
),
],
),
);
}
_buildBottomBar(BuildContext context) {
return CustomBottomBar(onChanged: (BottomBarEnum type) {});
_buildCommentSection(BuildContext context, RecipeLoadedState state) {
if (state.comments != null && state.comments!.isNotEmpty) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
for (int i = 0; i < state.comments!.length; i++)
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
CommentWidget(state.comments![i]),
SizedBox(height: 10.0), // Separator
],
),
],
);
} else {
// Return a message or placeholder widget when there are no comments
return Column(
children: [
Center(
child: Text('No comments available'),
),
SizedBox(
height: 20,
)
],
);
}
}
/// Navigates to the previous screen.
onTapArrowleftone(BuildContext context) {
NavigatorService.goBack();
GoRouter.of(context).pop();
}
Widget _buildStep(BuildContext context, RecipeLoadedState state, int i) {
return Container(
margin: EdgeInsets.only(
left: 16.h,
right: 16.h,
bottom: 10.h,
),
padding: EdgeInsets.all(12.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: Column(mainAxisSize: MainAxisSize.min, children: [
Align(
alignment: Alignment.centerLeft,
child: Text(
"Шаг ${i + 1}",
style: CustomTextStyles.titleMediumSemiBold,
),
),
SizedBox(height: 16.0),
CachedNetworkImage(
imageUrl: dotenv.env['STORAGE_URL']! + (state.steps?[i]['image']),
width: double.infinity,
// Установите ширину равной ширине родительского элемента
height: 200.0,
// Задайте постоянную высоту элемента
fit: BoxFit.cover,
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
),
SizedBox(height: 16.0),
Text(state.steps?[i]['description'])
]),
);
}
}

View File

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

View File

@@ -1,3 +1,5 @@
import 'package:go_router/go_router.dart';
import 'bloc/additional_information_bloc.dart';
import 'models/additional_information_model.dart';
import 'package:eatvio_app/core/app_export.dart';
@@ -16,85 +18,63 @@ import 'package:http/http.dart' as http;
class AdditionalInformationScreen extends StatelessWidget {
const AdditionalInformationScreen({Key? key}) : super(key: key);
static Widget builder(BuildContext context) {
final sheet =
ModalRoute.of(context)!.settings.arguments as UserRegistrationSheet;
static Widget builder(BuildContext context, sheet) {
return BlocProvider<AdditionalInformationBloc>(
create: (context) => AdditionalInformationBloc(
AdditionalInformationState(
additionalInformationModelObj:
AdditionalInformationModel(userRegistrationSheet: sheet)))
..add(AdditionalInformationInitialEvent()),
create: (context) =>
AdditionalInformationBloc(AdditionalInformationState(additionalInformationModelObj: AdditionalInformationModel(userRegistrationSheet: sheet)))
..add(AdditionalInformationInitialEvent()),
child: AdditionalInformationScreen());
}
@override
Widget build(BuildContext context) {
mediaQueryData = MediaQuery.of(context);
return SafeArea(
child: Scaffold(
resizeToAvoidBottomInset: false,
appBar: _buildAppBar(context),
body: Container(
width: double.maxFinite,
padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v),
child: Column(children: [
SizedBox(height: 7.v),
Container(
height: 16.v,
width: 343.h,
decoration: BoxDecoration(
color: theme.colorScheme.onPrimary,
borderRadius: BorderRadius.circular(8.h),
border: Border.all(
color: theme.colorScheme.primary, width: 1.h)),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.h),
child: LinearProgressIndicator(
value: 1,
backgroundColor: theme.colorScheme.onPrimary,
valueColor: AlwaysStoppedAnimation<Color>(
appTheme.blueA400)))),
SizedBox(height: 20.v),
SizedBox(height: 73.v),
_buildTextFormField(context),
Spacer(),
BlocBuilder<AdditionalInformationBloc,
AdditionalInformationState>(builder: (context, state) {
return NextStepButton(
text: "finish".tr.toUpperCase(),
margin: EdgeInsets.only(
left: 16.h, right: 16.h, bottom: 50.v),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
onPressed: onTapFinish(state, context));
}),
]))));
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: _buildAppBar(context),
body: Container(
width: double.maxFinite,
padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v),
child: Column(children: [
SizedBox(height: 7.v),
Container(
height: 16.v,
width: 343.h,
decoration: BoxDecoration(
color: theme.colorScheme.onPrimary,
borderRadius: BorderRadius.circular(8.h),
border: Border.all(color: theme.colorScheme.primary, width: 1.h)),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.h),
child: LinearProgressIndicator(
value: 1, backgroundColor: theme.colorScheme.onPrimary, valueColor: AlwaysStoppedAnimation<Color>(appTheme.blueA400)))),
SizedBox(height: 20.v),
SizedBox(height: 73.v),
_buildTextFormField(context),
Spacer(),
BlocBuilder<AdditionalInformationBloc, AdditionalInformationState>(builder: (context, state) {
return NextStepButton(
text: "finish".tr.toUpperCase(),
margin: EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
onPressed: onTapFinish(state, context));
}),
])));
}
onTapFinish(AdditionalInformationState state, BuildContext context) {
return () async {
UserRegistrationSheet sheet =
state.additionalInformationModelObj!.userRegistrationSheet;
UserRegistrationSheet sheet = state.additionalInformationModelObj!.userRegistrationSheet;
sheet.additionalInfo = state.editTextController!.text;
final response = await http.post(
Uri.parse(dotenv.env['SERVER_ROOT']! + '/register'),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: jsonEncode(sheet.toJson()),
encoding: utf8);
final response = await http.post(Uri.parse(dotenv.env['SERVER_ROOT']! + '/register'),
headers: {'Content-Type': 'application/json', 'Accept': 'application/json'}, body: jsonEncode(sheet.toJson()), encoding: utf8);
var res = await jsonDecode(response.body);
if (response.statusCode == 200) {
final box = await Hive.openBox('token');
final token = res['token'];
box.put('token', token);
Navigator.pushNamed(
context,
AppRoutes.myProfile,
);
GoRouter.of(context).goNamed('myProfileScreen');
} else {
showErrorMessage(context, res['message']);
}
@@ -105,10 +85,8 @@ class AdditionalInformationScreen extends StatelessWidget {
_buildAppBar(BuildContext context) {
return CustomAppBar(
leadingWidth: 31.h,
leading: AppbarLeadingImage(
imagePath: ImageConstant.imgArrowLeft,
margin: EdgeInsets.only(left: 18.h, top: 15.v, bottom: 15.v),
onTap: onTapBack(context)),
leading:
AppbarLeadingImage(imagePath: ImageConstant.imgArrowLeft, margin: EdgeInsets.only(left: 18.h, top: 15.v, bottom: 15.v), onTap: onTapBack(context)),
centerTitle: true,
title: AppbarTitleImage(imagePath: ImageConstant.imgLogoEatvio));
}
@@ -132,19 +110,14 @@ class AdditionalInformationScreen extends StatelessWidget {
width: 294.h,
margin: EdgeInsets.only(left: 24.h, right: 23.h),
child: Text("enter_additional_information".tr,
maxLines: 2,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
style: CustomTextStyles.titleMediumSegoeUI)),
maxLines: 2, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, style: CustomTextStyles.titleMediumSegoeUI)),
SizedBox(height: 10.v),
BlocSelector<AdditionalInformationBloc,
AdditionalInformationState, TextEditingController?>(
BlocSelector<AdditionalInformationBloc, AdditionalInformationState, TextEditingController?>(
selector: (state) => state.editTextController,
builder: (context, editTextController) {
return CustomTextFormField(
controller: editTextController,
hintText:
"У меня крепкое здоровье, но последнее время часто утомляюсь, однако, это мне не мешает радоваться жизни",
hintText: "У меня крепкое здоровье, но последнее время часто утомляюсь, однако, это мне не мешает радоваться жизни",
textInputAction: TextInputAction.done,
maxLines: 8);
})
@@ -154,7 +127,7 @@ class AdditionalInformationScreen extends StatelessWidget {
/// Navigates to the previous screen.
onTapArrowLeft(BuildContext context) {
NavigatorService.goBack();
GoRouter.of(context).pop();
}
/// Displays an alert dialog when the action is triggered.
@@ -165,9 +138,7 @@ class AdditionalInformationScreen extends StatelessWidget {
builder: (_) => AlertDialog(
title: const Text('Ошибка'),
content: Text(text),
actions: [
TextButton(onPressed: onTapOk(context), child: const Text('Ok'))
],
actions: [TextButton(onPressed: onTapOk(context), child: const Text('Ok'))],
));
}

View File

@@ -1,3 +1,5 @@
import 'package:go_router/go_router.dart';
import 'bloc/allergies_bloc.dart';
import 'models/allergies_model.dart';
import 'package:eatvio_app/core/app_export.dart';
@@ -12,13 +14,9 @@ import 'package:flutter/material.dart';
class AllergiesScreen extends StatefulWidget {
const AllergiesScreen({Key? key}) : super(key: key);
static Widget builder(BuildContext context) {
final sheet =
ModalRoute.of(context)!.settings.arguments as UserRegistrationSheet;
static Widget builder(BuildContext context, sheet) {
return BlocProvider<AllergiesBloc>(
create: (context) => AllergiesBloc(AllergiesState(
allergiesModelObj: AllergiesModel(userRegistrationSheet: sheet)))
..add(AllergiesInitialEvent()),
create: (context) => AllergiesBloc(AllergiesState(allergiesModelObj: AllergiesModel(userRegistrationSheet: sheet)))..add(AllergiesInitialEvent()),
child: AllergiesScreen());
}
@@ -29,78 +27,58 @@ class AllergiesScreen extends StatefulWidget {
class _AllergiesScreenState extends State<AllergiesScreen> {
@override
Widget build(BuildContext context) {
mediaQueryData = MediaQuery.of(context);
return SafeArea(
child: Scaffold(
resizeToAvoidBottomInset: false,
appBar: _buildAppBar(context),
body: Container(
width: double.maxFinite,
padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v),
child: Column(children: [
SizedBox(height: 7.v),
Container(
height: 16.v,
width: 343.h,
decoration: BoxDecoration(
color: theme.colorScheme.onPrimary,
borderRadius: BorderRadius.circular(8.h),
border: Border.all(
color: theme.colorScheme.primary, width: 1.h)),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.h),
child: LinearProgressIndicator(
value: 0.76,
backgroundColor: theme.colorScheme.onPrimary,
valueColor: AlwaysStoppedAnimation<Color>(
appTheme.blueA400)))),
SizedBox(height: 22.v),
Text("allergies_presence".tr,
style: theme.textTheme.headlineSmall),
Spacer(flex: 36),
_buildTextFormField(context),
Spacer(flex: 54),
BlocBuilder<AllergiesBloc, AllergiesState>(
builder: (context, state) {
return NextStepButton(
text: "continue".tr.toUpperCase(),
margin: EdgeInsets.only(
left: 16.h, right: 16.h, bottom: 50.v),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
onPressed: onTapContinue(state, context));
}),
BlocBuilder<AllergiesBloc, AllergiesState>(
builder: (context, state) {
return GestureDetector(
onTap: onTapSkip(state, context),
child: Text("skip".tr, style: theme.textTheme.bodyLarge),
);
}),
]))));
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: _buildAppBar(context),
body: Container(
width: double.maxFinite,
padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v),
child: Column(children: [
SizedBox(height: 7.v),
Container(
height: 16.v,
width: 343.h,
decoration: BoxDecoration(
color: theme.colorScheme.onPrimary,
borderRadius: BorderRadius.circular(8.h),
border: Border.all(color: theme.colorScheme.primary, width: 1.h)),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.h),
child: LinearProgressIndicator(
value: 0.76, backgroundColor: theme.colorScheme.onPrimary, valueColor: AlwaysStoppedAnimation<Color>(appTheme.blueA400)))),
SizedBox(height: 22.v),
Text("allergies_presence".tr, style: theme.textTheme.headlineSmall),
Spacer(flex: 36),
_buildTextFormField(context),
Spacer(flex: 54),
BlocBuilder<AllergiesBloc, AllergiesState>(builder: (context, state) {
return NextStepButton(
text: "continue".tr.toUpperCase(),
margin: EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
onPressed: onTapContinue(state, context));
}),
BlocBuilder<AllergiesBloc, AllergiesState>(builder: (context, state) {
return GestureDetector(
onTap: onTapSkip(state, context),
child: Text("skip".tr, style: theme.textTheme.bodyLarge),
);
}),
])));
}
onTapSkip(AllergiesState state, BuildContext context) {
return () {
UserRegistrationSheet sheet =
state.allergiesModelObj!.userRegistrationSheet;
Navigator.pushNamed(
context,
AppRoutes.medicineIntakeScreen,
arguments: sheet,
);
UserRegistrationSheet sheet = state.allergiesModelObj!.userRegistrationSheet;
GoRouter.of(context).goNamed('medicineIntakeScreen', extra: sheet);
};
}
onTapContinue(AllergiesState state, BuildContext context) {
return () {
UserRegistrationSheet sheet =
state.allergiesModelObj!.userRegistrationSheet;
UserRegistrationSheet sheet = state.allergiesModelObj!.userRegistrationSheet;
sheet.aboutAllergy = state.enterInformationController!.text;
Navigator.pushNamed(
context,
AppRoutes.medicineIntakeScreen,
arguments: sheet,
);
GoRouter.of(context).goNamed('medicineIntakeScreen', extra: sheet);
};
}
@@ -108,10 +86,8 @@ class _AllergiesScreenState extends State<AllergiesScreen> {
_buildAppBar(BuildContext context) {
return CustomAppBar(
leadingWidth: 31.h,
leading: AppbarLeadingImage(
imagePath: ImageConstant.imgArrowLeft,
margin: EdgeInsets.only(left: 18.h, top: 15.v, bottom: 15.v),
onTap: onTapBack(context)),
leading:
AppbarLeadingImage(imagePath: ImageConstant.imgArrowLeft, margin: EdgeInsets.only(left: 18.h, top: 15.v, bottom: 15.v), onTap: onTapBack(context)),
centerTitle: true,
title: AppbarTitleImage(imagePath: ImageConstant.imgLogoEatvio));
}
@@ -125,23 +101,19 @@ class _AllergiesScreenState extends State<AllergiesScreen> {
/// Section Widget
_buildTextFormField(BuildContext context) {
return Column(children: [
Text("fill_in_information".tr,
style: CustomTextStyles.titleMediumSegoeUI),
Text("fill_in_information".tr, style: CustomTextStyles.titleMediumSegoeUI),
SizedBox(height: 2.v),
BlocSelector<AllergiesBloc, AllergiesState, TextEditingController?>(
selector: (state) => state.enterInformationController,
builder: (context, enterInformationController) {
return CustomTextFormField(
controller: enterInformationController,
hintText: "enter_information".tr,
textInputAction: TextInputAction.done,
maxLines: 8);
controller: enterInformationController, hintText: "enter_information".tr, textInputAction: TextInputAction.done, maxLines: 8);
})
]);
}
/// Navigates to the previous screen.
onTapArrowLeft(BuildContext context) {
NavigatorService.goBack();
GoRouter.of(context).pop();
}
}

View File

@@ -1,3 +1,5 @@
import 'package:go_router/go_router.dart';
import 'bloc/app_navigation_bloc.dart';
import 'models/app_navigation_model.dart';
import 'package:eatvio_app/core/app_export.dart';
@@ -49,92 +51,77 @@ class _AppNavigationScreenState extends State<AppNavigationScreen> {
_buildScreenTitle(
context,
screenTitle: "date_of_birth".tr,
onTapScreenTitle: () =>
onTapScreenTitle(AppRoutes.dateOfBirthScreen),
onTapScreenTitle: () => GoRouter.of(context).goNamed('dateOfBirthScreen'),
),
_buildScreenTitle(
context,
screenTitle: "city_of_living".tr,
onTapScreenTitle: () => onTapScreenTitle(
AppRoutes.cityOfLivingScreen),
onTapScreenTitle: () => GoRouter.of(context).goNamed('cityOfLivingScreen'),
),
_buildScreenTitle(
context,
screenTitle: "body_circumferences".tr,
onTapScreenTitle: () => onTapScreenTitle(
AppRoutes.bodyCircumferencesScreen),
onTapScreenTitle: () => GoRouter.of(context).goNamed('bodyCircumferencesScreen'),
),
_buildScreenTitle(
context,
screenTitle: "weight".tr,
onTapScreenTitle: () =>
onTapScreenTitle(AppRoutes.weightScreen),
onTapScreenTitle: () => GoRouter.of(context).goNamed('weightScreen'),
),
_buildScreenTitle(
context,
screenTitle: "allergies".tr,
onTapScreenTitle: () =>
onTapScreenTitle(AppRoutes.allergiesScreen),
onTapScreenTitle: () => GoRouter.of(context).goNamed('allergiesScreen'),
),
_buildScreenTitle(
context,
screenTitle: "medicine_intake".tr,
onTapScreenTitle: () => onTapScreenTitle(
AppRoutes.medicineIntakeScreen),
onTapScreenTitle: () => GoRouter.of(context).goNamed('medicineIntakeScreen'),
),
_buildScreenTitle(
context,
screenTitle: "splash_screen".tr,
onTapScreenTitle: () =>
onTapScreenTitle(AppRoutes.splashScreen),
onTapScreenTitle: () => GoRouter.of(context).goNamed('splashScreen'),
),
_buildScreenTitle(
context,
screenTitle: "gender".tr,
onTapScreenTitle: () =>
onTapScreenTitle(AppRoutes.genderScreen),
onTapScreenTitle: () => GoRouter.of(context).goNamed('genderScreen'),
),
_buildScreenTitle(
context,
screenTitle: "blood_pressure".tr,
onTapScreenTitle: () => onTapScreenTitle(
AppRoutes.bloodPressureScreen),
onTapScreenTitle: () => GoRouter.of(context).goNamed('bloodPressureScreen'),
),
_buildScreenTitle(
context,
screenTitle: "favorite_sports".tr,
onTapScreenTitle: () => onTapScreenTitle(
AppRoutes.favoriteSportsScreen),
onTapScreenTitle: () => GoRouter.of(context).goNamed('favoriteSportsScreen'),
),
_buildScreenTitle(
context,
screenTitle: "favorite_foods".tr,
onTapScreenTitle: () => onTapScreenTitle(
AppRoutes.favoriteFoodsScreen),
onTapScreenTitle: () => GoRouter.of(context).goNamed('favoriteFoodsScreen'),
),
_buildScreenTitle(
context,
screenTitle: "schedule".tr,
onTapScreenTitle: () =>
onTapScreenTitle(AppRoutes.scheduleScreen),
onTapScreenTitle: () => GoRouter.of(context).goNamed('scheduleScreen'),
),
_buildScreenTitle(
context,
screenTitle: "additional_information".tr,
onTapScreenTitle: () => onTapScreenTitle(
AppRoutes.additionalInformationScreen),
onTapScreenTitle: () => GoRouter.of(context).goNamed('additionalInformationScreen'),
),
_buildScreenTitle(
context,
screenTitle: "language_select".tr,
onTapScreenTitle: () => onTapScreenTitle(
AppRoutes.languageSelectScreen),
onTapScreenTitle: () => GoRouter.of(context).goNamed('languageSelectScreen'),
),
_buildScreenTitle(
context,
screenTitle: "auth".tr,
onTapScreenTitle: () =>
onTapScreenTitle(AppRoutes.authScreen),
onTapScreenTitle: () => GoRouter.of(context).goNamed('authScreen'),
),
],
),
@@ -181,8 +168,7 @@ class _AppNavigationScreenState extends State<AppNavigationScreen> {
child: Padding(
padding: EdgeInsets.only(left: 20.h),
child: Text(
"Check your app's UI from the below demo screens of your app."
.tr,
"Check your app's UI from the below demo screens of your app.".tr,
textAlign: TextAlign.center,
style: TextStyle(
color: Color(0XFF888888),
@@ -247,9 +233,4 @@ class _AppNavigationScreenState extends State<AppNavigationScreen> {
),
);
}
/// Common click event
void onTapScreenTitle(String routeName) {
NavigatorService.pushNamed(routeName);
}
}

View File

@@ -1,3 +1,6 @@
import 'package:eatvio_app/app_router.dart';
import 'package:go_router/go_router.dart';
import 'bloc/blood_pressure_bloc.dart';
import 'package:flutter/services.dart';
import 'models/blood_pressure_model.dart';
@@ -14,14 +17,10 @@ import 'package:flutter/material.dart';
class BloodPressureScreen extends StatefulWidget {
const BloodPressureScreen({Key? key}) : super(key: key);
static Widget builder(BuildContext context) {
final sheet =
ModalRoute.of(context)!.settings.arguments as UserRegistrationSheet;
static Widget builder(BuildContext context, sheet) {
return BlocProvider<BloodPressureBloc>(
create: (context) => BloodPressureBloc(BloodPressureState(
bloodPressureModelObj:
BloodPressureModel(userRegistrationSheet: sheet)))
..add(BloodPressureInitialEvent()),
create: (context) =>
BloodPressureBloc(BloodPressureState(bloodPressureModelObj: BloodPressureModel(userRegistrationSheet: sheet)))..add(BloodPressureInitialEvent()),
child: BloodPressureScreen());
}
@@ -33,184 +32,133 @@ class _BloodPressureScreenState extends State<BloodPressureScreen> {
@override
Widget build(BuildContext context) {
final digitsOnly = FilteringTextInputFormatter.digitsOnly;
mediaQueryData = MediaQuery.of(context);
return SafeArea(
child: Scaffold(
resizeToAvoidBottomInset: false,
appBar: _buildAppBar(context),
body: Container(
width: double.maxFinite,
padding: EdgeInsets.symmetric(horizontal: 12.h, vertical: 9.v),
child: Column(children: [
SizedBox(height: 7.v),
Container(
height: 16.v,
width: 343.h,
padding: EdgeInsets.symmetric(
horizontal:
4.h), // Apply padding directly to the Container
decoration: BoxDecoration(
color: theme.colorScheme.onPrimary,
borderRadius: BorderRadius.circular(8.h),
border: Border.all(
color: theme.colorScheme.primary,
width: 1.h,
),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.h),
child: LinearProgressIndicator(
value: 0.62,
backgroundColor: theme.colorScheme.onPrimary,
valueColor:
AlwaysStoppedAnimation<Color>(appTheme.blueA400),
),
),
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: _buildAppBar(context),
body: Container(
width: double.maxFinite,
padding: EdgeInsets.symmetric(horizontal: 12.h, vertical: 9.v),
child: Column(children: [
SizedBox(height: 7.v),
Container(
height: 16.v,
width: 343.h,
padding: EdgeInsets.symmetric(horizontal: 4.h),
// Apply padding directly to the Container
decoration: BoxDecoration(
color: theme.colorScheme.onPrimary,
borderRadius: BorderRadius.circular(8.h),
border: Border.all(
color: theme.colorScheme.primary,
width: 1.h,
),
SizedBox(height: 21.v),
SizedBox(
width: 224.h,
child: Text("your_blood_pressure".tr,
maxLines: 2,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
style: theme.textTheme.headlineSmall)),
SizedBox(height: 21.v),
Container(
margin: EdgeInsets.only(right: 1.h),
padding: EdgeInsets.symmetric(horizontal: 3.h),
child: Column(children: [
Text("up_pressure".tr,
style: theme.textTheme.bodyLarge),
SizedBox(height: 2.v),
BlocSelector<BloodPressureBloc, BloodPressureState,
TextEditingController?>(
selector: (state) => state.upPressureController,
builder: (context, upPressureController) {
return CustomTextFormField(
inputFormatters: [digitsOnly],
controller: upPressureController,
hintText: "90".tr);
})
])),
SizedBox(height: 21.v),
Container(
margin: EdgeInsets.only(right: 1.h),
padding: EdgeInsets.symmetric(horizontal: 3.h),
child: Column(children: [
Text("down_pressure".tr,
style: theme.textTheme.bodyLarge),
SizedBox(height: 2.v),
BlocSelector<BloodPressureBloc, BloodPressureState,
TextEditingController?>(
selector: (state) => state.downPressureController,
builder: (context, downPressureController) {
return CustomTextFormField(
inputFormatters: [digitsOnly],
controller: downPressureController,
hintText: "90".tr);
})
])),
SizedBox(height: 22.v),
Text("do_you_have_hypotension_tendency".tr,
style: theme.textTheme.bodyLarge),
SizedBox(height: 2.v),
Padding(
padding: EdgeInsets.symmetric(horizontal: 4.h),
child: BlocSelector<BloodPressureBloc, BloodPressureState,
BloodPressureModel?>(
selector: (state) => state.bloodPressureModelObj,
builder: (context, bloodPressureModelObj) {
return CustomDropDown(
icon: Container(
margin: EdgeInsets.fromLTRB(
30.h, 11.v, 13.h, 11.v),
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(1.h)),
child: CustomImageView(
imagePath: ImageConstant.imgFavorite,
height: 12.adaptSize,
width: 12.adaptSize)),
hintText: "Не выбрано".tr,
items:
bloodPressureModelObj?.dropdownItemList ??
[],
onChanged: onChangeHyper(context));
})),
SizedBox(height: 22.v),
Text("do_you_have_hypertension_tendency".tr,
style: theme.textTheme.bodyLarge),
SizedBox(height: 2.v),
Padding(
padding: EdgeInsets.symmetric(horizontal: 4.h),
child: BlocSelector<BloodPressureBloc, BloodPressureState,
BloodPressureModel?>(
selector: (state) => state.bloodPressureModelObj,
builder: (context, bloodPressureModelObj) {
return CustomDropDown(
icon: Container(
margin: EdgeInsets.fromLTRB(
30.h, 11.v, 13.h, 11.v),
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(1.h)),
child: CustomImageView(
imagePath: ImageConstant.imgFavorite,
height: 12.adaptSize,
width: 12.adaptSize)),
hintText: "Не выбрано".tr,
items:
bloodPressureModelObj?.dropdownItemList ??
[],
onChanged: onChangeHypo(context));
})),
Spacer(),
BlocBuilder<BloodPressureBloc, BloodPressureState>(
builder: (context, state) {
return NextStepButton(
text: "continue".tr.toUpperCase(),
margin: EdgeInsets.only(
left: 16.h, right: 16.h, bottom: 50.v),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
onPressed: onTapContinue(state, context));
}),
BlocBuilder<BloodPressureBloc, BloodPressureState>(
builder: (context, state) {
return GestureDetector(
onTap: onTapSkip(state, context),
child: Text("skip".tr, style: theme.textTheme.bodyLarge),
);
}),
]))));
),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.h),
child: LinearProgressIndicator(
value: 0.62,
backgroundColor: theme.colorScheme.onPrimary,
valueColor: AlwaysStoppedAnimation<Color>(appTheme.blueA400),
),
),
),
SizedBox(height: 21.v),
SizedBox(
width: 224.h,
child: Text("your_blood_pressure".tr,
maxLines: 2, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, style: theme.textTheme.headlineSmall)),
SizedBox(height: 21.v),
Container(
margin: EdgeInsets.only(right: 1.h),
padding: EdgeInsets.symmetric(horizontal: 3.h),
child: Column(children: [
Text("up_pressure".tr, style: theme.textTheme.bodyLarge),
SizedBox(height: 2.v),
BlocSelector<BloodPressureBloc, BloodPressureState, TextEditingController?>(
selector: (state) => state.upPressureController,
builder: (context, upPressureController) {
return CustomTextFormField(inputFormatters: [digitsOnly], controller: upPressureController, hintText: "90".tr);
})
])),
SizedBox(height: 21.v),
Container(
margin: EdgeInsets.only(right: 1.h),
padding: EdgeInsets.symmetric(horizontal: 3.h),
child: Column(children: [
Text("down_pressure".tr, style: theme.textTheme.bodyLarge),
SizedBox(height: 2.v),
BlocSelector<BloodPressureBloc, BloodPressureState, TextEditingController?>(
selector: (state) => state.downPressureController,
builder: (context, downPressureController) {
return CustomTextFormField(inputFormatters: [digitsOnly], controller: downPressureController, hintText: "90".tr);
})
])),
SizedBox(height: 22.v),
Text("do_you_have_hypotension_tendency".tr, style: theme.textTheme.bodyLarge),
SizedBox(height: 2.v),
Padding(
padding: EdgeInsets.symmetric(horizontal: 4.h),
child: BlocSelector<BloodPressureBloc, BloodPressureState, BloodPressureModel?>(
selector: (state) => state.bloodPressureModelObj,
builder: (context, bloodPressureModelObj) {
return CustomDropDown(
icon: Container(
margin: EdgeInsets.fromLTRB(30.h, 11.v, 13.h, 11.v),
decoration: BoxDecoration(borderRadius: BorderRadius.circular(1.h)),
child: CustomImageView(imagePath: ImageConstant.imgFavorite, height: 12.adaptSize, width: 12.adaptSize)),
hintText: "Не выбрано".tr,
items: bloodPressureModelObj?.dropdownItemList ?? [],
onChanged: onChangeHyper(context));
})),
SizedBox(height: 22.v),
Text("do_you_have_hypertension_tendency".tr, style: theme.textTheme.bodyLarge),
SizedBox(height: 2.v),
Padding(
padding: EdgeInsets.symmetric(horizontal: 4.h),
child: BlocSelector<BloodPressureBloc, BloodPressureState, BloodPressureModel?>(
selector: (state) => state.bloodPressureModelObj,
builder: (context, bloodPressureModelObj) {
return CustomDropDown(
icon: Container(
margin: EdgeInsets.fromLTRB(30.h, 11.v, 13.h, 11.v),
decoration: BoxDecoration(borderRadius: BorderRadius.circular(1.h)),
child: CustomImageView(imagePath: ImageConstant.imgFavorite, height: 12.adaptSize, width: 12.adaptSize)),
hintText: "Не выбрано".tr,
items: bloodPressureModelObj?.dropdownItemList ?? [],
onChanged: onChangeHypo(context));
})),
Spacer(),
BlocBuilder<BloodPressureBloc, BloodPressureState>(builder: (context, state) {
return NextStepButton(
text: "continue".tr.toUpperCase(),
margin: EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
onPressed: onTapContinue(state, context));
}),
BlocBuilder<BloodPressureBloc, BloodPressureState>(builder: (context, state) {
return GestureDetector(
onTap: onTapSkip(state, context),
child: Text("skip".tr, style: theme.textTheme.bodyLarge),
);
}),
])));
}
onTapSkip(BloodPressureState state, BuildContext context) {
return () {
UserRegistrationSheet sheet =
state.bloodPressureModelObj!.userRegistrationSheet;
Navigator.pushNamed(
context,
AppRoutes.allergiesScreen,
arguments: sheet,
);
UserRegistrationSheet sheet = state.bloodPressureModelObj!.userRegistrationSheet;
AppRouter.navigatorKey.currentContext?.goNamed('allergiesScreen', extra: sheet);
};
}
onTapContinue(BloodPressureState state, BuildContext context) {
return () {
List<String> errors = validateFields(
state.upPressureController!.text, state.downPressureController!.text);
List<String> errors = validateFields(state.upPressureController!.text, state.downPressureController!.text);
if (errors.isEmpty) {
UserRegistrationSheet sheet =
state.bloodPressureModelObj!.userRegistrationSheet;
UserRegistrationSheet sheet = state.bloodPressureModelObj!.userRegistrationSheet;
sheet.bloodPressureUp = state.upPressureController!.text;
sheet.bloodPressureDown = state.downPressureController!.text;
Navigator.pushNamed(
context,
AppRoutes.favoriteSportsScreen,
arguments: sheet,
);
AppRouter.navigatorKey.currentContext?.goNamed('favoriteSportsScreen', extra: sheet);
} else {
String errorMessage = '';
@@ -243,9 +191,7 @@ class _BloodPressureScreenState extends State<BloodPressureScreen> {
onChangeHypo(BuildContext context) {
return (value) {
context
.read<BloodPressureBloc>()
.add(ChangeHyperDropDownEvent(value: value));
context.read<BloodPressureBloc>().add(ChangeHyperDropDownEvent(value: value));
};
}
@@ -259,10 +205,8 @@ class _BloodPressureScreenState extends State<BloodPressureScreen> {
_buildAppBar(BuildContext context) {
return CustomAppBar(
leadingWidth: 29.h,
leading: AppbarLeadingImage(
imagePath: ImageConstant.imgArrowLeft,
margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v),
onTap: onTapBack(context)),
leading:
AppbarLeadingImage(imagePath: ImageConstant.imgArrowLeft, margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v), onTap: onTapBack(context)),
centerTitle: true,
title: AppbarTitleImage(imagePath: ImageConstant.imgLogoEatvio));
}
@@ -275,7 +219,7 @@ class _BloodPressureScreenState extends State<BloodPressureScreen> {
/// Navigates to the previous screen.
onTapArrowLeft(BuildContext context) {
NavigatorService.goBack();
GoRouter.of(context).pop();
}
List<String> validateFields(
@@ -288,8 +232,7 @@ class _BloodPressureScreenState extends State<BloodPressureScreen> {
errors.add('pressure_empty'.tr);
}
if (![bloodPressureUp, bloodPressureDown]
.every((pressure) => int.tryParse(pressure) != null)) {
if (![bloodPressureUp, bloodPressureDown].every((pressure) => int.tryParse(pressure) != null)) {
errors.add('pressure_is_not_a_number'.tr);
}

View File

@@ -1,3 +1,5 @@
import 'package:go_router/go_router.dart';
import 'bloc/body_circumferences_bloc.dart';
import 'package:flutter/services.dart';
import 'models/body_circumferences_model.dart';
@@ -13,120 +15,87 @@ import 'package:flutter/material.dart';
class BodyCircumferencesScreen extends StatelessWidget {
const BodyCircumferencesScreen({Key? key}) : super(key: key);
static Widget builder(BuildContext context) {
final sheet =
ModalRoute.of(context)!.settings.arguments as UserRegistrationSheet;
static Widget builder(BuildContext context, sheet) {
return BlocProvider<BodyCircumferencesBloc>(
create: (context) => BodyCircumferencesBloc(BodyCircumferencesState(
bodyCircumferencesModelObj:
BodyCircumferencesModel(userRegistrationSheet: sheet)))
..add(BodyCircumferencesInitialEvent()),
child: BodyCircumferencesScreen());
create: (context) => BodyCircumferencesBloc(BodyCircumferencesState(bodyCircumferencesModelObj: BodyCircumferencesModel(userRegistrationSheet: sheet)))
..add(BodyCircumferencesInitialEvent()),
child: BodyCircumferencesScreen(),
);
}
@override
Widget build(BuildContext context) {
final digitsOnly = FilteringTextInputFormatter.digitsOnly;
mediaQueryData = MediaQuery.of(context);
return SafeArea(
child: Scaffold(
resizeToAvoidBottomInset: false,
appBar: _buildAppBar(context),
body: Container(
width: double.maxFinite,
padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v),
child: Column(children: [
SizedBox(height: 7.v),
Container(
height: 16.v,
width: 343.h,
decoration: BoxDecoration(
color: theme.colorScheme.onPrimary,
borderRadius: BorderRadius.circular(8.h),
border: Border.all(
color: theme.colorScheme.primary, width: 1.h)),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.h),
child: LinearProgressIndicator(
value: 0.40,
backgroundColor: theme.colorScheme.onPrimary,
valueColor: AlwaysStoppedAnimation<Color>(
appTheme.blueA400)))),
SizedBox(height: 39.v),
Text("initial_measurements".tr,
style: theme.textTheme.headlineSmall),
SizedBox(height: 93.v),
Column(children: [
Text("chest_circumference".tr,
style: theme.textTheme.bodyLarge),
SizedBox(height: 3.v),
BlocSelector<BodyCircumferencesBloc,
BodyCircumferencesState, TextEditingController?>(
selector: (state) => state.chestCircumferenceController,
builder: (context, chestCircumferenceController) {
return CustomTextFormField(
inputFormatters: [digitsOnly],
controller: chestCircumferenceController,
hintText: "90".tr);
})
]),
SizedBox(height: 19.v),
Text("waist_circumference".tr,
style: theme.textTheme.bodyLarge),
SizedBox(height: 5.v),
BlocSelector<BodyCircumferencesBloc, BodyCircumferencesState,
TextEditingController?>(
selector: (state) => state.waistCircumferenceController,
builder: (context, waistCircumferenceController) {
return CustomTextFormField(
inputFormatters: [digitsOnly],
controller: waistCircumferenceController,
hintText: "60".tr);
}),
SizedBox(height: 21.v),
Text("hip_circumference".tr,
style: theme.textTheme.bodyLarge),
SizedBox(height: 3.v),
BlocSelector<BodyCircumferencesBloc, BodyCircumferencesState,
TextEditingController?>(
selector: (state) => state.hipCircumferenceController,
builder: (context, hipCircumferenceController) {
return CustomTextFormField(
inputFormatters: [digitsOnly],
controller: hipCircumferenceController,
hintText: "90".tr,
textInputAction: TextInputAction.done);
}),
Spacer(),
BlocBuilder<BodyCircumferencesBloc, BodyCircumferencesState>(
builder: (context, state) {
return NextStepButton(
text: "continue".tr.toUpperCase(),
margin: EdgeInsets.only(
left: 16.h, right: 16.h, bottom: 50.v),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
onPressed: onTapContinue(state, context));
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: _buildAppBar(context),
body: Container(
width: double.maxFinite,
padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v),
child: Column(children: [
SizedBox(height: 7.v),
Container(
height: 16.v,
width: 343.h,
decoration: BoxDecoration(
color: theme.colorScheme.onPrimary,
borderRadius: BorderRadius.circular(8.h),
border: Border.all(color: theme.colorScheme.primary, width: 1.h)),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.h),
child: LinearProgressIndicator(
value: 0.40, backgroundColor: theme.colorScheme.onPrimary, valueColor: AlwaysStoppedAnimation<Color>(appTheme.blueA400)))),
SizedBox(height: 39.v),
Text("initial_measurements".tr, style: theme.textTheme.headlineSmall),
SizedBox(height: 93.v),
Column(children: [
Text("chest_circumference".tr, style: theme.textTheme.bodyLarge),
SizedBox(height: 3.v),
BlocSelector<BodyCircumferencesBloc, BodyCircumferencesState, TextEditingController?>(
selector: (state) => state.chestCircumferenceController,
builder: (context, chestCircumferenceController) {
return CustomTextFormField(inputFormatters: [digitsOnly], controller: chestCircumferenceController, hintText: "90".tr);
})
]),
SizedBox(height: 19.v),
Text("waist_circumference".tr, style: theme.textTheme.bodyLarge),
SizedBox(height: 5.v),
BlocSelector<BodyCircumferencesBloc, BodyCircumferencesState, TextEditingController?>(
selector: (state) => state.waistCircumferenceController,
builder: (context, waistCircumferenceController) {
return CustomTextFormField(inputFormatters: [digitsOnly], controller: waistCircumferenceController, hintText: "60".tr);
}),
]))));
SizedBox(height: 21.v),
Text("hip_circumference".tr, style: theme.textTheme.bodyLarge),
SizedBox(height: 3.v),
BlocSelector<BodyCircumferencesBloc, BodyCircumferencesState, TextEditingController?>(
selector: (state) => state.hipCircumferenceController,
builder: (context, hipCircumferenceController) {
return CustomTextFormField(
inputFormatters: [digitsOnly], controller: hipCircumferenceController, hintText: "90".tr, textInputAction: TextInputAction.done);
}),
Spacer(),
BlocBuilder<BodyCircumferencesBloc, BodyCircumferencesState>(builder: (context, state) {
return NextStepButton(
text: "continue".tr.toUpperCase(),
margin: EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
onPressed: onTapContinue(state, context));
}),
])));
}
onTapContinue(BodyCircumferencesState state, BuildContext context) {
return () {
List<String> errors = validateFields(
state.chestCircumferenceController!.text,
state.waistCircumferenceController!.text,
state.hipCircumferenceController!.text);
List<String> errors =
validateFields(state.chestCircumferenceController!.text, state.waistCircumferenceController!.text, state.hipCircumferenceController!.text);
if (errors.isEmpty) {
UserRegistrationSheet sheet =
state.bodyCircumferencesModelObj!.userRegistrationSheet;
UserRegistrationSheet sheet = state.bodyCircumferencesModelObj!.userRegistrationSheet;
sheet.chestCircumference = state.chestCircumferenceController!.text;
sheet.waistCircumference = state.waistCircumferenceController!.text;
sheet.hipCircumference = state.hipCircumferenceController!.text;
Navigator.pushNamed(
context,
AppRoutes.weightScreen,
arguments: sheet,
);
GoRouter.of(context).goNamed('weightScreen', extra: sheet);
} else {
String errorMessage = '';
@@ -161,10 +130,8 @@ class BodyCircumferencesScreen extends StatelessWidget {
_buildAppBar(BuildContext context) {
return CustomAppBar(
leadingWidth: 29.h,
leading: AppbarLeadingImage(
imagePath: ImageConstant.imgArrowLeft,
margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v),
onTap: onTapBack(context)),
leading:
AppbarLeadingImage(imagePath: ImageConstant.imgArrowLeft, margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v), onTap: onTapBack(context)),
centerTitle: true,
title: AppbarTitleImage(imagePath: ImageConstant.imgLogoEatvio));
}
@@ -177,15 +144,11 @@ class BodyCircumferencesScreen extends StatelessWidget {
/// Navigates to the previous screen.
onTapArrowLeft(BuildContext context) {
NavigatorService.goBack();
GoRouter.of(context).pop();
}
/// Navigates to the weightScreen when the action is triggered.
navigateToWeight(BuildContext context) {
NavigatorService.pushNamed(
AppRoutes.weightScreen,
);
}
navigateToWeight(BuildContext context) => GoRouter.of(context).goNamed('weightScreen');
List<String> validateFields(
String chestCircumference,
@@ -194,14 +157,11 @@ class BodyCircumferencesScreen extends StatelessWidget {
) {
List<String> errors = [];
if (chestCircumference.isEmpty |
waistCircumference.isEmpty |
hipCircumference.isEmpty) {
if (chestCircumference.isEmpty | waistCircumference.isEmpty | hipCircumference.isEmpty) {
errors.add('circumference_empty'.tr);
}
if (![chestCircumference, waistCircumference, hipCircumference]
.every((circumference) => int.tryParse(circumference) != null)) {
if (![chestCircumference, waistCircumference, hipCircumference].every((circumference) => int.tryParse(circumference) != null)) {
errors.add('circumference_is_not_a_number'.tr);
}

View File

@@ -1,3 +1,5 @@
import 'package:go_router/go_router.dart';
import 'bloc/city_of_living_bloc.dart';
import 'models/city_of_living_model.dart';
import 'package:eatvio_app/core/app_export.dart';
@@ -13,15 +15,12 @@ import 'package:flutter/material.dart';
class CityOfLivingScreen extends StatelessWidget {
const CityOfLivingScreen({Key? key}) : super(key: key);
static Widget builder(BuildContext context) {
final sheet =
ModalRoute.of(context)!.settings.arguments as UserRegistrationSheet;
static Widget builder(BuildContext context, sheet) {
return BlocProvider<CityOfLivingBloc>(
create: (context) => CityOfLivingBloc(CityOfLivingState(
cityOfLivingModelObj:
CityOfLivingModel(userRegistrationSheet: sheet)))
..add(CityOfLivingInitialEvent()),
child: CityOfLivingScreen());
create: (context) =>
CityOfLivingBloc(CityOfLivingState(cityOfLivingModelObj: CityOfLivingModel(userRegistrationSheet: sheet)))..add(CityOfLivingInitialEvent()),
child: CityOfLivingScreen(),
);
}
@override
@@ -1145,132 +1144,106 @@ class CityOfLivingScreen extends StatelessWidget {
"Ясный",
"Яхрома"
];
mediaQueryData = MediaQuery.of(context);
return SafeArea(
child: Scaffold(
appBar: _buildAppBar(context),
body: Container(
width: double.maxFinite,
padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v),
child: Column(children: [
SizedBox(height: 7.v),
Container(
height: 16.v,
width: 343.h,
decoration: BoxDecoration(
color: theme.colorScheme.onPrimary,
borderRadius: BorderRadius.circular(8.h),
border: Border.all(
color: theme.colorScheme.primary, width: 1.h)),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.h),
child: LinearProgressIndicator(
value: 0.32,
backgroundColor: theme.colorScheme.onPrimary,
valueColor: AlwaysStoppedAnimation<Color>(
appTheme.blueA400)))),
SizedBox(height: 39.v),
Text("place_of_living".tr,
style: theme.textTheme.headlineSmall),
Spacer(flex: 34),
Text("country".tr, style: theme.textTheme.bodyLarge),
SizedBox(height: 2.v),
BlocSelector<CityOfLivingBloc, CityOfLivingState,
CityOfLivingModel?>(
selector: (state) => state.cityOfLivingModelObj,
builder: (context, cityOfLivingModelObj) {
return CustomDropDown(
icon: Container(
margin:
EdgeInsets.fromLTRB(30.h, 11.v, 17.h, 11.v),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(1.h)),
child: CustomImageView(
imagePath: ImageConstant.imgFavorite,
height: 12.adaptSize,
width: 12.adaptSize)),
hintText: "Не выбрано".tr,
items: cityOfLivingModelObj?.dropdownItemList ?? [],
onChanged: onDropDownChange(context));
}),
SizedBox(height: 22.v),
Text("Населенный пункт", style: theme.textTheme.bodyLarge),
SizedBox(height: 2.v),
BlocBuilder<CityOfLivingBloc, CityOfLivingState>(
builder: (context, state) {
return RawAutocomplete<String>(
optionsBuilder: (TextEditingValue textEditingValue) =>
_options.where((String option) {
return option
.toLowerCase()
.startsWith(textEditingValue.text.toLowerCase());
}),
fieldViewBuilder: (
BuildContext context,
TextEditingController textEditingController,
FocusNode focusNode,
VoidCallback onFieldSubmitted,
) {
return CustomTextFormField(
controller: textEditingController,
focusNode: focusNode,
onFieldSubmitted: (String value) =>
onFieldSubmitted(),
);
},
optionsViewBuilder: (
BuildContext context,
AutocompleteOnSelected<String> onSelected,
Iterable<String> options,
) {
return Align(
alignment: Alignment.topLeft,
child: Material(
elevation: 4.0,
child: SizedBox(
height: 200.0,
child: ListView.builder(
padding: const EdgeInsets.all(8.0),
itemCount: options.length,
itemBuilder: (BuildContext context, int index) {
final String option =
options.elementAt(index);
return GestureDetector(
onTap: () => onSelected(option),
child: ListTile(
title: Text(option),
),
);
},
),
),
),
);
},
return Scaffold(
appBar: _buildAppBar(context),
body: Container(
width: double.maxFinite,
padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v),
child: Column(children: [
SizedBox(height: 7.v),
Container(
height: 16.v,
width: 343.h,
decoration: BoxDecoration(
color: theme.colorScheme.onPrimary,
borderRadius: BorderRadius.circular(8.h),
border: Border.all(color: theme.colorScheme.primary, width: 1.h)),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.h),
child: LinearProgressIndicator(
value: 0.32, backgroundColor: theme.colorScheme.onPrimary, valueColor: AlwaysStoppedAnimation<Color>(appTheme.blueA400)))),
SizedBox(height: 39.v),
Text("place_of_living".tr, style: theme.textTheme.headlineSmall),
Spacer(flex: 34),
Text("country".tr, style: theme.textTheme.bodyLarge),
SizedBox(height: 2.v),
BlocSelector<CityOfLivingBloc, CityOfLivingState, CityOfLivingModel?>(
selector: (state) => state.cityOfLivingModelObj,
builder: (context, cityOfLivingModelObj) {
return CustomDropDown(
icon: Container(
margin: EdgeInsets.fromLTRB(30.h, 11.v, 17.h, 11.v),
decoration: BoxDecoration(borderRadius: BorderRadius.circular(1.h)),
child: CustomImageView(imagePath: ImageConstant.imgFavorite, height: 12.adaptSize, width: 12.adaptSize)),
hintText: "Не выбрано".tr,
items: cityOfLivingModelObj?.dropdownItemList ?? [],
onChanged: onDropDownChange(context));
}),
SizedBox(height: 22.v),
Text("Населенный пункт", style: theme.textTheme.bodyLarge),
SizedBox(height: 2.v),
BlocBuilder<CityOfLivingBloc, CityOfLivingState>(builder: (context, state) {
return RawAutocomplete<String>(
optionsBuilder: (TextEditingValue textEditingValue) => _options.where((String option) {
return option.toLowerCase().startsWith(textEditingValue.text.toLowerCase());
}),
fieldViewBuilder: (
BuildContext context,
TextEditingController textEditingController,
FocusNode focusNode,
VoidCallback onFieldSubmitted,
) {
return CustomTextFormField(
controller: textEditingController,
focusNode: focusNode,
onFieldSubmitted: (String value) => onFieldSubmitted(),
);
}),
Spacer(flex: 65),
BlocBuilder<CityOfLivingBloc, CityOfLivingState>(
builder: (context, state) {
return NextStepButton(
text: "continue".tr.toUpperCase(),
margin: EdgeInsets.only(
left: 16.h, right: 16.h, bottom: 50.v),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
onPressed: onTapContinue(state, context));
}),
]))));
},
optionsViewBuilder: (
BuildContext context,
AutocompleteOnSelected<String> onSelected,
Iterable<String> options,
) {
return Align(
alignment: Alignment.topLeft,
child: Material(
elevation: 4.0,
child: SizedBox(
height: 200.0,
child: ListView.builder(
padding: const EdgeInsets.all(8.0),
itemCount: options.length,
itemBuilder: (BuildContext context, int index) {
final String option = options.elementAt(index);
return GestureDetector(
onTap: () => onSelected(option),
child: ListTile(
title: Text(option),
),
);
},
),
),
),
);
},
);
}),
Spacer(flex: 65),
BlocBuilder<CityOfLivingBloc, CityOfLivingState>(builder: (context, state) {
return NextStepButton(
text: "continue".tr.toUpperCase(),
margin: EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
onPressed: onTapContinue(state, context));
}),
])));
}
onTapContinue(CityOfLivingState state, BuildContext context) {
return () {
UserRegistrationSheet sheet =
state.cityOfLivingModelObj!.userRegistrationSheet;
Navigator.pushNamed(
context,
AppRoutes.bodyCircumferencesScreen,
arguments: sheet,
);
UserRegistrationSheet sheet = state.cityOfLivingModelObj!.userRegistrationSheet;
GoRouter.of(context).goNamed('bodyCircumferencesScreen', extra: sheet);
};
}
@@ -1284,10 +1257,8 @@ class CityOfLivingScreen extends StatelessWidget {
_buildAppBar(BuildContext context) {
return CustomAppBar(
leadingWidth: 29.h,
leading: AppbarLeadingImage(
imagePath: ImageConstant.imgArrowLeft,
margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v),
onTap: onTapBack(context)),
leading:
AppbarLeadingImage(imagePath: ImageConstant.imgArrowLeft, margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v), onTap: onTapBack(context)),
centerTitle: true,
title: AppbarTitleImage(imagePath: ImageConstant.imgLogoEatvio));
}
@@ -1300,12 +1271,11 @@ class CityOfLivingScreen extends StatelessWidget {
/// Navigates to the previous screen.
onTapArrowLeft(BuildContext context) {
NavigatorService.goBack();
GoRouter.of(context).pop();
}
}
selectProduct(AutocompleteOnSelected<String> onSelected, String option,
CityOfLivingState state) {
selectProduct(AutocompleteOnSelected<String> onSelected, String option, CityOfLivingState state) {
state.cityController?.text = option;
return () {
onSelected(option);

View File

@@ -1,3 +1,5 @@
import 'package:go_router/go_router.dart';
import 'bloc/credentials_bloc.dart';
import 'models/credentials_model.dart';
import 'package:eatvio_app/core/app_export.dart';
@@ -10,9 +12,7 @@ import 'package:eatvio_app/widgets/custom_text_form_field.dart';
import 'package:flutter/material.dart';
class CredentialsScreen extends StatelessWidget {
static Widget builder(BuildContext context) {
final sheet =
ModalRoute.of(context)!.settings.arguments as UserRegistrationSheet;
static Widget builder(BuildContext context, sheet) {
return BlocProvider<CredentialsBloc>(
create: (context) => CredentialsBloc(
CredentialsState(
@@ -40,45 +40,33 @@ class CredentialsScreen extends StatelessWidget {
decoration: BoxDecoration(
color: theme.colorScheme.onPrimary,
borderRadius: BorderRadius.circular(8.h),
border: Border.all(
color: theme.colorScheme.primary, width: 1.h)),
border: Border.all(color: theme.colorScheme.primary, width: 1.h)),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.h),
child: LinearProgressIndicator(
value: 0.05,
backgroundColor: theme.colorScheme.onPrimary,
valueColor: AlwaysStoppedAnimation<Color>(
appTheme.blueA400)))),
value: 0.05, backgroundColor: theme.colorScheme.onPrimary, valueColor: AlwaysStoppedAnimation<Color>(appTheme.blueA400)))),
SizedBox(height: 39.v),
Text("user_registration".tr,
style: theme.textTheme.headlineSmall),
Text("user_registration".tr, style: theme.textTheme.headlineSmall),
SizedBox(height: 16.v),
Text("email".tr, style: theme.textTheme.bodyLarge),
SizedBox(height: 3.v),
BlocSelector<CredentialsBloc, CredentialsState,
TextEditingController?>(
BlocSelector<CredentialsBloc, CredentialsState, TextEditingController?>(
selector: (state) => state.emailController,
builder: (context, emailController) {
return CustomTextFormField(
controller: emailController,
textInputAction: TextInputAction.done);
return CustomTextFormField(controller: emailController, textInputAction: TextInputAction.done);
}),
SizedBox(height: 22.v),
Text("username".tr, style: theme.textTheme.bodyLarge),
SizedBox(height: 3.v),
BlocSelector<CredentialsBloc, CredentialsState,
TextEditingController?>(
BlocSelector<CredentialsBloc, CredentialsState, TextEditingController?>(
selector: (state) => state.nameController,
builder: (context, nameController) {
return CustomTextFormField(
controller: nameController,
textInputAction: TextInputAction.done);
return CustomTextFormField(controller: nameController, textInputAction: TextInputAction.done);
}),
SizedBox(height: 19.v),
Text("password".tr, style: theme.textTheme.bodyLarge),
SizedBox(height: 5.v),
BlocSelector<CredentialsBloc, CredentialsState,
TextEditingController?>(
BlocSelector<CredentialsBloc, CredentialsState, TextEditingController?>(
selector: (state) => state.passwordController,
builder: (context, passwordController) {
return CustomTextFormField(
@@ -87,25 +75,18 @@ class CredentialsScreen extends StatelessWidget {
);
}),
SizedBox(height: 21.v),
Text("password_confirmation".tr,
style: theme.textTheme.bodyLarge),
Text("password_confirmation".tr, style: theme.textTheme.bodyLarge),
SizedBox(height: 3.v),
BlocSelector<CredentialsBloc, CredentialsState,
TextEditingController?>(
BlocSelector<CredentialsBloc, CredentialsState, TextEditingController?>(
selector: (state) => state.passwordConfirmationController,
builder: (context, passwordConfirmationController) {
return CustomTextFormField(
obscureText: true,
controller: passwordConfirmationController,
textInputAction: TextInputAction.done);
return CustomTextFormField(obscureText: true, controller: passwordConfirmationController, textInputAction: TextInputAction.done);
}),
Spacer(),
BlocBuilder<CredentialsBloc, CredentialsState>(
builder: (context, state) {
BlocBuilder<CredentialsBloc, CredentialsState>(builder: (context, state) {
return NextStepButton(
text: "continue".tr.toUpperCase(),
margin:
EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v),
margin: EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
onPressed: onTapContinue(state, context));
}),
@@ -114,23 +95,15 @@ class CredentialsScreen extends StatelessWidget {
onTapContinue(CredentialsState state, BuildContext context) {
return () {
List<String> errors = validateFields(
state.emailController!.text,
state.nameController!.text,
state.passwordController!.text,
state.passwordConfirmationController!.text);
List<String> errors =
validateFields(state.emailController!.text, state.nameController!.text, state.passwordController!.text, state.passwordConfirmationController!.text);
if (errors.isEmpty) {
UserRegistrationSheet sheet =
state.credentialsModelObj!.userRegistrationSheet;
UserRegistrationSheet sheet = state.credentialsModelObj!.userRegistrationSheet;
sheet.email = state.emailController!.text;
sheet.name = state.nameController!.text;
sheet.password = state.passwordController!.text;
sheet.passwordConfirmation = state.passwordConfirmationController!.text;
Navigator.pushNamed(
context,
AppRoutes.nameScreen,
arguments: sheet,
);
GoRouter.of(context).goNamed('nameScreen', extra: sheet);
} else {
String errorMessage = '';
@@ -163,14 +136,10 @@ class CredentialsScreen extends StatelessWidget {
/// Section Widget
_buildAppBar(BuildContext context) {
return CustomAppBar(
leadingWidth: 29.h,
centerTitle: true,
title: AppbarTitleImage(imagePath: ImageConstant.imgLogoEatvio));
return CustomAppBar(leadingWidth: 29.h, centerTitle: true, title: AppbarTitleImage(imagePath: ImageConstant.imgLogoEatvio));
}
List<String> validateFields(
String email, String name, String password, String passwordConfirmation) {
List<String> validateFields(String email, String name, String password, String passwordConfirmation) {
List<String> errors = [];
if (!isEmail(email)) {

View File

@@ -1,3 +1,5 @@
import 'package:go_router/go_router.dart';
import 'bloc/date_of_birth_bloc.dart';
import 'models/date_of_birth_model.dart';
import 'package:eatvio_app/core/app_export.dart';
@@ -11,15 +13,17 @@ import 'package:flutter/material.dart';
class DateOfBirthScreen extends StatefulWidget {
const DateOfBirthScreen({Key? key}) : super(key: key);
static Widget builder(BuildContext context) {
final sheet =
ModalRoute.of(context)!.settings.arguments as UserRegistrationSheet;
static Widget builder(BuildContext context, sheet) {
return BlocProvider<DateOfBirthBloc>(
create: (context) => DateOfBirthBloc(DateOfBirthState(
dateOfBirthModelObj:
DateOfBirthModel(userRegistrationSheet: sheet)))
..add(DateOfBirthInitialEvent()),
child: DateOfBirthScreen());
create: (context) => DateOfBirthBloc(
DateOfBirthState(
dateOfBirthModelObj: DateOfBirthModel(
userRegistrationSheet: sheet,
),
),
)..add(DateOfBirthInitialEvent()),
child: DateOfBirthScreen(),
);
}
@override
@@ -28,63 +32,49 @@ class DateOfBirthScreen extends StatefulWidget {
class _DateOfBirthScreenState extends State<DateOfBirthScreen> {
DateTime selectedDate = DateTime.now();
@override
Widget build(BuildContext context) {
mediaQueryData = MediaQuery.of(context);
return BlocBuilder<DateOfBirthBloc, DateOfBirthState>(
builder: (context, state) {
return SafeArea(
child: Scaffold(
appBar: _buildAppBar(context),
body: Container(
width: double.maxFinite,
padding:
EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v),
child: Column(children: [
Container(
height: 16.v,
width: 343.h,
decoration: BoxDecoration(
color: theme.colorScheme.onPrimary,
borderRadius: BorderRadius.circular(8.h),
border: Border.all(
color: theme.colorScheme.primary, width: 1.h)),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.h),
child: LinearProgressIndicator(
value: 0.24,
backgroundColor: theme.colorScheme.onPrimary,
valueColor: AlwaysStoppedAnimation<Color>(
appTheme.blueA400)))),
SizedBox(height: 38.v),
Text("date_of_birth".tr,
style: theme.textTheme.headlineSmall),
Spacer(flex: 39),
_buildDateInput(context),
Spacer(flex: 60),
BlocBuilder<DateOfBirthBloc, DateOfBirthState>(
builder: (context, state) {
return NextStepButton(
text: "continue".tr.toUpperCase(),
margin: EdgeInsets.only(
left: 16.h, right: 16.h, bottom: 50.v),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
onPressed: onTapContinue(state, context));
}),
]))));
return BlocBuilder<DateOfBirthBloc, DateOfBirthState>(builder: (context, state) {
return Scaffold(
appBar: _buildAppBar(context),
body: Container(
width: double.maxFinite,
padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v),
child: Column(children: [
Container(
height: 16.v,
width: 343.h,
decoration: BoxDecoration(
color: theme.colorScheme.onPrimary,
borderRadius: BorderRadius.circular(8.h),
border: Border.all(color: theme.colorScheme.primary, width: 1.h)),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.h),
child: LinearProgressIndicator(
value: 0.24, backgroundColor: theme.colorScheme.onPrimary, valueColor: AlwaysStoppedAnimation<Color>(appTheme.blueA400)))),
SizedBox(height: 38.v),
Text("date_of_birth".tr, style: theme.textTheme.headlineSmall),
Spacer(flex: 39),
_buildDateInput(context),
Spacer(flex: 60),
BlocBuilder<DateOfBirthBloc, DateOfBirthState>(builder: (context, state) {
return NextStepButton(
text: "continue".tr.toUpperCase(),
margin: EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
onPressed: onTapContinue(state, context));
}),
])));
});
}
onTapContinue(DateOfBirthState state, BuildContext context) {
return () {
UserRegistrationSheet sheet =
state.dateOfBirthModelObj!.userRegistrationSheet;
UserRegistrationSheet sheet = state.dateOfBirthModelObj!.userRegistrationSheet;
sheet.birthdate = selectedDate.toString();
Navigator.pushNamed(
context,
AppRoutes.cityOfLivingScreen,
arguments: sheet,
);
GoRouter.of(context).goNamed('cityOfLivingScreen', extra: sheet);
};
}
@@ -92,10 +82,8 @@ class _DateOfBirthScreenState extends State<DateOfBirthScreen> {
_buildAppBar(BuildContext context) {
return CustomAppBar(
leadingWidth: 29.h,
leading: AppbarLeadingImage(
imagePath: ImageConstant.imgArrowLeft,
margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v),
onTap: onTapBack(context)),
leading:
AppbarLeadingImage(imagePath: ImageConstant.imgArrowLeft, margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v), onTap: onTapBack(context)),
centerTitle: true,
title: AppbarTitleImage(imagePath: ImageConstant.imgLogoEatvio));
}
@@ -111,28 +99,17 @@ class _DateOfBirthScreenState extends State<DateOfBirthScreen> {
return Container(
width: 343.h,
padding: EdgeInsets.symmetric(horizontal: 4.h, vertical: 1.v),
decoration: AppDecoration.outlineBlueA
.copyWith(borderRadius: BorderRadiusStyle.circleBorder8),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
CustomImageView(
imagePath: ImageConstant.imgFavorite,
height: 12.adaptSize,
width: 12.adaptSize,
radius: BorderRadius.circular(1.h)),
SizedBox(height: 6.v),
Align(
alignment: Alignment.center,
child: GestureDetector(
onTap: onTapCalendar(context),
child: Text(
'${selectedDate.day}.${selectedDate.month}.${selectedDate.year}',
style: theme.textTheme.titleMedium))),
SizedBox(height: 6.v)
]));
decoration: AppDecoration.outlineBlueA.copyWith(borderRadius: BorderRadiusStyle.circleBorder8),
child: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [
CustomImageView(imagePath: ImageConstant.imgFavorite, height: 12.adaptSize, width: 12.adaptSize, radius: BorderRadius.circular(1.h)),
SizedBox(height: 6.v),
Align(
alignment: Alignment.center,
child: GestureDetector(
onTap: onTapCalendar(context),
child: Text('${selectedDate.day}.${selectedDate.month}.${selectedDate.year}', style: theme.textTheme.titleMedium))),
SizedBox(height: 6.v)
]));
}
onTapCalendar(BuildContext context) {
@@ -143,7 +120,7 @@ class _DateOfBirthScreenState extends State<DateOfBirthScreen> {
/// Navigates to the previous screen.
onTapArrowLeft(BuildContext context) {
NavigatorService.goBack();
GoRouter.of(context).pop();
}
/// Displays a date picker dialog to update the selected date

View File

@@ -1,3 +1,5 @@
import 'package:go_router/go_router.dart';
import 'bloc/favorite_foods_bloc.dart';
import 'models/favorite_foods_model.dart';
import 'package:eatvio_app/core/app_export.dart';
@@ -12,14 +14,10 @@ import 'package:flutter/material.dart';
class FavoriteFoodsScreen extends StatefulWidget {
const FavoriteFoodsScreen({Key? key}) : super(key: key);
static Widget builder(BuildContext context) {
final sheet =
ModalRoute.of(context)!.settings.arguments as UserRegistrationSheet;
static Widget builder(BuildContext context, sheet) {
return BlocProvider<FavoriteFoodsBloc>(
create: (context) => FavoriteFoodsBloc(FavoriteFoodsState(
favoriteFoodsModelObj:
FavoriteFoodsModel(userRegistrationSheet: sheet)))
..add(FavoriteFoodsInitialEvent()),
create: (context) =>
FavoriteFoodsBloc(FavoriteFoodsState(favoriteFoodsModelObj: FavoriteFoodsModel(userRegistrationSheet: sheet)))..add(FavoriteFoodsInitialEvent()),
child: FavoriteFoodsScreen());
}
@@ -30,87 +28,65 @@ class FavoriteFoodsScreen extends StatefulWidget {
class _FavoriteFoodsScreenState extends State<FavoriteFoodsScreen> {
@override
Widget build(BuildContext context) {
mediaQueryData = MediaQuery.of(context);
return SafeArea(
child: Scaffold(
resizeToAvoidBottomInset: false,
appBar: _buildAppBar(context),
body: Container(
width: double.maxFinite,
padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v),
child: Column(children: [
SizedBox(height: 7.v),
Container(
height: 16.v,
width: 343.h,
decoration: BoxDecoration(
color: theme.colorScheme.onPrimary,
borderRadius: BorderRadius.circular(8.h),
border: Border.all(
color: theme.colorScheme.primary, width: 1.h)),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.h),
child: LinearProgressIndicator(
value: 0.90,
backgroundColor: theme.colorScheme.onPrimary,
valueColor: AlwaysStoppedAnimation<Color>(
appTheme.blueA400)))),
SizedBox(height: 19.v),
Container(
width: 327.h,
margin: EdgeInsets.only(left: 8.h, right: 7.h),
child: Text("favorite_unfavorite_foods".tr,
maxLines: 2,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
style: theme.textTheme.headlineSmall)),
SizedBox(height: 13.v),
_buildTextFormFieldFav(context),
SizedBox(height: 12.v),
_buildTextFormFieldUnfav(context),
Spacer(),
BlocBuilder<FavoriteFoodsBloc, FavoriteFoodsState>(
builder: (context, state) {
return NextStepButton(
text: "continue".tr.toUpperCase(),
margin: EdgeInsets.only(
left: 16.h, right: 16.h, bottom: 40.v),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
onPressed: onTapContinue(state, context));
}),
BlocBuilder<FavoriteFoodsBloc, FavoriteFoodsState>(
builder: (context, state) {
return GestureDetector(
onTap: onTapSkip(state, context),
child: Text("skip".tr, style: theme.textTheme.bodyLarge),
);
}),
]))));
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: _buildAppBar(context),
body: Container(
width: double.maxFinite,
padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v),
child: Column(children: [
SizedBox(height: 7.v),
Container(
height: 16.v,
width: 343.h,
decoration: BoxDecoration(
color: theme.colorScheme.onPrimary,
borderRadius: BorderRadius.circular(8.h),
border: Border.all(color: theme.colorScheme.primary, width: 1.h)),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.h),
child: LinearProgressIndicator(
value: 0.90, backgroundColor: theme.colorScheme.onPrimary, valueColor: AlwaysStoppedAnimation<Color>(appTheme.blueA400)))),
SizedBox(height: 19.v),
Container(
width: 327.h,
margin: EdgeInsets.only(left: 8.h, right: 7.h),
child: Text("favorite_unfavorite_foods".tr,
maxLines: 2, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, style: theme.textTheme.headlineSmall)),
SizedBox(height: 13.v),
_buildTextFormFieldFav(context),
SizedBox(height: 12.v),
_buildTextFormFieldUnfav(context),
Spacer(),
BlocBuilder<FavoriteFoodsBloc, FavoriteFoodsState>(builder: (context, state) {
return NextStepButton(
text: "continue".tr.toUpperCase(),
margin: EdgeInsets.only(left: 16.h, right: 16.h, bottom: 40.v),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
onPressed: onTapContinue(state, context));
}),
BlocBuilder<FavoriteFoodsBloc, FavoriteFoodsState>(builder: (context, state) {
return GestureDetector(
onTap: onTapSkip(state, context),
child: Text("skip".tr, style: theme.textTheme.bodyLarge),
);
}),
])));
}
onTapSkip(FavoriteFoodsState state, BuildContext context) {
return () {
UserRegistrationSheet sheet =
state.favoriteFoodsModelObj!.userRegistrationSheet;
Navigator.pushNamed(
context,
AppRoutes.additionalInformationScreen,
arguments: sheet,
);
UserRegistrationSheet sheet = state.favoriteFoodsModelObj!.userRegistrationSheet;
GoRouter.of(context).goNamed('additionalInformationScreen', extra: sheet);
};
}
onTapContinue(FavoriteFoodsState state, BuildContext context) {
return () {
UserRegistrationSheet sheet =
state.favoriteFoodsModelObj!.userRegistrationSheet;
UserRegistrationSheet sheet = state.favoriteFoodsModelObj!.userRegistrationSheet;
sheet.favoriteFoods = state.favoriteFoodsController!.text;
sheet.unfavoriteFoods = state.unfavoriteFoodsController!.text;
Navigator.pushNamed(
context,
AppRoutes.additionalInformationScreen,
arguments: sheet,
);
GoRouter.of(context).goNamed('additionalInformationScreen', extra: sheet);
};
}
@@ -118,10 +94,8 @@ class _FavoriteFoodsScreenState extends State<FavoriteFoodsScreen> {
_buildAppBar(BuildContext context) {
return CustomAppBar(
leadingWidth: 31.h,
leading: AppbarLeadingImage(
imagePath: ImageConstant.imgArrowLeft,
margin: EdgeInsets.only(left: 18.h, top: 15.v, bottom: 15.v),
onTap: onTapBack(context)),
leading:
AppbarLeadingImage(imagePath: ImageConstant.imgArrowLeft, margin: EdgeInsets.only(left: 18.h, top: 15.v, bottom: 15.v), onTap: onTapBack(context)),
centerTitle: true,
title: AppbarTitleImage(imagePath: ImageConstant.imgLogoEatvio));
}
@@ -137,14 +111,10 @@ class _FavoriteFoodsScreenState extends State<FavoriteFoodsScreen> {
return Column(children: [
Text("favorite_foods".tr, style: CustomTextStyles.titleMediumSegoeUI),
SizedBox(height: 2.v),
BlocSelector<FavoriteFoodsBloc, FavoriteFoodsState,
TextEditingController?>(
BlocSelector<FavoriteFoodsBloc, FavoriteFoodsState, TextEditingController?>(
selector: (state) => state.favoriteFoodsController,
builder: (context, favoriteFoodsController) {
return CustomTextFormField(
controller: favoriteFoodsController,
hintText: "enter_information".tr,
maxLines: 7);
return CustomTextFormField(controller: favoriteFoodsController, hintText: "enter_information".tr, maxLines: 7);
})
]);
}
@@ -154,28 +124,22 @@ class _FavoriteFoodsScreenState extends State<FavoriteFoodsScreen> {
return Column(children: [
Text("unfavorite_foods".tr, style: CustomTextStyles.titleMediumSegoeUI),
SizedBox(height: 2.v),
BlocSelector<FavoriteFoodsBloc, FavoriteFoodsState,
TextEditingController?>(
BlocSelector<FavoriteFoodsBloc, FavoriteFoodsState, TextEditingController?>(
selector: (state) => state.unfavoriteFoodsController,
builder: (context, unfavoriteFoodsController) {
return CustomTextFormField(
controller: unfavoriteFoodsController,
hintText: "enter_information".tr,
textInputAction: TextInputAction.done,
maxLines: 7);
controller: unfavoriteFoodsController, hintText: "enter_information".tr, textInputAction: TextInputAction.done, maxLines: 7);
})
]);
}
/// Navigates to the previous screen.
onTapArrowLeft(BuildContext context) {
NavigatorService.goBack();
GoRouter.of(context).pop();
}
/// Navigates to the scheduleScreen when the action is triggered.
navigateToAdditionalInformationScreen(BuildContext context) {
NavigatorService.pushNamed(
AppRoutes.additionalInformationScreen,
);
GoRouter.of(context).goNamed('additionalInformationScreen');
}
}

View File

@@ -1,3 +1,5 @@
import 'package:go_router/go_router.dart';
import 'bloc/favorite_sports_bloc.dart';
import 'models/favorite_sports_model.dart';
import 'package:eatvio_app/core/app_export.dart';
@@ -12,13 +14,9 @@ import 'package:flutter/material.dart';
class FavoriteSportsScreen extends StatefulWidget {
const FavoriteSportsScreen({Key? key}) : super(key: key);
static Widget builder(BuildContext context) {
final sheet =
ModalRoute.of(context)!.settings.arguments as UserRegistrationSheet;
static Widget builder(BuildContext context, sheet) {
return BlocProvider<FavoriteSportsBloc>(
create: (context) => FavoriteSportsBloc(FavoriteSportsState(
favoriteSportsModelObj:
FavoriteSportsModel(userRegistrationSheet: sheet)))
create: (context) => FavoriteSportsBloc(FavoriteSportsState(favoriteSportsModelObj: FavoriteSportsModel(userRegistrationSheet: sheet)))
..add(FavoriteSportsInitialEvent()),
child: FavoriteSportsScreen());
}
@@ -30,92 +28,66 @@ class FavoriteSportsScreen extends StatefulWidget {
class _FavoriteSportsScreenState extends State<FavoriteSportsScreen> {
@override
Widget build(BuildContext context) {
mediaQueryData = MediaQuery.of(context);
return SafeArea(
child: Scaffold(
resizeToAvoidBottomInset: false,
appBar: _buildAppBar(context),
body: Container(
width: double.maxFinite,
padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 5.v),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(height: 20.v),
Container(
height: 16.v,
width: 343.h,
decoration: BoxDecoration(
color: theme.colorScheme.onPrimary,
borderRadius: BorderRadius.circular(8.h),
border: Border.all(
color: theme.colorScheme.primary,
width: 1.h)),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.h),
child: LinearProgressIndicator(
value: 0.83,
backgroundColor: theme.colorScheme.onPrimary,
valueColor: AlwaysStoppedAnimation<Color>(
appTheme.blueA400)))),
SizedBox(height: 38.v),
Text("your_favorite_sports".tr,
style: theme.textTheme.headlineSmall),
Spacer(flex: 32),
_buildTextFormFieldSection(context),
Spacer(flex: 67),
BlocBuilder<FavoriteSportsBloc, FavoriteSportsState>(
builder: (context, state) {
return NextStepButton(
text: "continue".tr.toUpperCase(),
margin: EdgeInsets.only(
left: 16.h, right: 16.h, bottom: 50.v),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
onPressed: onTapContinue(state, context));
}),
BlocBuilder<FavoriteSportsBloc, FavoriteSportsState>(
builder: (context, state) {
return GestureDetector(
onTap: onTapSkip(state, context),
child:
Text("skip".tr, style: theme.textTheme.bodyLarge),
);
}),
]))));
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: _buildAppBar(context),
body: Container(
width: double.maxFinite,
padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 5.v),
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
SizedBox(height: 20.v),
Container(
height: 16.v,
width: 343.h,
decoration: BoxDecoration(
color: theme.colorScheme.onPrimary,
borderRadius: BorderRadius.circular(8.h),
border: Border.all(color: theme.colorScheme.primary, width: 1.h)),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.h),
child: LinearProgressIndicator(
value: 0.83, backgroundColor: theme.colorScheme.onPrimary, valueColor: AlwaysStoppedAnimation<Color>(appTheme.blueA400)))),
SizedBox(height: 38.v),
Text("your_favorite_sports".tr, style: theme.textTheme.headlineSmall),
Spacer(flex: 32),
_buildTextFormFieldSection(context),
Spacer(flex: 67),
BlocBuilder<FavoriteSportsBloc, FavoriteSportsState>(builder: (context, state) {
return NextStepButton(
text: "continue".tr.toUpperCase(),
margin: EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
onPressed: onTapContinue(state, context));
}),
BlocBuilder<FavoriteSportsBloc, FavoriteSportsState>(builder: (context, state) {
return GestureDetector(
onTap: onTapSkip(state, context),
child: Text("skip".tr, style: theme.textTheme.bodyLarge),
);
}),
])));
}
onTapSkip(FavoriteSportsState state, BuildContext context) {
return () {
UserRegistrationSheet sheet =
state.favoriteSportsModelObj!.userRegistrationSheet;
Navigator.pushNamed(
context,
AppRoutes.favoriteFoodsScreen,
arguments: sheet,
);
UserRegistrationSheet sheet = state.favoriteSportsModelObj!.userRegistrationSheet;
GoRouter.of(context).goNamed('favoriteFoodsScreen', extra: sheet);
};
}
onTapContinue(FavoriteSportsState state, BuildContext context) {
return () {
UserRegistrationSheet sheet =
state.favoriteSportsModelObj!.userRegistrationSheet;
UserRegistrationSheet sheet = state.favoriteSportsModelObj!.userRegistrationSheet;
sheet.aboutSportHobbies = state.enterinformationController!.text;
Navigator.pushNamed(
context,
AppRoutes.favoriteFoodsScreen,
arguments: sheet,
);
GoRouter.of(context).goNamed('favoriteFoodsScreen', extra: sheet);
};
}
_buildAppBar(BuildContext context) {
return CustomAppBar(
leadingWidth: 29.h,
leading: AppbarLeadingImage(
imagePath: ImageConstant.imgArrowLeft,
margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v),
onTap: onTapBack(context)),
leading:
AppbarLeadingImage(imagePath: ImageConstant.imgArrowLeft, margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v), onTap: onTapBack(context)),
centerTitle: true,
title: AppbarTitleImage(imagePath: ImageConstant.imgLogoEatvio));
}
@@ -129,11 +101,9 @@ class _FavoriteSportsScreenState extends State<FavoriteSportsScreen> {
/// Section Widget
_buildTextFormFieldSection(BuildContext context) {
return Column(children: [
Text("fill_in_information".tr,
style: CustomTextStyles.titleMediumSegoeUI),
Text("fill_in_information".tr, style: CustomTextStyles.titleMediumSegoeUI),
SizedBox(height: 2.v),
BlocSelector<FavoriteSportsBloc, FavoriteSportsState,
TextEditingController?>(
BlocSelector<FavoriteSportsBloc, FavoriteSportsState, TextEditingController?>(
selector: (state) => state.enterinformationController,
builder: (context, enterinformationController) {
return CustomTextFormField(
@@ -147,6 +117,6 @@ class _FavoriteSportsScreenState extends State<FavoriteSportsScreen> {
}
onTapArrowLeft(BuildContext context) {
NavigatorService.goBack();
GoRouter.of(context).pop();
}
}

View File

@@ -1,3 +1,5 @@
import 'package:go_router/go_router.dart';
import 'bloc/gender_bloc.dart';
import 'models/gender_model.dart';
import 'package:eatvio_app/data/models/userRegistrationModel/user_registration_model.dart';
@@ -11,9 +13,7 @@ import 'package:flutter/material.dart';
class GenderScreen extends StatefulWidget {
const GenderScreen({Key? key}) : super(key: key);
static Widget builder(BuildContext context) {
final sheet =
ModalRoute.of(context)!.settings.arguments as UserRegistrationSheet;
static Widget builder(BuildContext context, sheet) {
return BlocProvider<GenderBloc>(
create: (context) => GenderBloc(
GenderState(
@@ -32,66 +32,57 @@ class _GenderScreenState extends State<GenderScreen> {
Widget build(BuildContext context) {
mediaQueryData = MediaQuery.of(context);
return BlocBuilder<GenderBloc, GenderState>(builder: (context, state) {
return SafeArea(
child: Scaffold(
appBar: _buildAppBar(context),
body: Container(
width: double.maxFinite,
padding:
EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v),
child: Column(children: [
SizedBox(height: 7.v),
Container(
height: 16.v,
width: 343.h,
decoration: BoxDecoration(
color: theme.colorScheme.onPrimary,
borderRadius: BorderRadius.circular(8.h),
border: Border.all(
color: theme.colorScheme.primary, width: 1.h)),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.h),
child: LinearProgressIndicator(
value: 0.16,
backgroundColor: theme.colorScheme.onPrimary,
valueColor: AlwaysStoppedAnimation<Color>(
appTheme.blueA400)))),
SizedBox(height: 36.v),
Text("your_gender".tr,
style: theme.textTheme.headlineSmall),
RadioListTile(
title: Text('Мужской'),
value: 'male',
groupValue: state.selectedRadioValue,
onChanged: onMale(context),
secondary: Image.asset(
'assets/images/img_male.png',
width: 40,
height: 40,
),
),
RadioListTile(
title: Text('Женский'),
value: 'female',
groupValue: state.selectedRadioValue,
onChanged: onFemale(context),
secondary: Image.asset(
'assets/images/img_female.png',
width: 40,
height: 40,
),
),
Spacer(flex: 66),
BlocBuilder<GenderBloc, GenderState>(
builder: (context, state) {
return NextStepButton(
text: "continue".tr.toUpperCase(),
margin: EdgeInsets.only(
left: 16.h, right: 16.h, bottom: 50.v),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
onPressed: onTapNext(state, context));
}),
]))));
return Scaffold(
appBar: _buildAppBar(context),
body: Container(
width: double.maxFinite,
padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v),
child: Column(children: [
SizedBox(height: 7.v),
Container(
height: 16.v,
width: 343.h,
decoration: BoxDecoration(
color: theme.colorScheme.onPrimary,
borderRadius: BorderRadius.circular(8.h),
border: Border.all(color: theme.colorScheme.primary, width: 1.h)),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.h),
child: LinearProgressIndicator(
value: 0.16, backgroundColor: theme.colorScheme.onPrimary, valueColor: AlwaysStoppedAnimation<Color>(appTheme.blueA400)))),
SizedBox(height: 36.v),
Text("your_gender".tr, style: theme.textTheme.headlineSmall),
RadioListTile(
title: Text('Мужской'),
value: 'male',
groupValue: state.selectedRadioValue,
onChanged: onMale(context),
secondary: Image.asset(
'assets/images/img_male.png',
width: 40,
height: 40,
),
),
RadioListTile(
title: Text('Женский'),
value: 'female',
groupValue: state.selectedRadioValue,
onChanged: onFemale(context),
secondary: Image.asset(
'assets/images/img_female.png',
width: 40,
height: 40,
),
),
Spacer(flex: 66),
BlocBuilder<GenderBloc, GenderState>(builder: (context, state) {
return NextStepButton(
text: "continue".tr.toUpperCase(),
margin: EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
onPressed: onTapNext(state, context));
}),
])));
});
}
@@ -99,50 +90,42 @@ class _GenderScreenState extends State<GenderScreen> {
return () {
UserRegistrationSheet sheet = state.genderModelObj!.userRegistrationSheet;
sheet.gender = state.selectedRadioValue!;
Navigator.pushNamed(
context,
AppRoutes.dateOfBirthScreen,
arguments: sheet,
);
GoRouter.of(context).goNamed('dateOfBirthScreen', extra: sheet);
};
}
onMale(BuildContext context) {
return (value) {
BlocProvider.of<GenderBloc>(context)
.add(GenderSelectEvent(value: 'male'));
BlocProvider.of<GenderBloc>(context).add(GenderSelectEvent(value: 'male'));
};
}
onFemale(BuildContext context) {
return (value) {
BlocProvider.of<GenderBloc>(context)
.add(GenderSelectEvent(value: 'female'));
BlocProvider.of<GenderBloc>(context).add(GenderSelectEvent(value: 'female'));
};
}
/// Section Widget
_buildAppBar(BuildContext context) {
return CustomAppBar(
leadingWidth: 29.h,
leading: AppbarLeadingImage(
imagePath: ImageConstant.imgArrowLeft,
margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v),
onTap: onTapBack()),
centerTitle: true,
title: AppbarTitleImage(imagePath: ImageConstant.imgLogoEatvio));
leadingWidth: 29.h,
leading: AppbarLeadingImage(imagePath: ImageConstant.imgArrowLeft, margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v), onTap: onTapBack()),
centerTitle: true,
title: AppbarTitleImage(
imagePath: ImageConstant.imgLogoEatvio,
),
);
}
Function onTapBack() {
return () {
NavigatorService.pushNamed(
AppRoutes.dateOfBirthScreen,
);
GoRouter.of(context).pop();
};
}
/// Navigates to the previous screen.
onTapArrowLeft(BuildContext context) {
NavigatorService.goBack();
GoRouter.of(context).pop();
}
}

View File

@@ -1,3 +1,5 @@
import 'package:go_router/go_router.dart';
import 'bloc/height_bloc.dart';
import 'models/height_model.dart';
import 'package:eatvio_app/core/app_export.dart';
@@ -13,98 +15,91 @@ import 'package:flutter/material.dart';
class HeightScreen extends StatelessWidget {
const HeightScreen({Key? key}) : super(key: key);
static Widget builder(BuildContext context) {
final sheet =
ModalRoute.of(context)!.settings.arguments as UserRegistrationSheet;
static Widget builder(BuildContext context, sheet) {
return BlocProvider<HeightBloc>(
create: (context) => HeightBloc(HeightState(
heightModelObj: HeightModel(userRegistrationSheet: sheet)))
..add(HeightInitialEvent()),
create: (context) => HeightBloc(HeightState(heightModelObj: HeightModel(userRegistrationSheet: sheet)))..add(HeightInitialEvent()),
child: HeightScreen());
}
@override
Widget build(BuildContext context) {
mediaQueryData = MediaQuery.of(context);
return SafeArea(
child: Scaffold(
appBar: _buildAppBar(context),
body: Container(
width: double.maxFinite,
padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v),
child: Column(
children: [
SizedBox(height: 7.v),
Container(
height: 16.v,
width: 343.h,
decoration: BoxDecoration(
color: theme.colorScheme.onPrimary,
borderRadius: BorderRadius.circular(8.h),
border: Border.all(
color: theme.colorScheme.primary,
width: 1.h,
),
return Scaffold(
appBar: _buildAppBar(context),
body: Container(
width: double.maxFinite,
padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v),
child: Column(
children: [
SizedBox(height: 7.v),
Container(
height: 16.v,
width: 343.h,
decoration: BoxDecoration(
color: theme.colorScheme.onPrimary,
borderRadius: BorderRadius.circular(8.h),
border: Border.all(
color: theme.colorScheme.primary,
width: 1.h,
),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.h),
child: LinearProgressIndicator(
value: 0.55,
backgroundColor: theme.colorScheme.onPrimary,
valueColor: AlwaysStoppedAnimation<Color>(
appTheme.blueA400,
),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.h),
child: LinearProgressIndicator(
value: 0.55,
backgroundColor: theme.colorScheme.onPrimary,
valueColor: AlwaysStoppedAnimation<Color>(
appTheme.blueA400,
),
),
),
SizedBox(height: 39.v),
Column(
children: [
Text(
"height_measurements".tr,
style: theme.textTheme.headlineSmall,
),
SizedBox(height: 174.v),
Column(
children: [
Text(
"your_height".tr,
style: theme.textTheme.bodyLarge,
),
SizedBox(height: 4.v),
BlocBuilder<HeightBloc, HeightState>(
builder: (context, state) {
return CustomTextFormField(
width: 150.h,
textInputAction: TextInputAction.done,
controller: state.heightController,
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly,
],
);
})
],
),
],
),
Spacer(),
BlocBuilder<HeightBloc, HeightState>(
builder: (context, state) {
return NextStepButton(
text: "continue".tr.toUpperCase(),
margin: EdgeInsets.only(
left: 16.h,
right: 16.h,
bottom: 50.v,
),
SizedBox(height: 39.v),
Column(
children: [
Text(
"height_measurements".tr,
style: theme.textTheme.headlineSmall,
),
SizedBox(height: 174.v),
Column(
children: [
Text(
"your_height".tr,
style: theme.textTheme.bodyLarge,
),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
onPressed: onTapNext(state, context),
);
},
),
],
),
SizedBox(height: 4.v),
BlocBuilder<HeightBloc, HeightState>(builder: (context, state) {
return CustomTextFormField(
width: 150.h,
textInputAction: TextInputAction.done,
controller: state.heightController,
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly,
],
);
})
],
),
],
),
Spacer(),
BlocBuilder<HeightBloc, HeightState>(
builder: (context, state) {
return NextStepButton(
text: "continue".tr.toUpperCase(),
margin: EdgeInsets.only(
left: 16.h,
right: 16.h,
bottom: 50.v,
),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
onPressed: onTapNext(state, context),
);
},
),
],
),
),
);
@@ -113,11 +108,7 @@ class HeightScreen extends StatelessWidget {
onTapNext(HeightState state, BuildContext context) {
return () {
UserRegistrationSheet sheet = state.heightModelObj!.userRegistrationSheet;
Navigator.pushNamed(
context,
AppRoutes.bloodPressureScreen,
arguments: sheet,
);
GoRouter.of(context).goNamed('bloodPressureScreen', extra: sheet);
};
}
@@ -125,10 +116,8 @@ class HeightScreen extends StatelessWidget {
_buildAppBar(BuildContext context) {
return CustomAppBar(
leadingWidth: 29.h,
leading: AppbarLeadingImage(
imagePath: ImageConstant.imgArrowLeft,
margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v),
onTap: onTapBack(context)),
leading:
AppbarLeadingImage(imagePath: ImageConstant.imgArrowLeft, margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v), onTap: onTapBack(context)),
centerTitle: true,
title: AppbarTitleImage(imagePath: ImageConstant.imgLogoEatvio));
}
@@ -141,6 +130,6 @@ class HeightScreen extends StatelessWidget {
/// Navigates to the previous screen.
onTapArrowLeft(BuildContext context) {
NavigatorService.goBack();
GoRouter.of(context).pop();
}
}

View File

@@ -1,4 +1,5 @@
import 'package:eatvio_app/data/models/userRegistrationModel/user_registration_model.dart';
import 'package:go_router/go_router.dart';
import 'bloc/language_select_bloc.dart';
import 'models/language_select_model.dart';
@@ -9,101 +10,107 @@ import 'package:flutter/material.dart';
class LanguageSelectScreen extends StatelessWidget {
const LanguageSelectScreen({Key? key}) : super(key: key);
static Widget builder(BuildContext context) {
return BlocProvider<LanguageSelectBloc>(
create: (context) => LanguageSelectBloc(
LanguageSelectState(languageSelectModelObj: LanguageSelectModel()))
..add(LanguageSelectInitialEvent()),
child: LanguageSelectScreen());
create: (context) => LanguageSelectBloc(
LanguageSelectState(
languageSelectModelObj: LanguageSelectModel(),
),
)..add(LanguageSelectInitialEvent()),
child: LanguageSelectScreen(),
);
}
@override
Widget build(BuildContext context) {
mediaQueryData = MediaQuery.of(context);
return SafeArea(
child: Scaffold(
body: Container(
width: double.maxFinite,
padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 5.v),
child: Column(children: [
CustomImageView(
imagePath: ImageConstant.imgLogoEatvio,
height: 50.adaptSize,
width: 50.adaptSize),
SizedBox(height: 20.v),
Container(
height: 16.v,
width: 343.h,
return Scaffold(
body: Container(
width: double.maxFinite,
padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 5.v),
child: Column(
children: [
CustomImageView(imagePath: ImageConstant.imgLogoEatvio, height: 50.adaptSize, width: 50.adaptSize),
SizedBox(height: 20.v),
Container(
height: 16.v,
width: 343.h,
decoration: BoxDecoration(
color: theme.colorScheme.onPrimary,
borderRadius: BorderRadius.circular(8.h),
border: Border.all(
color: theme.colorScheme.primary,
width: 1.h,
),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.h),
child: LinearProgressIndicator(
value: 0,
backgroundColor: theme.colorScheme.onPrimary,
valueColor: AlwaysStoppedAnimation<Color>(appTheme.blueA400),
),
),
),
SizedBox(height: 20.v),
Container(
width: 262.h,
margin: EdgeInsets.only(left: 41.h, right: 39.h),
child: Text(
"preferred_language".tr,
maxLines: 2,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
style: theme.textTheme.headlineSmall,
),
),
Spacer(flex: 36),
BlocSelector<LanguageSelectBloc, LanguageSelectState, LanguageSelectModel?>(
selector: (state) => state.languageSelectModelObj,
builder: (
context,
languageSelectModelObj,
) {
return CustomDropDown(
icon: Container(
margin: EdgeInsets.fromLTRB(30.h, 11.v, 17.h, 11.v),
decoration: BoxDecoration(
color: theme.colorScheme.onPrimary,
borderRadius: BorderRadius.circular(8.h),
border: Border.all(
color: theme.colorScheme.primary, width: 1.h)),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.h),
child: LinearProgressIndicator(
value: 0,
backgroundColor: theme.colorScheme.onPrimary,
valueColor: AlwaysStoppedAnimation<Color>(
appTheme.blueA400)))),
SizedBox(height: 20.v),
Container(
width: 262.h,
margin: EdgeInsets.only(left: 41.h, right: 39.h),
child: Text("preferred_language".tr,
maxLines: 2,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
style: theme.textTheme.headlineSmall)),
Spacer(flex: 36),
BlocSelector<LanguageSelectBloc, LanguageSelectState,
LanguageSelectModel?>(
selector: (state) => state.languageSelectModelObj,
builder: (
context,
languageSelectModelObj,
) {
return CustomDropDown(
icon: Container(
margin: EdgeInsets.fromLTRB(30.h, 11.v, 17.h, 11.v),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(1.h),
),
child: CustomImageView(
imagePath: ImageConstant.imgFavorite,
height: 12.adaptSize,
width: 12.adaptSize,
),
),
hintText: "Не выбрано".tr,
items: languageSelectModelObj?.dropdownItemList ?? [],
onChanged: (value) =>
_onDropDownChanged(context, value),
);
}),
Spacer(flex: 63),
])),
bottomNavigationBar:
BlocBuilder<LanguageSelectBloc, LanguageSelectState>(
builder: (context, state) {
return NextStepButton(
text: "continue".tr.toUpperCase(),
margin:
EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
onPressed: _saveAndPush(state, context));
})));
borderRadius: BorderRadius.circular(1.h),
),
child: CustomImageView(
imagePath: ImageConstant.imgFavorite,
height: 12.adaptSize,
width: 12.adaptSize,
),
),
hintText: "Не выбрано".tr,
items: languageSelectModelObj?.dropdownItemList ?? [],
onChanged: (value) => _onDropDownChanged(context, value),
);
}),
Spacer(flex: 63),
],
),
),
bottomNavigationBar: BlocBuilder<LanguageSelectBloc, LanguageSelectState>(
builder: (context, state) {
return NextStepButton(
text: "continue".tr.toUpperCase(),
margin: EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
onPressed: _saveAndPush(state, context),
);
},
),
);
}
void Function()? _saveAndPush(
LanguageSelectState state, BuildContext context) {
void Function()? _saveAndPush(LanguageSelectState state, BuildContext context) {
return () {
UserRegistrationSheet sheet =
state.languageSelectModelObj!.userRegistrationSheet;
Navigator.pushNamed(
context,
AppRoutes.credentialsScreen,
arguments: sheet,
UserRegistrationSheet sheet = state.languageSelectModelObj!.userRegistrationSheet;
GoRouter.of(context).goNamed(
'credentialsScreen',
extra: sheet,
);
};
}

View File

@@ -1,3 +1,5 @@
import 'package:go_router/go_router.dart';
import 'bloc/medicine_intake_bloc.dart';
import 'models/medicine_intake_model.dart';
import 'package:eatvio_app/core/app_export.dart';
@@ -12,13 +14,9 @@ import 'package:flutter/material.dart';
class MedicineIntakeScreen extends StatefulWidget {
const MedicineIntakeScreen({Key? key}) : super(key: key);
static Widget builder(BuildContext context) {
final sheet =
ModalRoute.of(context)!.settings.arguments as UserRegistrationSheet;
static Widget builder(BuildContext context, sheet) {
return BlocProvider<MedicineIntakeBloc>(
create: (context) => MedicineIntakeBloc(MedicineIntakeState(
medicineIntakeModelObj:
MedicineIntakeModel(userRegistrationSheet: sheet)))
create: (context) => MedicineIntakeBloc(MedicineIntakeState(medicineIntakeModelObj: MedicineIntakeModel(userRegistrationSheet: sheet)))
..add(MedicineIntakeInitialEvent()),
child: MedicineIntakeScreen());
}
@@ -31,82 +29,67 @@ class _MedicineIntakeScreenState extends State<MedicineIntakeScreen> {
@override
Widget build(BuildContext context) {
mediaQueryData = MediaQuery.of(context);
return SafeArea(
child: Scaffold(
resizeToAvoidBottomInset: false,
appBar: _buildAppBar(context),
body: Container(
width: double.maxFinite,
padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v),
child: Column(children: [
SizedBox(height: 7.v),
Container(
height: 16.v,
width: 343.h,
decoration: BoxDecoration(
color: theme.colorScheme.onPrimary,
borderRadius: BorderRadius.circular(8.h),
border: Border.all(
color: theme.colorScheme.primary, width: 1.h)),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.h),
child: LinearProgressIndicator(
value: 0.69,
backgroundColor: theme.colorScheme.onPrimary,
valueColor: AlwaysStoppedAnimation<Color>(
appTheme.blueA400)))),
SizedBox(height: 20.v),
Container(
width: 271.h,
margin: EdgeInsets.only(left: 35.h, right: 36.h),
child: Text("do_you_take_pills".tr,
maxLines: 2,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
style: theme.textTheme.headlineSmall)),
SizedBox(height: 86.v),
_buildTextFormField(context),
Spacer(),
BlocBuilder<MedicineIntakeBloc, MedicineIntakeState>(
builder: (context, state) {
return NextStepButton(
text: "continue".tr.toUpperCase(),
margin: EdgeInsets.only(
left: 16.h, right: 16.h, bottom: 50.v),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
onPressed: onTapNext(state, context));
}),
BlocBuilder<MedicineIntakeBloc, MedicineIntakeState>(
builder: (context, state) {
return GestureDetector(
onTap: onTapSkip(state, context),
child: Text("skip".tr, style: theme.textTheme.bodyLarge),
);
}),
]))));
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: _buildAppBar(context),
body: Container(
width: double.maxFinite,
padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v),
child: Column(children: [
SizedBox(height: 7.v),
Container(
height: 16.v,
width: 343.h,
decoration: BoxDecoration(
color: theme.colorScheme.onPrimary,
borderRadius: BorderRadius.circular(8.h),
border: Border.all(color: theme.colorScheme.primary, width: 1.h)),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.h),
child: LinearProgressIndicator(
value: 0.69, backgroundColor: theme.colorScheme.onPrimary, valueColor: AlwaysStoppedAnimation<Color>(appTheme.blueA400)))),
SizedBox(height: 20.v),
Container(
width: 271.h,
margin: EdgeInsets.only(left: 35.h, right: 36.h),
child: Text("do_you_take_pills".tr,
maxLines: 2, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, style: theme.textTheme.headlineSmall)),
SizedBox(height: 86.v),
_buildTextFormField(context),
Spacer(),
BlocBuilder<MedicineIntakeBloc, MedicineIntakeState>(builder: (context, state) {
return NextStepButton(
text: "continue".tr.toUpperCase(),
margin: EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
onPressed: onTapNext(state, context));
}),
BlocBuilder<MedicineIntakeBloc, MedicineIntakeState>(builder: (context, state) {
return GestureDetector(
onTap: onTapSkip(state, context),
child: Text("skip".tr, style: theme.textTheme.bodyLarge),
);
}),
])));
}
onTapSkip(MedicineIntakeState state, BuildContext context) {
return () {
UserRegistrationSheet sheet =
state.medicineIntakeModelObj!.userRegistrationSheet;
Navigator.pushNamed(
context,
AppRoutes.bloodPressureScreen,
arguments: sheet,
UserRegistrationSheet sheet = state.medicineIntakeModelObj!.userRegistrationSheet;
GoRouter.of(context).goNamed(
'bloodPressureScreen',
extra: sheet,
);
};
}
onTapNext(MedicineIntakeState state, BuildContext context) {
return () {
UserRegistrationSheet sheet =
state.medicineIntakeModelObj!.userRegistrationSheet;
UserRegistrationSheet sheet = state.medicineIntakeModelObj!.userRegistrationSheet;
sheet.aboutTakingPils = state.enterInformationController!.text;
Navigator.pushNamed(
context,
AppRoutes.bloodPressureScreen,
arguments: sheet,
GoRouter.of(context).goNamed(
'bloodPressureScreen',
extra: sheet,
);
};
}
@@ -115,10 +98,8 @@ class _MedicineIntakeScreenState extends State<MedicineIntakeScreen> {
_buildAppBar(BuildContext context) {
return CustomAppBar(
leadingWidth: 31.h,
leading: AppbarLeadingImage(
imagePath: ImageConstant.imgArrowLeft,
margin: EdgeInsets.only(left: 18.h, top: 15.v, bottom: 15.v),
onTap: onTapBack(context)),
leading:
AppbarLeadingImage(imagePath: ImageConstant.imgArrowLeft, margin: EdgeInsets.only(left: 18.h, top: 15.v, bottom: 15.v), onTap: onTapBack(context)),
centerTitle: true,
title: AppbarTitleImage(imagePath: ImageConstant.imgLogoEatvio));
}
@@ -132,24 +113,19 @@ class _MedicineIntakeScreenState extends State<MedicineIntakeScreen> {
/// Section Widget
_buildTextFormField(BuildContext context) {
return Column(children: [
Text("fill_in_information".tr,
style: CustomTextStyles.titleMediumSegoeUI),
Text("fill_in_information".tr, style: CustomTextStyles.titleMediumSegoeUI),
SizedBox(height: 2.v),
BlocSelector<MedicineIntakeBloc, MedicineIntakeState,
TextEditingController?>(
BlocSelector<MedicineIntakeBloc, MedicineIntakeState, TextEditingController?>(
selector: (state) => state.enterInformationController,
builder: (context, enterInformationController) {
return CustomTextFormField(
controller: enterInformationController,
hintText: "enter_information".tr,
textInputAction: TextInputAction.done,
maxLines: 8);
controller: enterInformationController, hintText: "enter_information".tr, textInputAction: TextInputAction.done, maxLines: 8);
})
]);
}
/// Navigates to the previous screen.
onTapArrowLeft(BuildContext context) {
NavigatorService.goBack();
GoRouter.of(context).pop();
}
}

View File

@@ -1,3 +1,6 @@
import 'package:eatvio_app/app_router.dart';
import 'package:go_router/go_router.dart';
import 'bloc/name_bloc.dart';
import 'models/name_model.dart';
import 'package:eatvio_app/core/app_export.dart';
@@ -11,10 +14,8 @@ import 'package:flutter/material.dart';
class NameScreen extends StatelessWidget {
const NameScreen({Key? key}) : super(key: key);
static Widget builder(BuildContext context) {
final sheet =
ModalRoute.of(context)!.settings.arguments as UserRegistrationSheet;
static Widget builder(BuildContext context, sheet) {
return BlocProvider<NameBloc>(
create: (context) => NameBloc(
NameState(
@@ -27,65 +28,55 @@ class NameScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
mediaQueryData = MediaQuery.of(context);
return SafeArea(
child: Scaffold(
resizeToAvoidBottomInset: false,
appBar: _buildAppBar(context),
body: Container(
width: double.maxFinite,
padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v),
child: Column(children: [
SizedBox(height: 7.v),
Container(
height: 16.v,
width: 343.h,
decoration: BoxDecoration(
color: theme.colorScheme.onPrimary,
borderRadius: BorderRadius.circular(8.h),
border: Border.all(
color: theme.colorScheme.primary, width: 1.h)),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.h),
child: LinearProgressIndicator(
value: 0.1,
backgroundColor: theme.colorScheme.onPrimary,
valueColor: AlwaysStoppedAnimation<Color>(
appTheme.blueA400)))),
SizedBox(height: 39.v),
Text("what_is_your_name".tr,
style: theme.textTheme.headlineSmall),
SizedBox(height: 93.v),
Text("first_name".tr, style: theme.textTheme.bodyLarge),
SizedBox(height: 3.v),
BlocSelector<NameBloc, NameState, TextEditingController?>(
selector: (state) => state.firstNameController,
builder: (context, firstNameController) {
return CustomTextFormField(
controller: firstNameController,
textInputAction: TextInputAction.done);
}),
SizedBox(height: 19.v),
Text("last_name".tr, style: theme.textTheme.bodyLarge),
SizedBox(height: 5.v),
BlocSelector<NameBloc, NameState, TextEditingController?>(
selector: (state) => state.lastNameController,
builder: (context, lastNameController) {
return CustomTextFormField(
controller: lastNameController,
);
}),
Spacer(),
BlocBuilder<NameBloc, NameState>(builder: (context, state) {
return NextStepButton(
text: "continue".tr.toUpperCase(),
margin: EdgeInsets.only(
left: 16.h, right: 16.h, bottom: 50.v),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
onPressed: () => _onTapNext(
context, state), // Assign callback function here
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: _buildAppBar(context),
body: Container(
width: double.maxFinite,
padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v),
child: Column(children: [
SizedBox(height: 7.v),
Container(
height: 16.v,
width: 343.h,
decoration: BoxDecoration(
color: theme.colorScheme.onPrimary,
borderRadius: BorderRadius.circular(8.h),
border: Border.all(color: theme.colorScheme.primary, width: 1.h)),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.h),
child: LinearProgressIndicator(
value: 0.1, backgroundColor: theme.colorScheme.onPrimary, valueColor: AlwaysStoppedAnimation<Color>(appTheme.blueA400)))),
SizedBox(height: 39.v),
Text("what_is_your_name".tr, style: theme.textTheme.headlineSmall),
SizedBox(height: 93.v),
Text("first_name".tr, style: theme.textTheme.bodyLarge),
SizedBox(height: 3.v),
BlocSelector<NameBloc, NameState, TextEditingController?>(
selector: (state) => state.firstNameController,
builder: (context, firstNameController) {
return CustomTextFormField(controller: firstNameController, textInputAction: TextInputAction.done);
}),
SizedBox(height: 19.v),
Text("last_name".tr, style: theme.textTheme.bodyLarge),
SizedBox(height: 5.v),
BlocSelector<NameBloc, NameState, TextEditingController?>(
selector: (state) => state.lastNameController,
builder: (context, lastNameController) {
return CustomTextFormField(
controller: lastNameController,
);
}),
]))));
Spacer(),
BlocBuilder<NameBloc, NameState>(builder: (context, state) {
return NextStepButton(
text: "continue".tr.toUpperCase(),
margin: EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
onPressed: () => _onTapNext(context, state), // Assign callback function here
);
}),
])));
}
/// Section Widget
@@ -93,33 +84,25 @@ class NameScreen extends StatelessWidget {
return CustomAppBar(
leadingWidth: 29.h,
leading: AppbarLeadingImage(
imagePath: ImageConstant.imgArrowLeft,
margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v),
onTap: () => onTapArrowLeft(context)),
imagePath: ImageConstant.imgArrowLeft, margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v), onTap: () => onTapArrowLeft(context)),
centerTitle: true,
title: AppbarTitleImage(imagePath: ImageConstant.imgLogoEatvio));
}
/// Navigates to the previous screen.
onTapArrowLeft(BuildContext context) {
NavigatorService.goBack();
GoRouter.of(context).pop();
}
_onTapNext(BuildContext context, NameState state) {
if (state.firstNameController != null && state.lastNameController != null) {
List<String> errors = validateFields(
state.firstNameController!.text, state.lastNameController!.text);
List<String> errors = validateFields(state.firstNameController!.text, state.lastNameController!.text);
if (errors.isEmpty) {
UserRegistrationSheet sheet = state.nameModelObj!.userRegistrationSheet;
sheet.firstName = state.firstNameController!.text;
sheet.lastName = state.lastNameController!.text;
Navigator.pushNamed(
context,
AppRoutes.genderScreen,
arguments: sheet,
);
AppRouter.navigatorKey.currentContext?.goNamed('genderScreen', extra: sheet);
} else {
String errorMessage = '';

View File

@@ -1,8 +1,12 @@
import 'package:eatvio_app/app_router.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '/core/app_export.dart';
import 'package:eatvio_app/presentation/registration/splash_screen/models/splash_model.dart';
part 'splash_event.dart';
part 'splash_state.dart';
/// A bloc that manages the state of a Splash according to the event that is dispatched to it.
@@ -16,9 +20,7 @@ class SplashBloc extends Bloc<SplashEvent, SplashState> {
Emitter<SplashState> emit,
) async {
Future.delayed(const Duration(milliseconds: 3000), () {
NavigatorService.popAndPushNamed(
AppRoutes.myProfile,
);
AppRouter.navigatorKey.currentContext?.goNamed('myProfileScreen');
});
}
}

View File

@@ -1,3 +1,5 @@
import 'package:go_router/go_router.dart';
import 'bloc/weight_bloc.dart';
import 'models/weight_model.dart';
import 'package:eatvio_app/core/app_export.dart';
@@ -13,98 +15,90 @@ import 'package:flutter/material.dart';
class WeightScreen extends StatelessWidget {
const WeightScreen({Key? key}) : super(key: key);
static Widget builder(BuildContext context) {
final sheet =
ModalRoute.of(context)!.settings.arguments as UserRegistrationSheet;
static Widget builder(BuildContext context, sheet) {
return BlocProvider<WeightBloc>(
create: (context) => WeightBloc(WeightState(
weightModelObj: WeightModel(userRegistrationSheet: sheet)))
..add(WeightInitialEvent()),
create: (context) => WeightBloc(WeightState(weightModelObj: WeightModel(userRegistrationSheet: sheet)))..add(WeightInitialEvent()),
child: WeightScreen());
}
@override
Widget build(BuildContext context) {
mediaQueryData = MediaQuery.of(context);
return SafeArea(
child: Scaffold(
appBar: _buildAppBar(context),
body: Container(
width: double.maxFinite,
padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v),
child: Column(
children: [
SizedBox(height: 7.v),
Container(
height: 16.v,
width: 343.h,
decoration: BoxDecoration(
color: theme.colorScheme.onPrimary,
borderRadius: BorderRadius.circular(8.h),
border: Border.all(
color: theme.colorScheme.primary,
width: 1.h,
),
return Scaffold(
appBar: _buildAppBar(context),
body: Container(
width: double.maxFinite,
padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 9.v),
child: Column(
children: [
SizedBox(height: 7.v),
Container(
height: 16.v,
width: 343.h,
decoration: BoxDecoration(
color: theme.colorScheme.onPrimary,
borderRadius: BorderRadius.circular(8.h),
border: Border.all(
color: theme.colorScheme.primary,
width: 1.h,
),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.h),
child: LinearProgressIndicator(
value: 0.55,
backgroundColor: theme.colorScheme.onPrimary,
valueColor: AlwaysStoppedAnimation<Color>(
appTheme.blueA400,
),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.h),
child: LinearProgressIndicator(
value: 0.55,
backgroundColor: theme.colorScheme.onPrimary,
valueColor: AlwaysStoppedAnimation<Color>(
appTheme.blueA400,
),
),
),
SizedBox(height: 39.v),
Column(
children: [
Text(
"weight_measurements".tr,
style: theme.textTheme.headlineSmall,
),
SizedBox(height: 174.v),
Column(
children: [
Text(
"your_weight".tr,
style: theme.textTheme.bodyLarge,
),
SizedBox(height: 4.v),
BlocBuilder<WeightBloc, WeightState>(
builder: (context, state) {
return CustomTextFormField(
width: 150.h,
textInputAction: TextInputAction.done,
controller: state.weightController,
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly,
],
);
})
],
),
],
),
Spacer(),
BlocBuilder<WeightBloc, WeightState>(
builder: (context, state) {
return NextStepButton(
text: "continue".tr.toUpperCase(),
margin: EdgeInsets.only(
left: 16.h,
right: 16.h,
bottom: 50.v,
),
SizedBox(height: 39.v),
Column(
children: [
Text(
"weight_measurements".tr,
style: theme.textTheme.headlineSmall,
),
SizedBox(height: 174.v),
Column(
children: [
Text(
"your_weight".tr,
style: theme.textTheme.bodyLarge,
),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
onPressed: onTapNext(state, context),
);
},
),
],
),
SizedBox(height: 4.v),
BlocBuilder<WeightBloc, WeightState>(builder: (context, state) {
return CustomTextFormField(
width: 150.h,
textInputAction: TextInputAction.done,
controller: state.weightController,
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly,
],
);
})
],
),
],
),
Spacer(),
BlocBuilder<WeightBloc, WeightState>(
builder: (context, state) {
return NextStepButton(
text: "continue".tr.toUpperCase(),
margin: EdgeInsets.only(
left: 16.h,
right: 16.h,
bottom: 50.v,
),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
onPressed: onTapNext(state, context),
);
},
),
],
),
),
);
@@ -113,11 +107,7 @@ class WeightScreen extends StatelessWidget {
onTapNext(WeightState state, BuildContext context) {
return () {
UserRegistrationSheet sheet = state.weightModelObj!.userRegistrationSheet;
Navigator.pushNamed(
context,
AppRoutes.heightScreen,
arguments: sheet,
);
GoRouter.of(context).goNamed('heightScreen', extra: sheet);
};
}
@@ -125,10 +115,8 @@ class WeightScreen extends StatelessWidget {
_buildAppBar(BuildContext context) {
return CustomAppBar(
leadingWidth: 29.h,
leading: AppbarLeadingImage(
imagePath: ImageConstant.imgArrowLeft,
margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v),
onTap: onTapBack(context)),
leading:
AppbarLeadingImage(imagePath: ImageConstant.imgArrowLeft, margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v), onTap: onTapBack(context)),
centerTitle: true,
title: AppbarTitleImage(imagePath: ImageConstant.imgLogoEatvio));
}
@@ -141,6 +129,6 @@ class WeightScreen extends StatelessWidget {
/// Navigates to the previous screen.
onTapArrowLeft(BuildContext context) {
NavigatorService.goBack();
GoRouter.of(context).pop();
}
}

View File

@@ -1,235 +0,0 @@
import 'package:eatvio_app/presentation/diets/diet_select_screen/diet_select_screen.dart';
import 'package:flutter/material.dart';
import 'package:eatvio_app/presentation/registration/date_of_birth_screen/date_of_birth_screen.dart';
import 'package:eatvio_app/presentation/registration/city_of_living_screen/city_of_living_screen.dart';
import 'package:eatvio_app/presentation/registration/body_circumferences_screen/body_circumferences_screen.dart';
import 'package:eatvio_app/presentation/registration/weight_screen/weight_screen.dart';
import 'package:eatvio_app/presentation/registration/allergies_screen/allergies_screen.dart';
import 'package:eatvio_app/presentation/registration/medicine_intake_screen/medicine_intake_screen.dart';
import 'package:eatvio_app/presentation/registration/splash_screen/splash_screen.dart';
import 'package:eatvio_app/presentation/registration/gender_screen/gender_screen.dart';
import 'package:eatvio_app/presentation/registration/blood_pressure_screen/blood_pressure_screen.dart';
import 'package:eatvio_app/presentation/registration/favorite_sports_screen/favorite_sports_screen.dart';
import 'package:eatvio_app/presentation/registration/favorite_foods_screen/favorite_foods_screen.dart';
import 'package:eatvio_app/presentation/registration/additional_information_screen/additional_information_screen.dart';
import 'package:eatvio_app/presentation/registration/language_select_screen/language_select_screen.dart';
import 'package:eatvio_app/presentation/registration/height_screen/height_screen.dart';
import 'package:eatvio_app/presentation/registration/credentials_screen/credentials_screen.dart';
import 'package:eatvio_app/presentation/registration/name_screen/name_screen.dart';
import 'package:eatvio_app/presentation/registration/app_navigation_screen/app_navigation_screen.dart';
import 'package:eatvio_app/presentation/auth/auth_screen/auth_screen.dart';
import 'package:eatvio_app/presentation/profile/my_profile_screen/my_profile_screen.dart';
import 'package:eatvio_app/presentation/profile/my_profile_screen/my_settings_screen.dart';
import 'package:eatvio_app/presentation/diary/diary_edit_screen/diary_edit_screen.dart';
import 'package:eatvio_app/presentation/chat/chat_screen/chat_screen.dart';
import 'package:eatvio_app/presentation/chat/chat_select_screen/chat_select_screen.dart';
import 'package:eatvio_app/presentation/feed/feed_screen/feed_screen.dart';
import 'package:eatvio_app/presentation/recipe/recipe_select_screen/recipe_select_screen.dart';
import 'package:eatvio_app/presentation/plans/plan_select_screen/plan_select_screen.dart';
import 'package:eatvio_app/presentation/plans/plan_edit_screen/plan_edit_screen.dart';
import 'package:eatvio_app/presentation/plans/plan_create_screen/plan_create_screen.dart';
import 'package:eatvio_app/presentation/diets/diet_show_screen/diet_show_screen.dart';
import 'package:eatvio_app/presentation/diets/diet_edit_screen/diet_edit_screen.dart';
import 'package:eatvio_app/presentation/diets/diet_create_screen/diet_create_screen.dart';
import 'package:eatvio_app/presentation/plans/plan_show_screen/plan_show_screen.dart';
import 'package:eatvio_app/presentation/recipe/recipe_show_screen/recipe_show_screen.dart';
import 'package:eatvio_app/presentation/recipe/recipe_create_screen/recipe_create_screen.dart';
import 'package:eatvio_app/presentation/recipe/recipe_edit_screen/recipe_edit_screen.dart';
import 'package:eatvio_app/presentation/feed/blog_post_screen/blog_post_screen.dart';
import 'package:eatvio_app/presentation/feed/blog_post_edit_screen/blog_post_edit_screen.dart';
import 'package:eatvio_app/presentation/feed/blog_post_create_screen/blog_post_create_screen.dart';
import 'package:eatvio_app/presentation/glossary/glossary_screen/glossary_screen.dart';
class AppRoutes {
// REGISTRATION
static const String dateOfBirthScreen = '/date_of_birth_screen';
static const String cityOfLivingScreen = '/city_of_living_screen';
static const String bodyCircumferencesScreen = '/body_circumferences_screen';
static const String weightScreen = '/weight_screen';
static const String heightScreen = '/height_screen';
static const String allergiesScreen = '/allergies_screen';
static const String medicineIntakeScreen = '/medicine_intake_screen';
static const String splashScreen = '/splash_screen';
static const String genderScreen = '/gender_screen';
static const String bloodPressureScreen = '/blood_pressure_screen';
static const String favoriteSportsScreen = '/favorite_sports_screen';
static const String favoriteFoodsScreen = '/favorite_foods_screen';
static const String scheduleScreen = '/schedule_screen';
static const String additionalInformationScreen =
'/additional_information_screen';
static const String languageSelectScreen = '/language_select_screen';
static const String nameScreen = '/name_screen';
static const String credentialsScreen = '/credentials_screen';
static const String appNavigationScreen = '/app_navigation_screen';
static const String authScreen = '/auth_screen';
static const String initialRoute = '/initialRoute';
//PROFILE
static const String myProfile = '/myProfile';
static const String mySettings = '/mySettings';
static const String diaryEdit = '/diaryEdit';
static const String chatScreen = '/chat';
static const String chatSelectScreen = '/chatSelect';
static const String feedScreen = '/feed';
static const String blogPostScreen = '/blogPost';
static const String blogPostEditScreen = '/blogPostEdit';
static const String blogPostCreateScreen = '/blogPostCreate';
static const String recipeSelectScreen = '/recipeSelect';
static const String recipeScreen = '/recipe';
static const String recipeEditScreen = '/recipeEdit';
static const String recipeCreateScreen = '/recipeCreate';
static const String dietSelectScreen = '/dietSelect';
static const String dietScreen = '/diet';
static const String dietEditScreen = '/dietEdit';
static const String dietCreateScreen = '/dietCreate';
static const String planSelectScreen = '/planSelect';
static const String planScreen = '/plan';
static const String planEditScreen = '/planEdit';
static const String planCreateScreen = '/planCreate';
static const String glossaryScreen = '/glossary';
static Map<String, WidgetBuilder> get routes => {
dateOfBirthScreen: (context) =>
_buildFadeRoute(context, DateOfBirthScreen.builder),
cityOfLivingScreen: (context) =>
_buildFadeRoute(context, CityOfLivingScreen.builder),
bodyCircumferencesScreen: (context) =>
_buildFadeRoute(context, BodyCircumferencesScreen.builder),
weightScreen: (context) =>
_buildFadeRoute(context, WeightScreen.builder),
heightScreen: (context) =>
_buildFadeRoute(context, HeightScreen.builder),
nameScreen: (context) => _buildFadeRoute(context, NameScreen.builder),
credentialsScreen: (context) =>
_buildFadeRoute(context, CredentialsScreen.builder),
allergiesScreen: (context) =>
_buildFadeRoute(context, AllergiesScreen.builder),
medicineIntakeScreen: (context) =>
_buildFadeRoute(context, MedicineIntakeScreen.builder),
splashScreen: (context) =>
_buildFadeRoute(context, SplashScreen.builder),
authScreen: (context) => _buildFadeRoute(context, AuthScreen.builder),
genderScreen: (context) =>
_buildFadeRoute(context, GenderScreen.builder),
bloodPressureScreen: (context) =>
_buildFadeRoute(context, BloodPressureScreen.builder),
favoriteSportsScreen: (context) =>
_buildFadeRoute(context, FavoriteSportsScreen.builder),
favoriteFoodsScreen: (context) =>
_buildFadeRoute(context, FavoriteFoodsScreen.builder),
additionalInformationScreen: (context) =>
_buildFadeRoute(context, AdditionalInformationScreen.builder),
languageSelectScreen: (context) =>
_buildFadeRoute(context, LanguageSelectScreen.builder),
appNavigationScreen: (context) =>
_buildFadeRoute(context, AppNavigationScreen.builder),
initialRoute: (context) =>
_buildFadeRoute(context, SplashScreen.builder),
myProfile: (context) =>
_buildFadeRoute(context, MyProfileScreen.builder),
mySettings: (context) =>
_buildFadeRoute(context, MySettingsScreen.builder),
diaryEdit: (context) =>
_buildFadeRoute(context, DiaryEditScreen.builder),
chatScreen: (context) => _buildFadeRoute(context, ChatScreen.builder),
chatSelectScreen: (context) =>
_buildFadeRoute(context, ChatSelectScreen.builder),
recipeSelectScreen: (context) =>
_buildFadeRoute(context, RecipeSelectScreen.builder),
recipeScreen: (context) =>
_buildFadeRoute(context, RecipeScreen.builder),
recipeEditScreen: (context) =>
_buildFadeRoute(context, RecipeEditScreen.builder),
recipeCreateScreen: (context) =>
_buildFadeRoute(context, RecipeCreateScreen.builder),
dietSelectScreen: (context) =>
_buildFadeRoute(context, DietSelectScreen.builder),
dietScreen: (context) => _buildFadeRoute(context, DietScreen.builder),
dietEditScreen: (context) =>
_buildFadeRoute(context, DietEditScreen.builder),
dietCreateScreen: (context) =>
_buildFadeRoute(context, DietCreateScreen.builder),
planSelectScreen: (context) =>
_buildFadeRoute(context, PlanSelectScreen.builder),
planScreen: (context) => _buildFadeRoute(context, PlanScreen.builder),
planEditScreen: (context) =>
_buildFadeRoute(context, PlanEditScreen.builder),
planCreateScreen: (context) =>
_buildFadeRoute(context, PlanCreateScreen.builder),
feedScreen: (context) => _buildFadeRoute(context, FeedScreen.builder),
blogPostScreen: (context) =>
_buildFadeRoute(context, BlogPostScreen.builder),
blogPostEditScreen: (context) =>
_buildFadeRoute(context, BlogPostEditScreen.builder),
blogPostCreateScreen: (context) =>
_buildFadeRoute(context, BlogPostCreateScreen.builder),
glossaryScreen: (context) =>
_buildFadeRoute(context, GlossaryScreen.builder),
};
static Widget _buildFadeRoute(BuildContext context, WidgetBuilder builder) {
return FadeTransition(
opacity: AlwaysStoppedAnimation<double>(1.0),
child: builder(context),
);
}
}
class FadeTransitionPage extends PageRouteBuilder {
final WidgetBuilder builder;
FadeTransitionPage({required this.builder})
: super(
pageBuilder: (BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
return builder(context);
},
transitionsBuilder: (BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
return FadeTransition(opacity: animation, child: child);
},
);
}

View File

@@ -16,7 +16,7 @@ class CustomButtonStyles {
);
// text button style
static ButtonStyle get none => ButtonStyle(
backgroundColor: MaterialStateProperty.all<Color>(Colors.transparent),
elevation: MaterialStateProperty.all<double>(0),
backgroundColor: WidgetStateProperty.all<Color>(Colors.transparent),
elevation: WidgetStateProperty.all<double>(0),
);
}

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../../core/app_export.dart';
/// Helper class for managing themes and colors.
@@ -50,6 +51,13 @@ class ThemeHelper {
colorScheme: colorScheme,
textTheme: TextThemes.textTheme(colorScheme),
scaffoldBackgroundColor: colorScheme.onPrimary,
appBarTheme: AppBarTheme(
systemOverlayStyle: SystemUiOverlayStyle(
statusBarIconBrightness: Brightness.dark,
statusBarBrightness: Brightness.light,
systemNavigationBarColor: Colors.white,
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
backgroundColor: Colors.transparent,

View File

@@ -30,7 +30,6 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
@override
Widget build(BuildContext context) {
return AppBar(
elevation: 0,
toolbarHeight: height ?? 56.v,
automaticallyImplyLeading: false,
backgroundColor: Colors.transparent,

View File

@@ -0,0 +1,160 @@
import 'package:eatvio_app/core/app_export.dart';
import 'package:flutter/material.dart';
import 'package:eatvio_app/widgets/custom_navigation_menu.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:go_router/go_router.dart';
int currentBottomNavBarIndex = 0;
class BottomNavigationBarWidget extends StatefulWidget {
final Widget child;
const BottomNavigationBarWidget({
super.key,
required this.child,
});
@override
State<BottomNavigationBarWidget> createState() => _BottomNavigationBarWidgetState();
}
class _BottomNavigationBarWidgetState extends State<BottomNavigationBarWidget> {
void _onItemTapped(int index) {
if (currentBottomNavBarIndex != index) {
setState(() {
currentBottomNavBarIndex = index;
});
}
switch (currentBottomNavBarIndex) {
case 0:
GoRouter.of(context).replace('/my-profile-screen');
break;
case 1:
GoRouter.of(context).replace('/feed-screen');
break;
case 2:
GoRouter.of(context).replace('/diary-screen');
break;
case 3:
GoRouter.of(context).replace('/chat-select-screen');
break;
case 4:
break;
case 5:
final RenderBox overlay = Overlay.of(context).context.findRenderObject() as RenderBox;
final size = overlay.size;
showMenu(
context: context,
color: Colors.white,
shadowColor: Colors.white,
surfaceTintColor: Colors.white,
items: [
PopupMenuItem(
child: CustomNavigationMenu(),
)
],
position: RelativeRect.fromLTRB(size.width, size.height, 0, 0),
);
break;
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: widget.child,
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
onTap: _onItemTapped,
selectedFontSize: 12,
unselectedFontSize: 12,
selectedItemColor: appTheme.blueA400,
currentIndex: currentBottomNavBarIndex,
items: [
BottomNavigationBarItem(
label: '',
icon: SvgPicture.asset(
ImageConstant.imgLogo,
),
),
BottomNavigationBarItem(
label: 'Лента',
icon: SvgPicture.asset(
width: 24,
height: 24,
ImageConstant.imgFeed,
),
activeIcon: SvgPicture.asset(
ImageConstant.imgFeed,
width: 24,
height: 24,
colorFilter: ColorFilter.mode(
appTheme.blueA400,
BlendMode.srcIn,
),
),
),
BottomNavigationBarItem(
label: "diary".tr,
icon: SvgPicture.asset(
ImageConstant.imgDiary,
width: 24,
height: 24,
),
activeIcon: SvgPicture.asset(
ImageConstant.imgDiary,
width: 24,
height: 24,
colorFilter: ColorFilter.mode(
appTheme.blueA400,
BlendMode.srcIn,
),
),
),
BottomNavigationBarItem(
label: "chat".tr,
icon: SvgPicture.asset(
ImageConstant.imgChat,
width: 24,
height: 24,
),
activeIcon: SvgPicture.asset(
ImageConstant.imgChat,
width: 24,
height: 24,
colorFilter: ColorFilter.mode(
appTheme.blueA400,
BlendMode.srcIn,
),
),
),
BottomNavigationBarItem(
label: '',
icon: SvgPicture.asset(
ImageConstant.imgBell,
width: 24,
height: 24,
),
activeIcon: SvgPicture.asset(
ImageConstant.imgBell,
width: 24,
height: 24,
colorFilter: ColorFilter.mode(
appTheme.blueA400,
BlendMode.srcIn,
),
),
),
BottomNavigationBarItem(
label: '',
icon: Image.asset(
ImageConstant.imgEllipse1,
width: 32,
height: 32,
),
),
],
),
);
}
}

View File

@@ -1,218 +0,0 @@
import 'package:eatvio_app/core/app_export.dart';
import 'package:flutter/material.dart';
import 'package:eatvio_app/widgets/app_bar/appbar_title_circleimage.dart';
import 'package:eatvio_app/widgets/custom_navigation_menu.dart';
// ignore: must_be_immutable
class CustomBottomBar extends StatefulWidget {
CustomBottomBar({this.onChanged});
Function(BottomBarEnum)? onChanged;
@override
CustomBottomBarState createState() => CustomBottomBarState();
}
class CustomBottomBarState extends State<CustomBottomBar> {
int selectedIndex = 2;
List<BottomMenuModel> bottomMenuList = [
BottomMenuModel(
icon: ImageConstant.imgLogo,
activeIcon: ImageConstant.imgLogo,
type: BottomBarEnum.tf,
image: false),
BottomMenuModel(
icon: ImageConstant.imgFeed,
activeIcon: ImageConstant.imgFeed,
title: "Лента",
type: BottomBarEnum.tf,
image: false),
BottomMenuModel(
icon: ImageConstant.imgDiary,
activeIcon: ImageConstant.imgDiary,
title: "diary".tr,
type: BottomBarEnum.tf,
image: false),
BottomMenuModel(
icon: ImageConstant.imgChat,
activeIcon: ImageConstant.imgChat,
title: "chat".tr,
type: BottomBarEnum.tf,
image: false),
BottomMenuModel(
icon: ImageConstant.imgBell,
activeIcon: ImageConstant.imgBell,
type: BottomBarEnum.tf,
image: false),
BottomMenuModel(
icon: ImageConstant.imgEllipse1,
activeIcon: ImageConstant.imgEllipse1,
type: BottomBarEnum.tf,
image: true,
),
];
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
border: Border.all(
color: appTheme.whiteA700,
width: 1.h,
),
),
child: BottomNavigationBar(
backgroundColor: Colors.transparent,
showSelectedLabels: false,
showUnselectedLabels: false,
selectedFontSize: 0,
elevation: 0,
currentIndex: selectedIndex,
type: BottomNavigationBarType.fixed,
items: List.generate(bottomMenuList.length, bottomMenu),
onTap: changeMeny,
),
);
}
void changeMeny(index) {
setState(() {
selectedIndex = index;
});
switch (index) {
case 0:
Navigator.pushNamed(context, AppRoutes.myProfile);
break;
case 1:
Navigator.pushNamed(context, AppRoutes.feedScreen);
break;
case 2:
Navigator.pushNamed(context, AppRoutes.diaryEdit);
break;
case 3:
Navigator.pushNamed(context, AppRoutes.chatSelectScreen);
break;
}
}
BottomNavigationBarItem bottomMenu(index) {
bool isSelected = selectedIndex == index;
double iconHeight;
double iconWidth;
if (index == 0) {
iconHeight = 50.v;
iconWidth = 50.h;
} else if (index == bottomMenuList.length - 2) {
iconHeight = 20.v;
iconWidth = 20.h;
} else if (index == bottomMenuList.length - 1) {
iconHeight = 40.v;
iconWidth = 40.h;
} else {
iconHeight = 24.v;
iconWidth = 19.h;
}
return BottomNavigationBarItem(
icon: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (!bottomMenuList[index].image)
CustomImageView(
imagePath: bottomMenuList[index].icon,
height: iconHeight,
width: iconWidth,
color:
isSelected ? theme.colorScheme.primary : appTheme.blueGray400,
),
if (bottomMenuList[index].image)
GestureDetector(
onTap: () => showMenu(
context: context,
color: Colors.white,
shadowColor: Colors.white,
surfaceTintColor: Colors.white,
position: RelativeRect.fromLTRB(
MediaQuery.of(context).size.width - 50,
kToolbarHeight + 340,
0, // Не смещаем по горизонтали
0, // Не смещаем по вертикали
),
items: [
PopupMenuItem(
child:
CustomNavigationMenu(), // Используйте ваше меню здесь
)
],
),
child: AppbarTitleCircleimage(
imagePath: ImageConstant.imgEllipse1,
),
),
if (bottomMenuList[index].title !=
null) // Проверяем, существует ли title
Padding(
padding: EdgeInsets.only(top: 8.v),
child: Text(
bottomMenuList[index].title!,
style: CustomTextStyles.bodySmallMulishBluegray80012.copyWith(
color: isSelected
? theme.colorScheme.primary
: appTheme.blueGray800,
),
),
),
],
),
activeIcon: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
CustomImageView(
imagePath: bottomMenuList[index].activeIcon,
height: iconHeight, // Используем тот же размер для активной иконки
width: iconWidth, // Используем тот же размер для активной иконки
color: theme.colorScheme.primary,
),
Padding(
padding: EdgeInsets.only(top: 9.v),
child: Text(
bottomMenuList[index].title ?? "",
style: CustomTextStyles.bodySmallMulishPrimary.copyWith(
color: theme.colorScheme.primary,
),
),
),
],
),
label: '',
);
}
}
enum BottomBarEnum {
tf,
}
class BottomMenuModel {
BottomMenuModel({
required this.icon,
required this.activeIcon,
this.title,
required this.type,
required this.image,
});
String icon;
String activeIcon;
String? title;
BottomBarEnum type;
bool image;
}

View File

@@ -3,6 +3,7 @@ import 'package:eatvio_app/core/app_export.dart';
import 'package:eatvio_app/presentation/profile/my_profile_screen/bloc/my_profile_bloc.dart';
import 'package:eatvio_app/widgets/custom_hyper_switch.dart';
import 'package:eatvio_app/widgets/custom_hypo_switch.dart';
import 'package:go_router/go_router.dart';
import 'package:intl/intl.dart';
import 'package:eatvio_app/widgets/next_step_button.dart';
import 'dart:convert';
@@ -395,10 +396,7 @@ class _ExpandableListState extends State<ExpandableList> {
}
void _navigateToProfilePage() {
Navigator.pushNamed(
context,
AppRoutes.myProfile,
);
GoRouter.of(context).go('/my-profile-screen');
}
String _getErrorMessage(http.Response response) {

View File

@@ -1,6 +1,7 @@
import 'package:eatvio_app/core/app_export.dart';
import 'package:flutter/material.dart';
import 'package:eatvio_app/widgets/custom_navigation_menu_item.dart';
import 'package:go_router/go_router.dart';
import 'package:hive/hive.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:http/http.dart' as http;
@@ -16,33 +17,30 @@ class CustomNavigationMenu extends StatelessWidget {
MenuItem(
icon: ImageConstant.imgSettings,
text: 'Настройки профиля',
onTap: () => Navigator.pushNamed(context, AppRoutes.mySettings),
onTap: () => GoRouter.of(context).push('/my-settings-screen'),
),
MenuItem(
icon: ImageConstant.imgRecipes,
text: 'Мои рецепты',
onTap: () =>
Navigator.pushNamed(context, AppRoutes.recipeSelectScreen),
onTap: () => GoRouter.of(context).push('/recipe-select-screen'),
// Действие при нажатии на "Мои рецепты"
),
MenuItem(
icon: ImageConstant.imgDiets,
text: 'Мои диеты',
onTap: () =>
Navigator.pushNamed(context, AppRoutes.dietSelectScreen),
onTap: () => GoRouter.of(context).push('/diet-select-screen'),
// Действие при нажатии на "Мои рецепты"
),
MenuItem(
icon: ImageConstant.imgPlans,
text: 'Мои планы',
onTap: () =>
Navigator.pushNamed(context, AppRoutes.planSelectScreen),
onTap: () => GoRouter.of(context).push('/plan-select-screen'),
// Действие при нажатии на "Мои рецепты"
),
MenuItem(
icon: ImageConstant.imgFinance,
text: 'Глоссарий',
onTap: () => Navigator.pushNamed(context, AppRoutes.glossaryScreen),
onTap: () => GoRouter.of(context).push('/glossary-screen'),
),
MenuItem(
icon: ImageConstant.imgSupport,
@@ -77,10 +75,7 @@ _onTapLogout(BuildContext context) {
if (response.statusCode == 204) {
print(response.statusCode);
box.delete('token'); // Удаляем токен из Hive
Navigator.pushNamed(
context,
AppRoutes.authScreen,
);
GoRouter.of(context).go('/auth-screen');
}
};
}

View File

@@ -30,6 +30,8 @@ class CustomTextFormField extends StatelessWidget {
this.keyboardType,
this.onFieldSubmitted,
this.onSaved,
this.initialValue,
this.onChanged,
}) : super(
key: key,
);
@@ -57,8 +59,10 @@ class CustomTextFormField extends StatelessWidget {
final bool? filled;
final TextInputType? keyboardType;
final FormFieldValidator<String>? validator;
final String? initialValue;
final Function(String)? onFieldSubmitted;
final void Function(String?)? onSaved;
final void Function(String?)? onChanged;
@override
Widget build(BuildContext context) {
@@ -69,13 +73,14 @@ class CustomTextFormField extends StatelessWidget {
width: width ?? double.maxFinite,
child: TextFormField(
controller: controller,
focusNode: focusNode ?? FocusNode(),
focusNode: focusNode ?? null,
autofocus: autofocus!,
style: textStyle ?? theme.textTheme.titleMedium,
obscureText: obscureText!,
textInputAction: textInputAction,
maxLines: maxLines ?? 1,
onFieldSubmitted: onFieldSubmitted,
onChanged: onChanged,
decoration: InputDecoration(
hintText: hintText ?? "",
hintStyle: hintStyle ?? theme.textTheme.bodyLarge,
@@ -116,6 +121,7 @@ class CustomTextFormField extends StatelessWidget {
validator: validator,
onSaved: onSaved,
inputFormatters: inputFormatters,
initialValue: initialValue,
),
),
)
@@ -124,11 +130,12 @@ class CustomTextFormField extends StatelessWidget {
child: TextFormField(
controller: controller,
focusNode: focusNode ?? null,
autofocus: autofocus ?? false,
autofocus: autofocus!,
style: textStyle ?? theme.textTheme.titleMedium,
obscureText: obscureText!,
textInputAction: textInputAction,
maxLines: maxLines ?? 1,
initialValue: initialValue,
decoration: InputDecoration(
hintText: hintText ?? "",
hintStyle: hintStyle ?? theme.textTheme.bodyLarge,
@@ -211,4 +218,4 @@ class CustomTextFormField extends StatelessWidget {
),
);
}
}*/
}*/

View File

@@ -1,8 +1,8 @@
name: eatvio_app
description: A new Flutter project.
version: 0.0.0+1
environment:
sdk: '>=2.12.0 <3.0.0'
environment:
sdk: '>=3.3.4 <4.0.0'
dependencies:
flutter:
sdk: flutter
@@ -27,6 +27,8 @@ dependencies:
image_picker: ^1.0.7
flutter_typeahead: ^5.2.0
bloc_test: ^9.1.6
# navigation
go_router: ^14.1.4
keyboard_dismisser: ^3.0.0
dev_dependencies:

View File

@@ -16,10 +16,8 @@ void main() {
);
});*/
testWidgets('renders _buildDiaryEditScreen when in loaded state',
(WidgetTester tester) async {
final diaryEditBloc =
DiaryEditBloc(DiaryEditState(diaryEditModelObj: DiaryEditModel()));
testWidgets('renders _buildDiaryEditScreen when in loaded state', (WidgetTester tester) async {
final diaryEditBloc = DiaryEditBloc(DiaryEditState(diaryEditModelObj: DiaryEditModel()));
final loadedState = DiaryEditLoadedState(
diaryId: 1,
selectedSatiety: 4,
@@ -36,9 +34,7 @@ void main() {
MaterialApp(
home: BlocProvider<DiaryEditBloc>.value(
value: diaryEditBloc,
child: DiaryEditScreen(
onInitialize: () {},
),
child: DiaryEditScreen(),
),
),
);