Merge pull request 'improvements: added loaders' (#766) from misha/eatvio_app:issue-580 into main

Reviewed-on: eatvio/eatvio_app#766
This commit is contained in:
2025-11-13 07:13:00 +00:00
6 changed files with 97 additions and 70 deletions

View File

@@ -411,7 +411,7 @@ class _AuthScreenState extends State<AuthScreen> {
_isLoading = false; _isLoading = false;
}); });
}, },
label: state is AuthStateLoading label: state is AuthStateLoading || _isLoading
? const CupertinoActivityIndicator() ? const CupertinoActivityIndicator()
: Text('Войти', style: theme.textTheme.titleSmall!.copyWith(color: Colors.white)), : Text('Войти', style: theme.textTheme.titleSmall!.copyWith(color: Colors.white)),
), ),
@@ -426,8 +426,8 @@ class _AuthScreenState extends State<AuthScreen> {
elevation: 0, elevation: 0,
focusElevation: 0, focusElevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
backgroundColor: state is AuthStateLoading ? Colors.black.withAlpha(50) : appTheme.blueA400, backgroundColor: state is AuthStateLoading || _isLoading ? Colors.black.withAlpha(50) : appTheme.blueA400,
onPressed: state is AuthStateLoading onPressed: state is AuthStateLoading || _isLoading
? null ? null
: () async { : () async {
await showDialog( await showDialog(
@@ -487,7 +487,7 @@ class _AuthScreenState extends State<AuthScreen> {
}, },
); );
}, },
label: state is AuthStateLoading label: state is AuthStateLoading || _isLoading
? const CupertinoActivityIndicator() ? const CupertinoActivityIndicator()
: const Icon(Icons.qr_code_scanner_rounded, color: Colors.white), : const Icon(Icons.qr_code_scanner_rounded, color: Colors.white),
), ),

View File

@@ -5,6 +5,7 @@ import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/app_router.dart'; import 'package:eatvio_app/core/app_router.dart';
import 'package:eatvio_app/core/permission_handler/permission_handler.dart'; import 'package:eatvio_app/core/permission_handler/permission_handler.dart';
import 'package:eatvio_app/core/theme/app_decoration.dart'; import 'package:eatvio_app/core/theme/app_decoration.dart';
import 'package:eatvio_app/core/widgets/loader.dart';
import 'package:eatvio_app/core/widgets/show_error_dialog.dart'; import 'package:eatvio_app/core/widgets/show_error_dialog.dart';
import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_cubit.dart'; import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_cubit.dart';
import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_state.dart'; import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_state.dart';
@@ -620,16 +621,16 @@ class _BlogPostCreateScreenState extends State<BlogPostCreateScreen> {
} }
}, },
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
child: Ink( child: isLoading
width: MediaQuery.of(context).size.width - 32, ? const Center(child: Loader())
height: 52, : Ink(
decoration: BoxDecoration(borderRadius: BorderRadius.circular(16), color: appTheme.blueA400), width: MediaQuery.of(context).size.width - 32,
child: isLoading height: 52,
? const Center(child: CircularProgressIndicator()) decoration: BoxDecoration(borderRadius: BorderRadius.circular(16), color: appTheme.blueA400),
: const Center( child: const Center(
child: Text('Создать', style: TextStyle(color: Colors.white)), child: Text('Создать', style: TextStyle(color: Colors.white)),
), ),
), ),
); );
} }

View File

