forked from eatvio/eatvio_app
fix(personal-diet): open client chat after diet assignment
- navigate to the client chat after a successful attach or reattach - reset the stack so back returns to the chat list - keep the diet screen on missing chat id and cover the flow with tests
This commit is contained in:
@@ -5,6 +5,7 @@ import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/utils/localization_utils.dart';
|
||||
import 'package:eatvio_app/core/utils/toasts.dart';
|
||||
import 'package:eatvio_app/core/widgets/bottom_sheet_handler_bar.dart';
|
||||
import 'package:eatvio_app/core/widgets/bottom_sheet_with_blur.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
|
||||
@@ -181,35 +182,8 @@ class ConsultationWidget extends StatelessWidget {
|
||||
userName: consultationModel.userName,
|
||||
dietName: dietName,
|
||||
onTap: () async {
|
||||
final result = await context
|
||||
.read<PersonalDietSuggestionCubit>()
|
||||
.reattachDietToConsultation(
|
||||
consultationId: consultationModel.id,
|
||||
);
|
||||
AppRouter.navigatorKey.currentContext?.pop();
|
||||
if (result) {
|
||||
if (!context.mounted) return;
|
||||
showBottomSheetWithBlur(
|
||||
userRootNavigator: true,
|
||||
context: context,
|
||||
height: null,
|
||||
child: SuggestionSuccessBottomSheet(
|
||||
userName: consultationModel.userName,
|
||||
dietName: dietName,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
showBottomSheetWithBlur(
|
||||
userRootNavigator: true,
|
||||
context: context,
|
||||
height: null,
|
||||
child: SuggestionFailureBottomSheet(
|
||||
userName: consultationModel.userName,
|
||||
dietName: dietName,
|
||||
),
|
||||
);
|
||||
await _attachDiet(context, reattach: true);
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -241,34 +215,7 @@ class ConsultationWidget extends StatelessWidget {
|
||||
dietName: dietName,
|
||||
onTap: () async {
|
||||
AppRouter.navigatorKey.currentContext?.pop();
|
||||
final result = await context
|
||||
.read<PersonalDietSuggestionCubit>()
|
||||
.attachDietToConsultation(
|
||||
consultationId: consultationModel.id,
|
||||
);
|
||||
if (result) {
|
||||
if (!context.mounted) return;
|
||||
showBottomSheetWithBlur(
|
||||
userRootNavigator: true,
|
||||
context: context,
|
||||
height: null,
|
||||
child: SuggestionSuccessBottomSheet(
|
||||
userName: consultationModel.userName,
|
||||
dietName: dietName,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
showBottomSheetWithBlur(
|
||||
userRootNavigator: true,
|
||||
context: context,
|
||||
height: null,
|
||||
child: SuggestionFailureBottomSheet(
|
||||
userName: consultationModel.userName,
|
||||
dietName: dietName,
|
||||
),
|
||||
);
|
||||
await _attachDiet(context, reattach: false);
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -278,6 +225,41 @@ class ConsultationWidget extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _attachDiet(
|
||||
BuildContext context, {
|
||||
required bool reattach,
|
||||
}) async {
|
||||
final router = GoRouter.of(context);
|
||||
final chatId = consultationModel.chatId;
|
||||
final noChatMessage = S.of(context).msg_something_went_wrong;
|
||||
final cubit = context.read<PersonalDietSuggestionCubit>();
|
||||
final result = reattach
|
||||
? await cubit.reattachDietToConsultation(
|
||||
consultationId: consultationModel.id,
|
||||
)
|
||||
: await cubit.attachDietToConsultation(
|
||||
consultationId: consultationModel.id,
|
||||
);
|
||||
if (!result) {
|
||||
if (!context.mounted) return;
|
||||
showBottomSheetWithBlur(
|
||||
userRootNavigator: true,
|
||||
context: context,
|
||||
height: null,
|
||||
child: SuggestionFailureBottomSheet(
|
||||
userName: consultationModel.userName,
|
||||
dietName: dietName,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (chatId == null || chatId.isEmpty) {
|
||||
showErrorToast(noChatMessage);
|
||||
return;
|
||||
}
|
||||
router.goNamed('chatScreen', queryParameters: {'uuid': chatId});
|
||||
}
|
||||
}
|
||||
|
||||
class SuggestDietBottomSheet extends StatefulWidget {
|
||||
@@ -336,65 +318,6 @@ class _SuggestDietBottomSheet extends State<SuggestDietBottomSheet> {
|
||||
}
|
||||
}
|
||||
|
||||
class SuggestionSuccessBottomSheet extends StatefulWidget {
|
||||
const SuggestionSuccessBottomSheet({
|
||||
super.key,
|
||||
required this.userName,
|
||||
required this.dietName,
|
||||
});
|
||||
final String userName;
|
||||
final String dietName;
|
||||
|
||||
@override
|
||||
State<SuggestionSuccessBottomSheet> createState() =>
|
||||
_SuggestionSuccessBottomSheet();
|
||||
}
|
||||
|
||||
class _SuggestionSuccessBottomSheet
|
||||
extends State<SuggestionSuccessBottomSheet> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
const BottomSheetHandlerBar(),
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
'Предложение диеты',
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'«${widget.dietName}» предложена пользователю ${widget.userName}',
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
CustomOutlinedButton(
|
||||
text: s.ok,
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(
|
||||
color: appTheme.whiteA700,
|
||||
),
|
||||
buttonStyle: ButtonStyle(
|
||||
side: const WidgetStatePropertyAll(BorderSide.none),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
||||
),
|
||||
onPressed: () async {
|
||||
AppRouter.navigatorKey.currentContext?.pop();
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SuggestionFailureBottomSheet extends StatefulWidget {
|
||||
const SuggestionFailureBottomSheet({
|
||||
super.key,
|
||||
|
||||
@@ -35,6 +35,7 @@ void main() {
|
||||
|
||||
final mockConsultation = ConsultationModel(
|
||||
id: 1,
|
||||
chatId: 'chat-1',
|
||||
chosenPlanState: ChosenPlanStateEnum.stateInProgress,
|
||||
planTitle: 'Test Plan',
|
||||
userName: 'Test User',
|
||||
@@ -69,6 +70,32 @@ void main() {
|
||||
path: '/personal-diet-suggestion',
|
||||
builder: (context, state) => const Scaffold(body: Text('Personal Diet')),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/chat-select-screen',
|
||||
name: 'chatSelectScreen',
|
||||
builder: (context, state) => const Scaffold(body: Text('Chats')),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/chat-screen',
|
||||
name: 'chatScreen',
|
||||
builder: (context, state) {
|
||||
final uuid = state.uri.queryParameters['uuid'];
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: BackButton(
|
||||
onPressed: () {
|
||||
if (context.canPop()) {
|
||||
GoRouter.of(context).pop();
|
||||
return;
|
||||
}
|
||||
GoRouter.of(context).go('/chat-select-screen');
|
||||
},
|
||||
),
|
||||
),
|
||||
body: Text('Chat $uuid'),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -202,7 +229,7 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('shows success bottom sheet after successful diet attachment', (tester) async {
|
||||
testWidgets('opens chat after successful diet attachment', (tester) async {
|
||||
await mockNetworkImagesFor(() async {
|
||||
final cubit = MockPersonalDietSuggestionCubit();
|
||||
|
||||
@@ -227,16 +254,22 @@ void main() {
|
||||
await tester.tap(offerButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('«Test Diet» предложена пользователю Test User'), findsOneWidget);
|
||||
expect(find.text('Chat chat-1'), findsOneWidget);
|
||||
verify(cubit.attachDietToConsultation(consultationId: 1)).called(1);
|
||||
|
||||
await tester.tap(find.byType(BackButton));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Chats'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('shows success bottom sheet after successful diet reattachment', (tester) async {
|
||||
testWidgets('opens chat after successful diet reattachment', (tester) async {
|
||||
await mockNetworkImagesFor(() async {
|
||||
final cubit = MockPersonalDietSuggestionCubit();
|
||||
final consultationWithDiet = ConsultationModel(
|
||||
id: 1,
|
||||
chatId: 'chat-2',
|
||||
chosenPlanState: ChosenPlanStateEnum.stateInProgress,
|
||||
planTitle: 'Test Plan',
|
||||
userName: 'Test User',
|
||||
@@ -274,9 +307,47 @@ void main() {
|
||||
await tester.tap(offerButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('«Test Diet» предложена пользователю Test User'), findsOneWidget);
|
||||
expect(find.text('Chat chat-2'), findsOneWidget);
|
||||
verify(cubit.reattachDietToConsultation(consultationId: 1)).called(1);
|
||||
verifyNever(cubit.attachDietToConsultation(consultationId: 1));
|
||||
|
||||
await tester.tap(find.byType(BackButton));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Chats'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('stays on screen when chat id is missing after success', (tester) async {
|
||||
await mockNetworkImagesFor(() async {
|
||||
final cubit = MockPersonalDietSuggestionCubit();
|
||||
|
||||
when(cubit.state).thenReturn(PersonalDietSuggestionState());
|
||||
when(cubit.stream).thenAnswer((_) => const Stream.empty());
|
||||
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(cubit.attachDietToConsultation(consultationId: 1)).thenAnswer((_) async => true);
|
||||
|
||||
await tester.pumpWidget(
|
||||
BlocProvider<PersonalDietSuggestionCubit>.value(
|
||||
value: cubit,
|
||||
child: wrap(
|
||||
ConsultationWidget(
|
||||
consultationModel: mockConsultation.copyWith(chatId: null),
|
||||
dietName: 'Test Diet',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('Предложить диету'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text(S.of(tester.element(find.byType(ConsultationWidget))).toOffer));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.textContaining('Chat '), findsNothing);
|
||||
expect(find.text('Test User'), findsOneWidget);
|
||||
verify(cubit.attachDietToConsultation(consultationId: 1)).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user