From 75fa9ebc5c09a40e397f02b77780a7ca2dffb706 Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Mon, 23 Jun 2025 19:46:23 +0300 Subject: [PATCH 1/2] feat(chat): fix chat first message, add pick images from gallery --- lib/core/app_router.dart | 7 +- lib/core/websockets/ws_manager.dart | 3 +- .../bloc/chat_room_cubit/chat_room_cubit.dart | 2 - .../chat/presentation/pages/chat_screen.dart | 40 ++- .../presentation/pages/empty_chat_screen.dart | 289 ++++++++++++++++++ .../chat/repositories/chat_repository.dart | 9 - .../bloc/profile_cubit/profile_cubit.dart | 13 +- .../presentation/pages/profile_screen.dart | 2 +- .../widgets/sub_action_button_widget.dart | 14 +- .../presentation/widgets/sub_widget.dart | 2 + 10 files changed, 347 insertions(+), 34 deletions(-) create mode 100644 lib/features/chat/presentation/pages/empty_chat_screen.dart diff --git a/lib/core/app_router.dart b/lib/core/app_router.dart index c29754ab..2affcfd1 100644 --- a/lib/core/app_router.dart +++ b/lib/core/app_router.dart @@ -17,6 +17,7 @@ import 'package:eatvio_app/features/bottom_navigation_bar/presentation/widgets/b import 'package:eatvio_app/features/calendar/presentation/pages/calendar_screen.dart'; import 'package:eatvio_app/features/chat/presentation/pages/chat_screen.dart'; import 'package:eatvio_app/features/chat/presentation/pages/chats_screen.dart'; +import 'package:eatvio_app/features/chat/presentation/pages/empty_chat_screen.dart'; import 'package:eatvio_app/features/chat/presentation/pages/payment_screen.dart'; import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart'; import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/become_consultant_screen.dart'; @@ -173,7 +174,6 @@ class AppRouter { name: 'welcomeScreen', builder: (context, state) => const WelcomeScreen(), redirect: (context, state) async { - try { if (await TokenStorage.haveTokenInStorage()) { final firebaseApi = sl(); @@ -312,6 +312,11 @@ class AppRouter { ); }, ), + GoRoute( + path: '/empty-chat-screen', + name: 'emptyChatScreen', + builder: (context, state) => EmptyChatScreen(author: state.extra as Author), + ), GoRoute( path: '/settings-screen', name: 'settingsScreen', diff --git a/lib/core/websockets/ws_manager.dart b/lib/core/websockets/ws_manager.dart index 02857ca0..152f0fb9 100644 --- a/lib/core/websockets/ws_manager.dart +++ b/lib/core/websockets/ws_manager.dart @@ -39,6 +39,7 @@ class WsManager { metadata: const PusherChannelsOptionsMetadata.byDefault(), port: 443, ); + client = PusherChannelsClient.websocket( options: options, connectionErrorHandler: (exception, trace, refresh) { @@ -136,7 +137,7 @@ class WsManager { _privateDirectChatChannel?.unsubscribe(); } - Future clear() async{ + Future clear() async { await client?.disconnect(); client = null; userId = null; diff --git a/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart b/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart index cbb886c2..369993e0 100644 --- a/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart +++ b/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart @@ -177,14 +177,12 @@ class ChatRoomCubit extends Cubit { } Future sendMessage({ - required String chatId, String? data, List? files, required String userSlug, }) async { try { await chatRepository.sendMessage( - chatId: chatId, data: data, files: files != null ? await ImageCompressUtility.compressFilesList(files) : null, slug: userSlug, diff --git a/lib/features/chat/presentation/pages/chat_screen.dart b/lib/features/chat/presentation/pages/chat_screen.dart index ba08452a..e2e222cd 100644 --- a/lib/features/chat/presentation/pages/chat_screen.dart +++ b/lib/features/chat/presentation/pages/chat_screen.dart @@ -387,7 +387,6 @@ class _ChatScreenState extends State { onTap: () { if (textEditingController.text.isNotEmpty) { chatRoomCubit.sendMessage( - chatId: widget.chatId, data: textEditingController.text.trim(), userSlug: state.chatRoom.users.receiver.slug, ); @@ -411,14 +410,15 @@ class _ChatScreenState extends State { ), if (showAboutOrderButton) Positioned( - top: 8, - right: 15, - left: 16, - child: AboutOrderButton( - onTap: () async { - GoRouter.of(context).push(InfoByOrderScreen.path, extra: {'planId': state.chatRoom.chosenPlan?.id}); - }, - )), + top: 8, + right: 15, + left: 16, + child: AboutOrderButton( + onTap: () async { + GoRouter.of(context).push(InfoByOrderScreen.path, extra: {'planId': state.chatRoom.chosenPlan?.id}); + }, + ), + ), ], ), ), @@ -436,7 +436,7 @@ class _ChatScreenState extends State { final picker = ImagePicker(); XFile? image = await picker.pickImage(source: ImageSource.camera); if (image != null) { - await chatRoomCubit.sendMessage(chatId: widget.chatId, files: [File(image.path)], userSlug: slug); + await chatRoomCubit.sendMessage(files: [File(image.path)], userSlug: slug); } }, child: const Row( @@ -447,6 +447,24 @@ class _ChatScreenState extends State { ], ), ), + CupertinoActionSheetAction( + onPressed: () async { + Navigator.of(context).pop(); + final picker = ImagePicker(); + List images = await picker.pickMultiImage(); + if (images != []) { + List files = images.map((image) => File(image.path)).toList(); + await chatRoomCubit.sendMessage(files: files, userSlug: slug); + } + }, + child: const Row( + children: [ + Icon(Icons.photo_library_rounded), + SizedBox(width: 8), + Text('Галерея'), + ], + ), + ), CupertinoActionSheetAction( onPressed: () async { Navigator.of(context).pop(); @@ -476,7 +494,7 @@ class _ChatScreenState extends State { ); if (result != null) { List files = result.paths.map((path) => File(path!)).toList(); - await chatRoomCubit.sendMessage(chatId: widget.chatId, files: files, userSlug: slug); + await chatRoomCubit.sendMessage(files: files, userSlug: slug); } }, child: const Row( diff --git a/lib/features/chat/presentation/pages/empty_chat_screen.dart b/lib/features/chat/presentation/pages/empty_chat_screen.dart new file mode 100644 index 00000000..2f8e91b3 --- /dev/null +++ b/lib/features/chat/presentation/pages/empty_chat_screen.dart @@ -0,0 +1,289 @@ +import 'dart:io'; +import 'dart:math'; + +import 'package:eatvio_app/core/app_router.dart'; +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/features/chat/repositories/chat_repository.dart'; +import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:image_picker/image_picker.dart'; + +class EmptyChatScreen extends StatefulWidget { + final Author author; + + const EmptyChatScreen({ + super.key, + required this.author, + }); + + @override + State createState() => _EmptyChatScreenState(); +} + +class _EmptyChatScreenState extends State { + final TextEditingController textEditingController = TextEditingController(); + + @override + Widget build(BuildContext context) { + return Scaffold( + resizeToAvoidBottomInset: true, + appBar: AppBar( + titleSpacing: 0, + backgroundColor: appTheme.gray100, + centerTitle: false, + title: GestureDetector( + onTap: () { + GoRouter.of(context).pushNamed( + 'profileScreen', + extra: Author( + authorSlug: widget.author.authorSlug, + authorName: widget.author.authorName, + authorProfileIcon: widget.author.authorProfileIcon, + authorIsMe: false, + ), + ); + }, + child: Row( + children: [ + CircleAvatar( + radius: 20, + foregroundImage: NetworkImage(widget.author.authorProfileIcon), + backgroundImage: const AssetImage('assets/images/no_avatar.png'), + ), + const SizedBox(width: 16), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.author.authorName, + style: CustomTextStyles.titleSmallBold.copyWith( + color: PrimaryColors().black, + ), + ), + ], + ), + ], + ), + ), + ), + body: SafeArea( + child: Column( + children: [ + const Spacer(), + Container( + padding: const EdgeInsets.symmetric(vertical: 8), + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + const SizedBox(width: 8), + InkWell( + onTap: () => showImagePicker(widget.author.authorSlug), + customBorder: const CircleBorder(), + child: Ink( + height: 40, + width: 40, + child: Transform.rotate( + angle: pi / 4, + child: const Icon( + Icons.attach_file_rounded, + size: 30, + color: Color(0xFF858E99), + ), + ), + ), + ), + const SizedBox(width: 8), + Expanded( + child: SizedBox( + height: 40, + child: TextFormField( + expands: true, + textCapitalization: TextCapitalization.sentences, + controller: textEditingController, + maxLines: null, + minLines: null, + keyboardType: TextInputType.multiline, + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w400, color: Colors.black, height: 1), + decoration: InputDecoration( + isDense: true, + contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + hintText: 'Сообщение', + hintStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w400, height: 1, color: Color(0xFFAEAEB2)), + filled: true, + border: + OutlineInputBorder(borderRadius: BorderRadius.circular(100), borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1)), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(100), + borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(100), + borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1), + ), + ), + ), + ), + ), + const SizedBox(width: 8), + InkWell( + customBorder: const CircleBorder(), + onTap: () async { + if (textEditingController.text.isNotEmpty) { + await sl().sendMessage( + data: textEditingController.text.trim(), + slug: widget.author.authorSlug, + ); + setState(() { + textEditingController.clear(); + }); + final chatIdOrError = await sl().getChatByUserSlug(widget.author.authorSlug); + AppRouter.navigatorKey.currentContext?.pushReplacementNamed( + 'chatScreen', + extra: { + 'chatId': chatIdOrError, + }, + ); + } + }, + child: Ink( + decoration: BoxDecoration(color: appTheme.blueA400, shape: BoxShape.circle), + height: 40, + width: 40, + child: const Icon(Icons.arrow_upward_rounded, size: 24, color: Colors.white), + ), + ), + const SizedBox(width: 8), + ], + ), + ), + ], + ), + ), + ); + } + + showImagePicker(String slug) { + showCupertinoModalPopup( + context: context, + builder: (context) => CupertinoActionSheet( + actions: [ + CupertinoActionSheetAction( + onPressed: () async { + Navigator.of(context).pop(); + final picker = ImagePicker(); + XFile? image = await picker.pickImage(source: ImageSource.camera); + if (image != null) { + await sl().sendMessage( + files: [File(image.path)], + slug: widget.author.authorSlug, + ); + final chatIdOrError = await sl().getChatByUserSlug(widget.author.authorSlug); + AppRouter.navigatorKey.currentContext?.pushReplacementNamed( + 'chatScreen', + extra: { + 'chatId': chatIdOrError, + }, + ); + } + }, + child: const Row( + children: [ + Icon(Icons.photo_library_rounded), + SizedBox(width: 8), + Text('Галерея'), + ], + ), + ), + CupertinoActionSheetAction( + onPressed: () async { + Navigator.of(context).pop(); + final picker = ImagePicker(); + List? images = await picker.pickMultiImage(); + if (images.isNotEmpty) { + List files = images.map((image) => File(image.path)).toList(); + await sl().sendMessage( + files: files, + slug: widget.author.authorSlug, + ); + final chatIdOrError = await sl().getChatByUserSlug(widget.author.authorSlug); + AppRouter.navigatorKey.currentContext?.pushReplacementNamed( + 'chatScreen', + extra: { + 'chatId': chatIdOrError, + }, + ); + } + }, + child: const Row( + children: [ + Icon(Icons.photo_camera), + SizedBox(width: 8), + Text('Фото'), + ], + ), + ), + CupertinoActionSheetAction( + onPressed: () async { + Navigator.of(context).pop(); + FilePickerResult? result = await FilePicker.platform.pickFiles( + allowMultiple: true, + type: FileType.custom, + allowedExtensions: [ + 'pdf', + 'doc', + 'docx', + 'json', + 'ppt', + 'pptx', + 'rtf', + 'txt', + 'xls', + 'xlsx', + 'csv', + 'jpeg', + 'jpg', + 'png', + 'gif', + 'svg', + 'xml', + 'webp', + ], + ); + if (result != null) { + List files = result.paths.map((path) => File(path!)).toList(); + await sl().sendMessage( + files: files, + slug: widget.author.authorSlug, + ); + final chatIdOrError = await sl().getChatByUserSlug(widget.author.authorSlug); + AppRouter.navigatorKey.currentContext?.pushReplacementNamed( + 'chatScreen', + extra: { + 'chatId': chatIdOrError, + }, + ); + } + }, + child: const Row( + children: [ + Icon(Icons.file_present_rounded), + SizedBox(width: 8), + Text('Файлы'), + ], + ), + ), + ], + cancelButton: CupertinoActionSheetAction( + onPressed: () => Navigator.of(context).pop(), + isDestructiveAction: true, + child: const Text('Отмена'), + ), + ), + ); + } +} diff --git a/lib/features/chat/repositories/chat_repository.dart b/lib/features/chat/repositories/chat_repository.dart index 1215a5f6..e249c708 100644 --- a/lib/features/chat/repositories/chat_repository.dart +++ b/lib/features/chat/repositories/chat_repository.dart @@ -27,7 +27,6 @@ class ChatRepository { } Future sendMessage({ - required String chatId, required String slug, String? data, List? files, @@ -168,12 +167,4 @@ class ChatRepository { return 'error'; } } - - Future sendFirstChatMessageForCreateChatRoom(String userSlug) async { - try { - return await chatService.sendMessageByUserSlug(userSlug: userSlug, data: 'Создал чат!'); - } catch (e) { - rethrow; - } - } } diff --git a/lib/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart b/lib/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart index 530bc9aa..1312dea8 100644 --- a/lib/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart +++ b/lib/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart @@ -5,6 +5,7 @@ import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart'; import 'package:eatvio_app/core/utils/toasts.dart'; import 'package:eatvio_app/features/chat/repositories/chat_repository.dart'; import 'package:eatvio_app/features/consultant/become_consultant/repositories/consultant_repository.dart'; +import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart'; import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_state.dart'; import 'package:eatvio_app/features/profile/repositories/profile_repository.dart'; @@ -107,18 +108,14 @@ class ProfileCubit extends Cubit { } getChatRoomIdBySlug({ - required String slug, + required Author author, }) async { try { - final chatIdOrError = await chatRepository.getChatByUserSlug(slug); + final chatIdOrError = await chatRepository.getChatByUserSlug(author.authorSlug); if (chatIdOrError == '404') { - await chatRepository.sendFirstChatMessageForCreateChatRoom(slug); - final chatIdOrError = await chatRepository.getChatByUserSlug(slug); AppRouter.navigatorKey.currentContext?.pushNamed( - 'chatScreen', - extra: { - 'chatId': chatIdOrError, - }, + 'emptyChatScreen', + extra: author, ); } else if (chatIdOrError == 'error') { showErrorToast('Ошибка создания чата'); diff --git a/lib/features/profile/presentation/pages/profile_screen.dart b/lib/features/profile/presentation/pages/profile_screen.dart index a847e697..9dc4f880 100644 --- a/lib/features/profile/presentation/pages/profile_screen.dart +++ b/lib/features/profile/presentation/pages/profile_screen.dart @@ -1232,7 +1232,7 @@ class _ProfileScreenState extends State { children: [ const SizedBox(width: 16), GestureDetector( - onTap: () async => await profileCubit.getChatRoomIdBySlug(slug: widget.author!.authorSlug), + onTap: () async => await profileCubit.getChatRoomIdBySlug(author: widget.author!), child: Container( alignment: Alignment.center, padding: const EdgeInsets.all(8), diff --git a/lib/features/profile/presentation/widgets/sub_action_button_widget.dart b/lib/features/profile/presentation/widgets/sub_action_button_widget.dart index c1929a13..bca5e3da 100644 --- a/lib/features/profile/presentation/widgets/sub_action_button_widget.dart +++ b/lib/features/profile/presentation/widgets/sub_action_button_widget.dart @@ -1,5 +1,6 @@ import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart'; import 'package:eatvio_app/features/profile/presentation/bloc/sub_cubit/sub_cubit.dart'; import 'package:flutter/material.dart'; @@ -11,11 +12,15 @@ enum SubActionType { sendMessage, subscribe } class SubActionButtonWidget extends StatefulWidget { final SubActionType subActionType; final String slug; + final String name; + final String porfileIcon; const SubActionButtonWidget({ super.key, required this.subActionType, required this.slug, + required this.name, + required this.porfileIcon, }); @override @@ -64,7 +69,14 @@ class _SubActionButtonWidgetState extends State { GoRouter.of(context).pushNamed( 'chatScreen', extra: { - 'chatId': await sl().getChatRoomIdBySlug(slug: widget.slug), + 'chatId': await sl().getChatRoomIdBySlug( + author: Author( + authorSlug: widget.slug, + authorName: widget.name, + authorProfileIcon: widget.porfileIcon, + authorIsMe: false, + ), + ), }, ); }, diff --git a/lib/features/profile/presentation/widgets/sub_widget.dart b/lib/features/profile/presentation/widgets/sub_widget.dart index aa17433d..27365fd2 100644 --- a/lib/features/profile/presentation/widgets/sub_widget.dart +++ b/lib/features/profile/presentation/widgets/sub_widget.dart @@ -70,6 +70,8 @@ class _SubWidgetState extends State { SubActionButtonWidget( subActionType: widget.subModel.isEntitySubscribedByMe ? SubActionType.sendMessage : SubActionType.subscribe, slug: widget.subModel.slug, + name: widget.subModel.name, + porfileIcon: widget.subModel.profilePhotoUrl, ), ], ), From 7530270a2e909a7fae468ced1cf3aab4308aaee2 Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Wed, 25 Jun 2025 12:22:14 +0300 Subject: [PATCH 2/2] feat(qr+devtools): scan qr code and dev tools --- ios/Runner.xcodeproj/project.pbxproj | 6 +- ios/Runner/Info.plist | 2 +- .../auth/presentation/pages/auth_screen.dart | 6 +- .../pages/scan_qr_code_screen.dart | 70 ++++++++++ .../widgets/bottom_navigation_bar_widget.dart | 21 ++- .../presentation/dev_tools_screen.dart | 124 ++++++++++-------- pubspec.yaml | 3 +- 7 files changed, 165 insertions(+), 67 deletions(-) diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 4fc6eec0..e6a8ab5e 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -388,7 +388,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 30; + CURRENT_PROJECT_VERSION = 31; DEVELOPMENT_TEAM = VL68C36HYS; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -538,7 +538,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 30; + CURRENT_PROJECT_VERSION = 31; DEVELOPMENT_TEAM = VL68C36HYS; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -576,7 +576,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 30; + CURRENT_PROJECT_VERSION = 31; DEVELOPMENT_TEAM = VL68C36HYS; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index f93e2e21..71ddffdc 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -21,7 +21,7 @@ CFBundleSignature ???? CFBundleVersion - 30 + 31 ITSAppUsesNonExemptEncryption LSRequiresIPhoneOS diff --git a/lib/features/auth/presentation/pages/auth_screen.dart b/lib/features/auth/presentation/pages/auth_screen.dart index 147e71cd..877aaf1a 100644 --- a/lib/features/auth/presentation/pages/auth_screen.dart +++ b/lib/features/auth/presentation/pages/auth_screen.dart @@ -88,10 +88,10 @@ class _AuthScreenState extends State { title: GestureDetector( onTap: () { tapsCount++; - if (tapsCount == 6) { + if (tapsCount == 7) { tapsCount = 0; + GoRouter.of(context).pushNamed(DevToolsScreen.name); } - GoRouter.of(context).pushNamed(DevToolsScreen.name); }, child: SvgPicture.asset( ImageConstant.imgLogoEatvio, @@ -244,7 +244,7 @@ class _AuthScreenState extends State { height: 48, width: 48, child: FloatingActionButton.extended( - heroTag: 'authButton', + heroTag: 'scanQrCodeButton', highlightElevation: 0, elevation: 0, focusElevation: 0, diff --git a/lib/features/auth/presentation/pages/scan_qr_code_screen.dart b/lib/features/auth/presentation/pages/scan_qr_code_screen.dart index 2b72d3a0..35f56b50 100644 --- a/lib/features/auth/presentation/pages/scan_qr_code_screen.dart +++ b/lib/features/auth/presentation/pages/scan_qr_code_screen.dart @@ -1,4 +1,14 @@ +import 'dart:io'; + +import 'package:dio/dio.dart'; +import 'package:eatvio_app/core/app_router.dart'; +import 'package:eatvio_app/core/const.dart'; +import 'package:eatvio_app/core/locator_service.dart'; +import 'package:eatvio_app/core/token_storage.dart'; +import 'package:eatvio_app/core/utils/toasts.dart'; import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:qr_code_scanner_plus/qr_code_scanner_plus.dart'; class ScanQRCodeScreen extends StatefulWidget { const ScanQRCodeScreen({super.key}); @@ -8,6 +18,20 @@ class ScanQRCodeScreen extends StatefulWidget { } class _ScanQRCodeScreenState extends State { + final GlobalKey qrKey = GlobalKey(debugLabel: 'QR'); + Barcode? result; + QRViewController? controller; + + @override + void reassemble() { + super.reassemble(); + if (Platform.isAndroid) { + controller!.pauseCamera(); + } else if (Platform.isIOS) { + controller!.resumeCamera(); + } + } + @override Widget build(BuildContext context) { return Scaffold( @@ -15,6 +39,52 @@ class _ScanQRCodeScreenState extends State { title: const Text('Сканирование QR-кода'), centerTitle: true, ), + body: Column( + children: [ + Expanded( + child: QRView( + key: qrKey, + onQRViewCreated: _onQRViewCreated, + ), + ), + ], + ), ); } + + void _onQRViewCreated(QRViewController controller) { + this.controller = controller; + controller.scannedDataStream.listen((scanData) async { + setState(() { + result = scanData; + }); + controller.pauseCamera(); + try { + final scannedUrl = scanData.code; + final uri = Uri.parse(scannedUrl ?? ''); + final scannedBaseUrl = (uri.hasAuthority) ? '${uri.scheme}://${uri.host}' : ''; + baseUrl = scannedBaseUrl; + sl().options.baseUrl = baseUrl; + final response = await Dio().getUri( + uri, + options: Options( + headers: { + 'accept': 'application/json', + 'content-type': 'application/json', + }, + ), + ); + final token = response.data['token']; + await TokenStorage.saveTokenInStorage(token); + AppRouter.navigatorKey.currentContext?.go( + '/feed-screen', + extra: {'needToInitializeWebSocket': false}, + ); + showSuccessToast('Добро пожаловать!'); + } catch (e) { + controller.resumeCamera(); + showErrorToast('Что-то пошло не так, попробуйте обновить страницу с qr кодом или используйте вход через логин и пароль'); + } + }); + } } diff --git a/lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart b/lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart index e370452a..a67fbbaf 100644 --- a/lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart +++ b/lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart @@ -1,6 +1,7 @@ import 'package:badges/badges.dart' as badges; import 'package:eatvio_app/core/const.dart'; import 'package:eatvio_app/core/utils/image_constant.dart'; +import 'package:eatvio_app/features/dev_tools/presentation/dev_tools_screen.dart'; import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart'; import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_state.dart'; import 'package:eatvio_app/features/profile/models/profile_model.dart'; @@ -30,6 +31,7 @@ class BottomNavigationBarWidget extends StatefulWidget { class _BottomNavigationBarWidgetState extends State { int currentBottomNavBarIndex = 1; late final List pages; + int tapsCount = 0; @override void initState() { @@ -58,12 +60,29 @@ class _BottomNavigationBarWidgetState extends State { } void _onItemTapped(int index) async { + if (pages.length == 7) { + if (index == 6) { + tapsCount++; + if (tapsCount == 7) { + tapsCount = 0; + GoRouter.of(context).pushNamed(DevToolsScreen.name); + } + } + } else { + if (index == 5) { + tapsCount++; + if (tapsCount == 7) { + tapsCount = 0; + GoRouter.of(context).pushNamed(DevToolsScreen.name); + } + } + } if (currentBottomNavBarIndex != index) { setState(() { currentBottomNavBarIndex = index; }); + GoRouter.of(context).replace(pages[currentBottomNavBarIndex]); } - GoRouter.of(context).replace(pages[currentBottomNavBarIndex]); } @override diff --git a/lib/features/dev_tools/presentation/dev_tools_screen.dart b/lib/features/dev_tools/presentation/dev_tools_screen.dart index 4658b10a..a57e7580 100644 --- a/lib/features/dev_tools/presentation/dev_tools_screen.dart +++ b/lib/features/dev_tools/presentation/dev_tools_screen.dart @@ -81,6 +81,8 @@ class _DevToolsScreenContentState extends State { onPressed: () { setState(() { baseUrlForTest = controller.text; + baseUrl = baseUrlForTest.toLowerCase(); + sl().options.baseUrl = baseUrl; controller.clear(); }); }, @@ -89,73 +91,79 @@ class _DevToolsScreenContentState extends State { ], ), const SizedBox(height: 10), - if (baseUrlForTest != baseUrl) - CustomOutlinedButton( - text: 'Set dev2 base url', - onPressed: () { - setState(() { - baseUrlForTest = baseUrl; - }); - }, - buttonStyle: const ButtonStyle(), - ), + CustomOutlinedButton( + text: baseUrl == 'https://dev2.eatvio.com' ? 'Set production base url' : 'Set dev2 base url', + onPressed: () { + setState(() { + if (baseUrl == 'https://eatvio.ru') { + baseUrlForTest = 'https://dev2.eatvio.com'; + } else { + baseUrlForTest = 'https://eatvio.ru'; + } + baseUrl = baseUrlForTest.toLowerCase(); + sl().options.baseUrl = baseUrl; + controller.clear(); + }); + }, + buttonStyle: const ButtonStyle(), + ), SizedBox(height: baseUrlForTest != baseUrl ? 40 : 10), - ListView.builder( - shrinkWrap: true, - itemCount: testCases.length, - physics: const NeverScrollableScrollPhysics(), - itemBuilder: - (context, index) => Padding( - padding: const EdgeInsets.only(bottom: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded(child: Text('${index + 1}. ${testCases[index].name}')), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomOutlinedButton( - text: 'run', - height: 20, - width: 60, - onPressed: () async { - if (testsStates[index] == TestState.loading) return; + if (baseUrl.toLowerCase() != 'https://eatvio.ru') + ListView.builder( + shrinkWrap: true, + itemCount: testCases.length, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, index) => Padding( + padding: const EdgeInsets.only(bottom: 10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded(child: Text('${index + 1}. ${testCases[index].name}')), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + CustomOutlinedButton( + text: 'run', + height: 20, + width: 60, + onPressed: () async { + if (testsStates[index] == TestState.loading) return; + setState(() { + testsStates[index] = TestState.loading; + }); + final result = await testCases[index].run(baseUrl: baseUrlForTest); + if (result) { setState(() { - testsStates[index] = TestState.loading; + testsStates[index] = TestState.success; }); - final result = await testCases[index].run(baseUrl: baseUrlForTest); - if (result) { - setState(() { - testsStates[index] = TestState.success; - }); - showSuccessToast('${testCases[index].name} passed'); - } else { - setState(() { - testsStates[index] = TestState.fail; - }); - showErrorToast('${testCases[index].name} failed'); - } - }, - buttonStyle: const ButtonStyle(), - ), - const SizedBox(width: 5), - switch (testsStates[index]) { - TestState.success => Container(width: 20, height: 20, color: Colors.green), - TestState.fail => Container(width: 20, height: 20, color: Colors.red), - TestState.notRun => Container(width: 20, height: 20, color: Colors.grey), - TestState.loading => const SizedBox( + showSuccessToast('${testCases[index].name} passed'); + } else { + setState(() { + testsStates[index] = TestState.fail; + }); + showErrorToast('${testCases[index].name} failed'); + } + }, + buttonStyle: const ButtonStyle(), + ), + const SizedBox(width: 5), + switch (testsStates[index]) { + TestState.success => Container(width: 20, height: 20, color: Colors.green), + TestState.fail => Container(width: 20, height: 20, color: Colors.red), + TestState.notRun => Container(width: 20, height: 20, color: Colors.grey), + TestState.loading => const SizedBox( width: 20, height: 20, child: SizedBox(height: 20, width: 20, child: Center(child: CircularProgressIndicator())), ), - }, - ], - ), - ], - ), + }, + ], + ), + ], ), - ), + ), + ), const SizedBox(height: 10), CustomOutlinedButton( text: 'add token to server', diff --git a/pubspec.yaml b/pubspec.yaml index e9316685..9e4ef5af 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: eatvio_app description: A new Flutter project. -version: 1.1.7+37 +version: 1.1.8+38 publish_to: 'none' environment: @@ -71,6 +71,7 @@ dependencies: package_info_plus: ^8.3.0 device_info_plus: ^11.4.0 permission_handler: ^12.0.0+1 + qr_code_scanner_plus: ^2.0.10+1 dev_dependencies: build_runner: ^2.4.15