@@ -33,7 +33,7 @@ class _RecipeCreateScreenState extends State<RecipeCreateScreen> {
late final CreateRecipeCubit createRecipeCubit; late final CreateRecipeCubit createRecipeCubit;
final GlobalKey<MarkdownEditorState> _mdKey = GlobalKey<MarkdownEditorState>(); final GlobalKey<MarkdownEditorState> _mdKey = GlobalKey<MarkdownEditorState>();
bool _mdToolbarVisible = false; bool _mdToolbarVisible = false;
bool _isRecipeBeingPublished = false;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@@ -58,9 +58,7 @@ class _RecipeCreateScreenState extends State<RecipeCreateScreen> {
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFF0F2F5), backgroundColor: const Color(0xFFF0F2F5),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: _mdToolbarVisible && _mdKey.currentState != null floatingActionButton: _mdToolbarVisible && _mdKey.currentState != null ? _mdKey.currentState!.buildToolbar(context) : null,
? _mdKey.currentState!.buildToolbar(context)
: null,
appBar: AppBar( appBar: AppBar(
title: widget.recipe != null ? const Text('Редактирование рецепта') : const Text('Добавление рецепта'), title: widget.recipe != null ? const Text('Редактирование рецепта') : const Text('Добавление рецепта'),
centerTitle: true, centerTitle: true,
@@ -83,44 +81,55 @@ class _RecipeCreateScreenState extends State<RecipeCreateScreen> {
builder: (context, state) { builder: (context, state) {
return Builder( return Builder(
builder: (context) { builder: (context) {
return InkWell( return _isRecipeBeingPublished
key: AppKeys.recipeCreateScreenPublishButton, ? const Center(child: Loader())
onTap: () async { : InkWell(
if (context.read<CreateRecipeCubit>().validateAll()) { key: AppKeys.recipeCreateScreenPublishButton,
final isSuccess = await context.read<CreateRecipeCubit>().publishRecipe( onTap: () async {
widget.recipe == null ? false : true, setState(() {
widget.recipe?.slug, _isRecipeBeingPublished = true;
});
if (context.read<CreateRecipeCubit>().validateAll()) {
final isSuccess = await context.read<CreateRecipeCubit>().publishRecipe(
widget.recipe == null ? false : true,
widget.recipe?.slug,
);
setState(() {
_isRecipeBeingPublished = false;
});
if (isSuccess) {
Future.delayed(const Duration(milliseconds: 500), () {
if (!context.mounted) return;
context.pop();
showBottomSheetWithBlur(
userRootNavigator: true,
context: context,
child: SuccessPublishedRecipeBottomSheet(title: state.entity.title ?? 'Рецепт'),
);
});
} else {
if (!context.mounted) return;
showErrorDialog(context, title: '${s.could_not_publish_recipe} ${state.entity.title}');
}
}
setState(() {
_isRecipeBeingPublished = false;
});
},
borderRadius: BorderRadius.circular(20),
child: Ink(
width: MediaQuery.of(context).size.width - 32,
decoration: BoxDecoration(color: appTheme.blueA400, borderRadius: BorderRadius.circular(20)),
child: Container(
padding: const EdgeInsets.all(16),
alignment: Alignment.center,
child: Text(
widget.recipe != null ? 'Обновить' : 'Опубликовать',
style: const TextStyle(fontSize: 16, color: Colors.white, fontWeight: FontWeight.w600),
),
),
),
); );
if (isSuccess) {
Future.delayed(const Duration(milliseconds: 500), () {
if (!context.mounted) return;
context.pop();
showBottomSheetWithBlur(
userRootNavigator: true,
context: context,
child: SuccessPublishedRecipeBottomSheet(title: state.entity.title ?? 'Рецепт'),
);
});
} else {
if (!context.mounted) return;
showErrorDialog(context, title: '${s.could_not_publish_recipe} ${state.entity.title}');
}
}
},
borderRadius: BorderRadius.circular(20),
child: Ink(
width: MediaQuery.of(context).size.width - 32,
decoration: BoxDecoration(color: appTheme.blueA400, borderRadius: BorderRadius.circular(20)),
child: Container(
padding: const EdgeInsets.all(16),
alignment: Alignment.center,
child: Text(
widget.recipe != null ? 'Обновить' : 'Опубликовать',
style: const TextStyle(fontSize: 16, color: Colors.white, fontWeight: FontWeight.w600),
),
),
),
);
}, },
); );
}, },

View File

@@ -44,16 +44,24 @@ class UserDiaryScreen extends StatelessWidget {
} }
} }
class DiaryScreenContent extends StatelessWidget { class DiaryScreenContent extends StatefulWidget {
const DiaryScreenContent({super.key, this.diaryId, this.currentPlanDay}); const DiaryScreenContent({super.key, this.diaryId, this.currentPlanDay});
final int? diaryId; final int? diaryId;
final int? currentPlanDay; final int? currentPlanDay;
@override
State<DiaryScreenContent> createState() => _DiaryScreenContentState();
}
class _DiaryScreenContentState extends State<DiaryScreenContent> {
bool _isDiaryFinishing = false;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final s = S.of(context); final s = S.of(context);
return MultiBlocProvider( return MultiBlocProvider(
providers: [ providers: [
BlocProvider<DiaryCubit>(create: (BuildContext context) => sl<DiaryCubit>()..fetchDiaryV2(needLoading: true, diaryId: diaryId)), BlocProvider<DiaryCubit>(create: (BuildContext context) => sl<DiaryCubit>()..fetchDiaryV2(needLoading: true, diaryId: widget.diaryId)),
BlocProvider(create: (context) => sl<ConsultantCommentsCubit>()), BlocProvider(create: (context) => sl<ConsultantCommentsCubit>()),
], ],
child: BlocBuilder<DiaryCubit, DiaryState>( child: BlocBuilder<DiaryCubit, DiaryState>(
@@ -63,13 +71,13 @@ class DiaryScreenContent extends StatelessWidget {
} }
if (state is DiaryStateError) { if (state is DiaryStateError) {
return Scaffold( return Scaffold(
body: DataErrorWidget(onReload: () => context.read<DiaryCubit>().fetchDiaryV2(needLoading: false, diaryId: diaryId)), body: DataErrorWidget(onReload: () => context.read<DiaryCubit>().fetchDiaryV2(needLoading: false, diaryId: widget.diaryId)),
); );
} }
return Scaffold( return Scaffold(
backgroundColor: Colors.grey.shade200, backgroundColor: Colors.grey.shade200,
appBar: diaryId == null appBar: widget.diaryId == null
? AppBar(toolbarHeight: 0) ? AppBar(toolbarHeight: 0)
: AppBar( : AppBar(
title: Text( title: Text(
@@ -83,7 +91,7 @@ class DiaryScreenContent extends StatelessWidget {
physics: const BouncingScrollPhysics(), physics: const BouncingScrollPhysics(),
child: Column( child: Column(
children: [ children: [
if (diaryId == null) const CalendarWidget(), if (widget.diaryId == null) const CalendarWidget(),
if (state is DiaryStateLoading) ...[const SizedBox(height: 60), const Loader()], if (state is DiaryStateLoading) ...[const SizedBox(height: 60), const Loader()],
if (state is DiaryStateLoaded) if (state is DiaryStateLoaded)
WakeupAndRetireWidget( WakeupAndRetireWidget(
@@ -116,7 +124,23 @@ class DiaryScreenContent extends StatelessWidget {
if (state is DiaryStateLoaded && !state.isConsultantView && !state.isDiaryFinished && !state.isDiaryCommented) if (state is DiaryStateLoaded && !state.isConsultantView && !state.isDiaryFinished && !state.isDiaryCommented)
Padding( Padding(
padding: const EdgeInsets.only(top: 12), padding: const EdgeInsets.only(top: 12),
child: FinishDayButtonWidget(diaryDay: state.diary), child: _isDiaryFinishing
? const Center(child: Loader())
: FinishDayButtonWidget(
onPressed: () async {
setState(() {
_isDiaryFinishing = true;
});
await context.read<DiaryCubit>().finishDiaryDay(
diaryId: state.diary.id,
wakeUp: state.diary.wakeup.toString(),
retire: state.diary.retire.toString(),
);
setState(() {
_isDiaryFinishing = false;
});
},
),
), ),
if (state is DiaryStateLoaded && state.isConsultantView && !state.isDiaryCommented) ...[ if (state is DiaryStateLoaded && state.isConsultantView && !state.isDiaryCommented) ...[
const SizedBox(height: 16), const SizedBox(height: 16),

View File

@@ -1,13 +1,10 @@
import 'package:eatvio_app/features/diary/entities/diary_day_entity.dart';
import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class FinishDayButtonWidget extends StatefulWidget { class FinishDayButtonWidget extends StatefulWidget {
final DiaryDayEntity diaryDay; final Future<void> Function() onPressed;
const FinishDayButtonWidget({super.key, required this.diaryDay}); const FinishDayButtonWidget({super.key, required this.onPressed});
@override @override
State<FinishDayButtonWidget> createState() => _FinishDayButtonWidgetState(); State<FinishDayButtonWidget> createState() => _FinishDayButtonWidgetState();
@@ -17,11 +14,7 @@ class _FinishDayButtonWidgetState extends State<FinishDayButtonWidget> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return InkWell( return InkWell(
onTap: () => context.read<DiaryCubit>().finishDiaryDay( onTap: widget.onPressed,
diaryId: widget.diaryDay.id,
wakeUp: widget.diaryDay.wakeup.toString(),
retire: widget.diaryDay.retire.toString(),
),
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
child: Ink( child: Ink(
padding: const EdgeInsets.symmetric(horizontal: 8), padding: const EdgeInsets.symmetric(horizontal: 8),

View File

@@ -9,7 +9,7 @@ import 'package:flutter_portal/flutter_portal.dart';
import 'package:keyboard_dismisser/keyboard_dismisser.dart'; import 'package:keyboard_dismisser/keyboard_dismisser.dart';
Future<void> main() async { Future<void> main() async {
await AppInitializer.initialize(buildType: AppBuildType.prod); await AppInitializer.initialize(buildType: AppBuildType.dev);
await AppInitializer.runAppWithSentry(() => runApp(const MyApp())); await AppInitializer.runAppWithSentry(() => runApp(const MyApp()));
} }