forked from eatvio/eatvio_app
fix(consultations): open chat from consultation card
The chat icon on the consultant consultation card had an empty onTap handler. Resolve the conversation with the client via the chat API and navigate to it. related to #1106
This commit is contained in:
@@ -263,7 +263,7 @@ class CubitRegistrations {
|
||||
),
|
||||
);
|
||||
sl.registerFactory<ConsultationsCubit>(
|
||||
() => ConsultationsCubit(consultationsRepository: sl()),
|
||||
() => ConsultationsCubit(consultationsRepository: sl(), chatRepository: sl()),
|
||||
);
|
||||
sl.registerFactory<ConsultantCommentsCubit>(
|
||||
() => ConsultantCommentsCubit(diaryRepository: sl()),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:eatvio_app/core/enums/base_state.dart';
|
||||
import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
|
||||
import 'package:eatvio_app/features/consultations/presentation/cubit/consultations_state.dart';
|
||||
import 'package:eatvio_app/features/consultations/repositories/consultations_repository.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -8,7 +9,8 @@ import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||
|
||||
class ConsultationsCubit extends Cubit<ConsultationsState> {
|
||||
final ConsultationsRepository consultationsRepository;
|
||||
ConsultationsCubit({required this.consultationsRepository}) : super(ConsultationsState());
|
||||
final ChatRepository chatRepository;
|
||||
ConsultationsCubit({required this.consultationsRepository, required this.chatRepository}) : super(ConsultationsState());
|
||||
|
||||
Future<void> loadConsultations() async {
|
||||
try {
|
||||
@@ -20,4 +22,15 @@ class ConsultationsCubit extends Cubit<ConsultationsState> {
|
||||
emit(state.copyWith(state: BaseState.error));
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> getChatIdByUserSlug(String slug) async {
|
||||
try {
|
||||
final chatIdOrError = await chatRepository.getChatByUserSlug(slug);
|
||||
return chatIdOrError == '404' ? null : chatIdOrError;
|
||||
} catch (e, st) {
|
||||
await reportFrom(e, st, this);
|
||||
log(e.toString(), stackTrace: st);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,17 @@ import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_divider.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
|
||||
import 'package:eatvio_app/core/utils/toasts.dart';
|
||||
import 'package:eatvio_app/core/widgets/show_error_dialog.dart';
|
||||
import 'package:eatvio_app/features/consultations/models/consultation_model.dart';
|
||||
import 'package:eatvio_app/features/consultations/presentation/cubit/consultations_cubit.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/pages/diary_screen.dart';
|
||||
import 'package:eatvio_app/features/my_finance/presentation/pages/card_settings.dart';
|
||||
import 'package:eatvio_app/features/plans/enums/chosen_plan_state_enum.dart';
|
||||
import 'package:eatvio_app/features/user_details/presentation/user_details_screen.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class ConsultationCardWidget extends StatefulWidget {
|
||||
@@ -34,6 +37,7 @@ class _ConsultationCardWidgetState extends State<ConsultationCardWidget> {
|
||||
final List<Color> legendColors = const [Colors.white, Color(0xFFF0C497), Color(0xFFFF9500), Color(0xFF2F80ED), Color(0xFF4CD964)];
|
||||
|
||||
late final List<(int, DiaryModel)> givenDaysWithDayNumber;
|
||||
bool _isOpeningChat = false;
|
||||
final List<List<DiaryStateEnum>> legendStates = [
|
||||
[DiaryStateEnum.init],
|
||||
[DiaryStateEnum.started, DiaryStateEnum.lastDayStarted],
|
||||
@@ -279,7 +283,7 @@ class _ConsultationCardWidgetState extends State<ConsultationCardWidget> {
|
||||
const SizedBox(width: 8),
|
||||
InkWell(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
onTap: () {},
|
||||
onTap: _isOpeningChat ? null : _openChat,
|
||||
child: Ink(
|
||||
height: 52,
|
||||
width: 52,
|
||||
@@ -308,6 +312,18 @@ class _ConsultationCardWidgetState extends State<ConsultationCardWidget> {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _openChat() async {
|
||||
setState(() => _isOpeningChat = true);
|
||||
final chatId = await context.read<ConsultationsCubit>().getChatIdByUserSlug(widget.consultationsModel.userSlug);
|
||||
if (!mounted) return;
|
||||
setState(() => _isOpeningChat = false);
|
||||
if (chatId == null) {
|
||||
showErrorToast(S.of(context).msg_something_went_wrong);
|
||||
return;
|
||||
}
|
||||
GoRouter.of(context).pushNamed('chatScreen', extra: {'uuid': chatId});
|
||||
}
|
||||
|
||||
Row _legendTitle(String text, Color color) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:eatvio_app/core/enums/base_state.dart';
|
||||
import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
|
||||
import 'package:eatvio_app/features/consultations/models/consultation_model.dart';
|
||||
import 'package:eatvio_app/features/consultations/presentation/cubit/consultations_cubit.dart';
|
||||
import 'package:eatvio_app/features/consultations/presentation/cubit/consultations_state.dart';
|
||||
@@ -8,14 +9,21 @@ import 'package:mocktail/mocktail.dart';
|
||||
|
||||
class MockConsultationsRepository extends Mock implements ConsultationsRepository {}
|
||||
|
||||
class MockChatRepository extends Mock implements ChatRepository {}
|
||||
|
||||
void main() {
|
||||
group('ConsultationsCubit', () {
|
||||
late ConsultationsCubit cubit;
|
||||
late MockConsultationsRepository mockRepository;
|
||||
late MockChatRepository mockChatRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockConsultationsRepository();
|
||||
cubit = ConsultationsCubit(consultationsRepository: mockRepository);
|
||||
mockChatRepository = MockChatRepository();
|
||||
cubit = ConsultationsCubit(
|
||||
consultationsRepository: mockRepository,
|
||||
chatRepository: mockChatRepository,
|
||||
);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
@@ -47,5 +55,33 @@ void main() {
|
||||
|
||||
expect(cubit.state.state, BaseState.error);
|
||||
});
|
||||
|
||||
test('getChatIdByUserSlug - возвращает id чата', () async {
|
||||
when(() => mockChatRepository.getChatByUserSlug('john-doe'))
|
||||
.thenAnswer((_) async => 'chat-uuid');
|
||||
|
||||
final chatId = await cubit.getChatIdByUserSlug('john-doe');
|
||||
|
||||
expect(chatId, 'chat-uuid');
|
||||
verify(() => mockChatRepository.getChatByUserSlug('john-doe')).called(1);
|
||||
});
|
||||
|
||||
test('getChatIdByUserSlug - возвращает null при ошибке', () async {
|
||||
when(() => mockChatRepository.getChatByUserSlug(any()))
|
||||
.thenThrow(Exception('Error'));
|
||||
|
||||
final chatId = await cubit.getChatIdByUserSlug('john-doe');
|
||||
|
||||
expect(chatId, isNull);
|
||||
});
|
||||
|
||||
test('getChatIdByUserSlug - возвращает null когда чат не найден', () async {
|
||||
when(() => mockChatRepository.getChatByUserSlug(any()))
|
||||
.thenAnswer((_) async => '404');
|
||||
|
||||
final chatId = await cubit.getChatIdByUserSlug('john-doe');
|
||||
|
||||
expect(chatId, isNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,16 +3,21 @@ import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/consultations/models/consultation_model.dart';
|
||||
import 'package:eatvio_app/features/consultations/presentation/cubit/consultations_cubit.dart';
|
||||
import 'package:eatvio_app/features/consultations/presentation/widgets/consultation_card_widget.dart';
|
||||
import 'package:eatvio_app/features/plans/enums/chosen_plan_state_enum.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:network_image_mock/network_image_mock.dart';
|
||||
|
||||
class MockConsultationsCubit extends Mock implements ConsultationsCubit {}
|
||||
|
||||
void main() {
|
||||
group('ConsultationCardWidget smoke', () {
|
||||
setUpAll(() async {
|
||||
@@ -64,7 +69,8 @@ void main() {
|
||||
personalDietId: null,
|
||||
);
|
||||
|
||||
Widget wrap(Widget child) {
|
||||
Widget wrap(Widget child, {ConsultationsCubit? cubit}) {
|
||||
final consultationsCubit = cubit ?? MockConsultationsCubit();
|
||||
return MaterialApp.router(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
@@ -77,7 +83,20 @@ void main() {
|
||||
routerConfig: GoRouter(
|
||||
initialLocation: '/',
|
||||
routes: [
|
||||
GoRoute(path: '/', builder: (context, state) => child),
|
||||
GoRoute(
|
||||
path: '/',
|
||||
builder: (context, state) => BlocProvider<ConsultationsCubit>.value(
|
||||
value: consultationsCubit,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/chat-screen',
|
||||
name: 'chatScreen',
|
||||
builder: (context, state) => Scaffold(
|
||||
body: Text('chat:${(state.extra as Map)['uuid']}'),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/profile-screen',
|
||||
name: 'profileScreen',
|
||||
@@ -163,5 +182,30 @@ void main() {
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('opens chat with consultation user when chat icon tapped', (
|
||||
tester,
|
||||
) async {
|
||||
final cubit = MockConsultationsCubit();
|
||||
when(() => cubit.stream).thenAnswer((_) => const Stream.empty());
|
||||
when(() => cubit.getChatIdByUserSlug('john-doe'))
|
||||
.thenAnswer((_) async => 'chat-uuid');
|
||||
|
||||
await mockNetworkImagesFor(() async {
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
ConsultationCardWidget(consultationsModel: mockConsultation),
|
||||
cubit: cubit,
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.byIcon(Icons.chat_outlined));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('chat:chat-uuid'), findsOneWidget);
|
||||
verify(() => cubit.getChatIdByUserSlug('john-doe')).called(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user