From 0512e22727519f45440990d47839af5ec915078e Mon Sep 17 00:00:00 2001 From: OkaykOrhmn Date: Wed, 2 Oct 2024 15:01:07 +0330 Subject: [PATCH] 11/06/1403 -- Rhmn -- fix web and change ai message bar and dall e bot. appVersion: 3.3.4 --- lib/models/ai/ai_chat_args.dart | 3 +- lib/models/ai/files_model.dart | 5 +- lib/services/media/voice.dart | 4 +- lib/utils/action_sheet.dart | 14 +- lib/views/ai/ai.dart | 140 ++-- lib/views/ai/ai_chat_page.dart | 103 ++- lib/views/ai/ai_chat_state.dart | 17 +- lib/views/ai/widgets/ai_message_bar.dart | 586 ++++++++------ lib/views/ai/widgets/ai_message_bar_ios.dart | 791 +++++++++++++++++++ lib/views/ai/widgets/audio_wave.dart | 17 +- lib/views/direct/direct_state.dart | 8 +- lib/views/direct/widgets/message.dart | 4 +- lib/views/widgets/skeleton_image.dart | 9 +- pubspec.lock | 56 ++ pubspec.yaml | 2 +- 15 files changed, 1435 insertions(+), 324 deletions(-) create mode 100644 lib/views/ai/widgets/ai_message_bar_ios.dart diff --git a/lib/models/ai/ai_chat_args.dart b/lib/models/ai/ai_chat_args.dart index d0ab3cf..1445425 100644 --- a/lib/models/ai/ai_chat_args.dart +++ b/lib/models/ai/ai_chat_args.dart @@ -4,6 +4,7 @@ import 'package:didvan/models/ai/chats_model.dart'; class AiChatArgs { final BotsModel bot; final ChatsModel? chat; + final Prompts? prompts; - AiChatArgs({required this.bot, this.chat}); + AiChatArgs({required this.bot, this.chat, this.prompts}); } diff --git a/lib/models/ai/files_model.dart b/lib/models/ai/files_model.dart index c2620d5..09faf13 100644 --- a/lib/models/ai/files_model.dart +++ b/lib/models/ai/files_model.dart @@ -32,6 +32,7 @@ class FilesModel { : name != null ? p.extension(name) : ''; + main = File(path); } @@ -42,7 +43,9 @@ class FilesModel { } bool isImage() { - return image ?? lookupMimeType(path)?.startsWith('image/') ?? false; + return image ?? + lookupMimeType(path)?.startsWith('image/') ?? + false || path.contains(".png"); } bool isNetwork() { diff --git a/lib/services/media/voice.dart b/lib/services/media/voice.dart index 4569718..ba5619b 100644 --- a/lib/services/media/voice.dart +++ b/lib/services/media/voice.dart @@ -1,4 +1,3 @@ - import 'package:didvan/services/network/request.dart'; import 'package:didvan/services/network/request_helper.dart'; import 'package:flutter/foundation.dart'; @@ -66,7 +65,6 @@ class VoiceService { static Future voiceHelper( {required String src, - final Uint8List? bytes, final Duration? duration, final Function()? startTimer, final Function()? stopTimer}) async { @@ -96,7 +94,7 @@ class VoiceService { // AudioSource.uri(Uri.parse(blobUrl)), // ); // } else { - await audioPlayer.setUrl(source); + await audioPlayer.setUrl(source.replaceAll('%3A', '')); // } } else if (src.startsWith('blob:') || src == '') { await audioPlayer.setUrl(src); diff --git a/lib/utils/action_sheet.dart b/lib/utils/action_sheet.dart index 1ef10bc..bfa0ee3 100644 --- a/lib/utils/action_sheet.dart +++ b/lib/utils/action_sheet.dart @@ -364,12 +364,14 @@ class ActionSheetUtils { padding: const EdgeInsets.symmetric( vertical: 8), decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Theme.of(context) - .colorScheme - .border, - width: 1))), + border: index == state.bots.length - 1 + ? null + : Border( + bottom: BorderSide( + color: Theme.of(context) + .colorScheme + .border, + width: 1))), child: Row( children: [ ClipOval( diff --git a/lib/views/ai/ai.dart b/lib/views/ai/ai.dart index 86cc9b5..e27a244 100644 --- a/lib/views/ai/ai.dart +++ b/lib/views/ai/ai.dart @@ -164,80 +164,96 @@ class _AiState extends State { ), ), Padding( - padding: const EdgeInsets.fromLTRB(20, 0, 20, 32), + padding: const EdgeInsets.fromLTRB(20, 0, 20, 12), child: InkWell( onTap: () => Navigator.of(context).pushNamed(Routes.aiChat, arguments: AiChatArgs( bot: bot, )), - child: Row( + child: Column( children: [ - Expanded( - child: Container( - decoration: BoxDecoration( - boxShadow: DesignConfig.defaultShadow, - color: Theme.of(context).colorScheme.surface, - border: Border.all( + Row( + children: [ + Expanded( + child: Container( + decoration: BoxDecoration( + boxShadow: DesignConfig.defaultShadow, color: - Theme.of(context).colorScheme.border), - borderRadius: DesignConfig.highBorderRadius), - child: Row( - children: [ - const SizedBox( - width: 8, - ), - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8.0, - ), - child: Form( - child: Row( - children: [ - const MessageBarBtn( - enable: true, - icon: - DidvanIcons.mic_regular), - const SizedBox( - width: 8, + Theme.of(context).colorScheme.surface, + border: Border.all( + color: Theme.of(context) + .colorScheme + .border), + borderRadius: + DesignConfig.highBorderRadius), + child: Row( + children: [ + Expanded( + child: Padding( + padding: + const EdgeInsets.symmetric( + horizontal: 8.0, ), - Expanded( - child: TextFormField( - textInputAction: - TextInputAction.newline, - style: Theme.of(context) - .textTheme - .bodyMedium, - minLines: 1, - enabled: false, - decoration: InputDecoration( - border: InputBorder.none, - hintText: 'بنویسید...', - hintStyle: Theme.of(context) - .textTheme - .bodySmall! - .copyWith( - color: Theme.of( - context) - .colorScheme - .disabledText), + child: Form( + child: Row( + children: [ + const MessageBarBtn( + enable: true, + icon: DidvanIcons + .mic_regular), + const SizedBox( + width: 8, ), - ), - ), - const SizedBox( - width: 8, - ), - const MessageBarBtn( - enable: false, - icon: Icons - .attach_file_rounded), - ], - )))) - ], + Expanded( + child: TextFormField( + textInputAction: + TextInputAction + .newline, + style: Theme.of(context) + .textTheme + .bodyMedium, + minLines: 1, + enabled: false, + decoration: + InputDecoration( + border: + InputBorder.none, + hintText: 'بنویسید...', + hintStyle: Theme.of( + context) + .textTheme + .bodySmall! + .copyWith( + color: Theme.of( + context) + .colorScheme + .disabledText), + ), + ), + ), + const SizedBox( + width: 8, + ), + const MessageBarBtn( + enable: false, + icon: Icons + .attach_file_rounded), + ], + )))) + ], + ), + ), ), - ), + ], ), + const Padding( + padding: EdgeInsets.fromLTRB(8, 8, 8, 4), + child: DidvanText( + 'مدل‌های هوش مصنوعی می‌توانند اشتباه کنند، صحت اطلاعات مهم را بررسی کنید و از وارد کردن اطلاعات حساس بپرهیزید.', + fontSize: 12, + ), + ) ], )), ), diff --git a/lib/views/ai/ai_chat_page.dart b/lib/views/ai/ai_chat_page.dart index f9e1fe6..6f83d27 100644 --- a/lib/views/ai/ai_chat_page.dart +++ b/lib/views/ai/ai_chat_page.dart @@ -1,21 +1,27 @@ // ignore_for_file: library_private_types_in_public_api, deprecated_member_use, depend_on_referenced_packages +import 'dart:io'; + import 'package:cached_network_image/cached_network_image.dart'; import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/constants/assets.dart'; +import 'package:didvan/main.dart'; import 'package:didvan/models/ai/ai_chat_args.dart'; import 'package:didvan/models/ai/chats_model.dart'; import 'package:didvan/models/ai/files_model.dart'; +import 'package:didvan/models/ai/messages_model.dart'; import 'package:didvan/models/enums.dart'; import 'package:didvan/models/view/action_sheet_data.dart'; import 'package:didvan/models/view/alert_data.dart'; +import 'package:didvan/routes/routes.dart'; import 'package:didvan/utils/action_sheet.dart'; import 'package:didvan/utils/date_time.dart'; import 'package:didvan/views/ai/ai_chat_state.dart'; import 'package:didvan/views/ai/history_ai_chat_state.dart'; import 'package:didvan/views/ai/widgets/ai_message_bar.dart'; +import 'package:didvan/views/ai/widgets/ai_message_bar_ios.dart'; import 'package:didvan/views/ai/widgets/audio_wave.dart'; import 'package:didvan/views/widgets/didvan/icon_button.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; @@ -62,6 +68,17 @@ class _AiChatPageState extends State { () => focusNode.requestFocus(), ); } + if (widget.args.prompts != null) { + state.messages.add(MessageModel( + dateTime: DateTime.now() + .subtract(const Duration(minutes: 210)) + .toIso8601String(), + prompts: [widget.args.prompts!])); + + state.message.clear(); + state.update(); + await state.postMessage(widget.args.bot); + } }); super.initState(); } @@ -296,10 +313,13 @@ class _AiChatPageState extends State { bottomSheet: Column( mainAxisSize: MainAxisSize.min, children: [ - AiMessageBar( - bot: widget.args.bot, - // focusNode: focusNode, - ), + Platform.isIOS + ? AiMessageBarIOS( + bot: widget.args.bot, + ) + : AiMessageBar( + bot: widget.args.bot, + ), ], )), ), @@ -429,8 +449,7 @@ class _AiChatPageState extends State { ) : Column( children: [ - if (message.role.toString().contains('user') && - file != null) + if (file != null) (file.isAudio() // && (!kIsWeb && !Platform.isIOS) ) @@ -472,6 +491,78 @@ class _AiChatPageState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ + if (message.role.toString().contains('user')) + PopupMenuButton( + offset: const Offset(0, 46), + onSelected: (value) async { + navigatorKey.currentState!.pushNamed( + Routes.aiChat, + arguments: AiChatArgs( + bot: value, + prompts: message)); + }, + itemBuilder: (BuildContext context) { + final historyAiChatState = context + .read(); + return [ + ...List.generate( + historyAiChatState.bots.length, + (index) => PopupMenuItem( + value: historyAiChatState + .bots[index], + height: 72, + child: Row( + children: [ + ClipOval( + child: CachedNetworkImage( + imageUrl: + historyAiChatState + .bots[index] + .image + .toString(), + width: 42, + height: 42, + ), + ), + const SizedBox(width: 12), + DidvanText( + '${historyAiChatState.bots[index].name}X', + ), + ], + ), + ), + ) + ]; + }, + child: Container( + alignment: Alignment.center, + margin: const EdgeInsets.all(8), + padding: const EdgeInsets.symmetric( + horizontal: 8), + constraints: const BoxConstraints( + maxWidth: 120), + decoration: BoxDecoration( + borderRadius: + DesignConfig.lowBorderRadius, + border: Border.all( + color: Theme.of(context) + .colorScheme + .title)), + child: Row( + children: [ + Expanded( + child: DidvanText( + '${widget.args.bot.name}', + maxLines: 1, + overflow: + TextOverflow.ellipsis, + ), + ), + const Icon( + DidvanIcons.angle_down_light), + ], + ), + )), if (message.role .toString() .contains('user') && diff --git a/lib/views/ai/ai_chat_state.dart b/lib/views/ai/ai_chat_state.dart index 2858796..b534842 100644 --- a/lib/views/ai/ai_chat_state.dart +++ b/lib/views/ai/ai_chat_state.dart @@ -177,9 +177,13 @@ class AiChatState extends CoreProvier { final r = res.listen((value) async { var str = utf8.decode(value); + if (!kIsWeb) { + if (bot.id == 12) { + responseMessgae += str.split('{{{').first; + } if (str.contains('{{{')) { - dataMessgae += str; + dataMessgae += "{{{${str.split('{{{').last}"; update(); return; } @@ -198,6 +202,10 @@ class AiChatState extends CoreProvier { } catch (e) { e.printError(); } + if (bot.id == 12) { + responseMessgae = "${responseMessgae.split('.png').first}.png"; + return; + } } messageOnstream.value = Stream.value(responseMessgae); @@ -233,8 +241,11 @@ class AiChatState extends CoreProvier { e.printError(); return; } - messages.last.prompts.last = messages.last.prompts.last - .copyWith(finished: true, text: responseMessgae, id: aiMessageId); + messages.last.prompts.last = messages.last.prompts.last.copyWith( + finished: true, + text: bot.id == 12 ? null : responseMessgae, + file: bot.id == 12 ? responseMessgae : null, + id: aiMessageId); if (messages.last.prompts.length > 2) { messages.last.prompts[messages.last.prompts.length - 2] = messages .last.prompts[messages.last.prompts.length - 2] diff --git a/lib/views/ai/widgets/ai_message_bar.dart b/lib/views/ai/widgets/ai_message_bar.dart index 4816e9a..6c3cd3c 100644 --- a/lib/views/ai/widgets/ai_message_bar.dart +++ b/lib/views/ai/widgets/ai_message_bar.dart @@ -1,7 +1,7 @@ // ignore_for_file: library_private_types_in_public_api, avoid_web_libraries_in_flutter import 'dart:async'; -// import 'dart:html' as html; +import 'package:universal_html/html.dart' as html; import 'dart:io'; import 'package:audio_session/audio_session.dart'; import 'package:didvan/config/design_config.dart'; @@ -60,6 +60,7 @@ class _AiMessageBarState extends State { Timer? _timer; final theSource = AudioSource.microphone; final ValueNotifier _countTimer = ValueNotifier(Duration.zero); + late HistoryAiChatState historyState = context.read(); @override void initState() { @@ -174,9 +175,6 @@ class _AiMessageBarState extends State { _mPlayer! .startPlayer( fromURI: _mPath, - // fromDataBuffer: Uint8List.fromList( - // recordedData.expand((element) => element).toList()), - //codec: kIsWeb ? Codec.opusWebM : Codec.aacADTS, whenFinished: () { setState(() {}); }) @@ -226,26 +224,18 @@ class _AiMessageBarState extends State { @override Widget build(BuildContext context) { return Consumer(builder: (context, state, child) { - final historyState = context.read(); - return Container( - padding: const EdgeInsets.symmetric(vertical: 24, horizontal: 12) - .copyWith( - top: openAttach || - (state.file != null && !state.file!.isRecorded) - ? 0 - : 24), + padding: const EdgeInsets.fromLTRB(12, 24, 12, 0).copyWith( + top: (state.file != null && !state.file!.isRecorded) ? 0 : 24), decoration: BoxDecoration( - color: Theme.of(context).colorScheme.background, - border: Border( - top: openAttach - ? BorderSide(color: Theme.of(context).colorScheme.border) - : BorderSide.none)), - child: Stack( + color: Theme.of(context).colorScheme.background, + ), + child: Column( children: [ - Column( + if (state.file != null && !(state.file!.isRecorded)) + fileContainer(), + Stack( children: [ - attachmentLayout(state, historyState, context), Container( decoration: BoxDecoration( boxShadow: DesignConfig.defaultShadow, @@ -283,24 +273,35 @@ class _AiMessageBarState extends State { ], ), ), + if (state.onResponsing) + Positioned.fill( + child: Container( + decoration: BoxDecoration( + color: Theme.of(context) + .colorScheme + .focused + .withOpacity(0.5)), + child: Center( + child: SpinKitThreeBounce( + color: Theme.of(context).colorScheme.primary, + size: 32, + ), + ), + ), + ) ], ), - if (state.onResponsing) - Positioned.fill( - child: Container( - decoration: BoxDecoration( - color: Theme.of(context) - .colorScheme - .focused - .withOpacity(0.5)), - child: Center( - child: SpinKitThreeBounce( - color: Theme.of(context).colorScheme.primary, - size: 32, + MediaQuery.of(context).viewInsets.bottom == 0 + ? const Padding( + padding: EdgeInsets.fromLTRB(8, 8, 8, 4), + child: DidvanText( + 'مدل‌های هوش مصنوعی می‌توانند اشتباه کنند، صحت اطلاعات مهم را بررسی کنید و از وارد کردن اطلاعات حساس بپرهیزید.', + fontSize: 12, ), - ), - ), - ) + ) + : const SizedBox( + height: 12, + ) ], ), ); @@ -411,26 +412,28 @@ class _AiMessageBarState extends State { }, )), ), - Padding( - padding: const EdgeInsets.fromLTRB(12, 0, 12, 0), - child: MessageBarBtn( - enable: false, - icon: openAttach || state.file != null - ? DidvanIcons.close_regular - : Icons.attach_file_outlined, - click: () { - if (_mPlayer!.isPlaying) { - stopPlayer(); - } - if (state.file != null) { - state.file = null; - } else { - openAttach = !openAttach; - } - state.update(); - }, - ), - ) + attachmentLayout(state, context), + if (widget.bot.attachmentType!.isNotEmpty) + Padding( + padding: const EdgeInsets.fromLTRB(12, 0, 12, 0), + child: MessageBarBtn( + enable: false, + icon: openAttach || state.file != null + ? DidvanIcons.close_regular + : Icons.attach_file_outlined, + click: () { + if (_mPlayer!.isPlaying) { + stopPlayer(); + } + if (state.file != null) { + state.file = null; + } else { + openAttach = !openAttach; + } + state.update(); + }, + ), + ) ], ); } @@ -510,186 +513,328 @@ class _AiMessageBarState extends State { ); } - AnimatedVisibility attachmentLayout(AiChatState state, - HistoryAiChatState historyState, BuildContext context) { + // AnimatedVisibility attachmentLayout(AiChatState state, + // HistoryAiChatState historyState, BuildContext context) { + // return AnimatedVisibility( + // isVisible: openAttach, + // duration: DesignConfig.lowAnimationDuration, + // child: Container( + // padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 12), + // child: state.file != null && !(state.file!.isRecorded) + // ? fileContainer() + // : Row( + // mainAxisAlignment: MainAxisAlignment.center, + // children: [ + // if (historyState.bot!.attachmentType!.contains('pdf')) + // attachBtn( + // title: "PDF", + // icon: CupertinoIcons.doc_fill, + // color: Colors.redAccent, + // click: () async { + // MediaService.onLoadingPickFile(context); + // FilePickerResult? result = + // await MediaService.pickPdfFile(); + // if (result != null) { + // if (kIsWeb) { + // Uint8List? bytes = result.files.first + // .bytes; // Access the bytes property + // String? name = result.files.first.name; + + // // Store bytes and file name directly in your state or model + // state.file = FilesModel( + // '', // No need for a file path on web + // name: name, + // bytes: bytes, + // audio: false, + // image: false, + // ); + // } else { + // state.file = FilesModel(result.files.single.path!, + // audio: false, image: false); + // } + // } + // Future.delayed( + // Duration.zero, + // () => ActionSheetUtils(context).pop(), + // ); + // }, + // ), + // if (historyState.bot!.attachmentType!.contains('image')) + // attachBtn( + // title: "تصویر", + // icon: CupertinoIcons.photo, + // color: Colors.deepOrangeAccent, + // click: () async { + // MediaService.onLoadingPickFile(context); + + // final pickedFile = await MediaService.pickImage( + // source: ImageSource.gallery); + // File? file; + // if (pickedFile != null && !kIsWeb) { + // file = await ImageCropper().cropImage( + // sourcePath: pickedFile.path, + // androidUiSettings: const AndroidUiSettings( + // toolbarTitle: 'برش تصویر'), + // iosUiSettings: const IOSUiSettings( + // title: 'برش تصویر', + // doneButtonTitle: 'تایید', + // cancelButtonTitle: 'بازگشت', + // ), + // compressQuality: 30, + // ); + + // if (file == null) { + // await Future.delayed( + // Duration.zero, + // () => ActionSheetUtils(context).pop(), + // ); + + // return; + // } + // } + // if (pickedFile == null) { + // await Future.delayed( + // Duration.zero, + // () => ActionSheetUtils(context).pop(), + // ); + + // return; + // } + // state.file = kIsWeb + // ? FilesModel(pickedFile.path, + // name: pickedFile.name, + // image: true, + // audio: false) + // : FilesModel(file!.path, + // image: true, audio: false); + // await Future.delayed( + // Duration.zero, + // () => ActionSheetUtils(context).pop(), + // ); + // }, + // ), + // // if (!kIsWeb && !Platform.isIOS) + // if (historyState.bot!.attachmentType!.contains('audio')) + // attachBtn( + // title: "صوت", + // icon: CupertinoIcons.music_note_2, + // color: Colors.indigoAccent, + // click: () async { + // MediaService.onLoadingPickFile(context); + + // FilePickerResult? result = + // await MediaService.pickAudioFile(); + // if (result != null) { + // if (kIsWeb) { + // Uint8List? bytes = result.files.first + // .bytes; // Access the bytes property + // String? name = result.files.first.name; + + // // final blob = html.Blob([bytes]); + // // final blobUrl = + // // html.Url.createObjectUrlFromBlob(blob); + + // state.file = FilesModel( + // "", // No need for a file path on web + // name: name, + // bytes: bytes, + // audio: true, + // image: false, + // ); + // } else { + // state.file = FilesModel(result.files.single.path!, + // audio: true, image: false); + // } + // } + // await Future.delayed( + // Duration.zero, + // () => ActionSheetUtils(context).pop(), + // ); + // }, + // ) + // ], + // ), + // )); + // } + + AnimatedVisibility attachmentLayout(AiChatState state, BuildContext context) { return AnimatedVisibility( isVisible: openAttach, + fadeMode: FadeMode.horizontal, duration: DesignConfig.lowAnimationDuration, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 12), - child: state.file != null && !(state.file!.isRecorded) - ? fileContainer() - : Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - if (historyState.bot!.attachmentType!.contains('pdf')) - attachBtn( - title: "PDF", - icon: CupertinoIcons.doc_fill, - color: Colors.redAccent, - click: () async { - MediaService.onLoadingPickFile(context); - FilePickerResult? result = - await MediaService.pickPdfFile(); - if (result != null) { - if (kIsWeb) { - Uint8List? bytes = result.files.first - .bytes; // Access the bytes property - String? name = result.files.first.name; + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (historyState.bot!.attachmentType!.contains('pdf')) + MessageBarBtn( + enable: true, + icon: CupertinoIcons.doc_fill, + click: () async { + MediaService.onLoadingPickFile(context); + FilePickerResult? result = await MediaService.pickPdfFile(); + if (result != null) { + if (kIsWeb) { + Uint8List? bytes = + result.files.first.bytes; // Access the bytes property + String? name = result.files.first.name; - // Store bytes and file name directly in your state or model - state.file = FilesModel( - '', // No need for a file path on web - name: name, - bytes: bytes, - audio: false, - image: false, - ); - } else { - state.file = FilesModel(result.files.single.path!, - audio: false, image: false); - } - } - Future.delayed( - Duration.zero, - () => ActionSheetUtils(context).pop(), - ); - }, - ), - if (historyState.bot!.attachmentType!.contains('image')) - attachBtn( - title: "تصویر", - icon: CupertinoIcons.photo, - color: Colors.deepOrangeAccent, - click: () async { - MediaService.onLoadingPickFile(context); + // Store bytes and file name directly in your state or model + state.file = FilesModel( + '', // No need for a file path on web + name: name, + bytes: bytes, + audio: false, + image: false, + ); + } else { + state.file = FilesModel(result.files.single.path!, + audio: false, image: false); + } + } + Future.delayed( + Duration.zero, + () => ActionSheetUtils(context).pop(), + ); + openAttach = !openAttach; - final pickedFile = await MediaService.pickImage( - source: ImageSource.gallery); - File? file; - if (pickedFile != null && !kIsWeb) { - file = await ImageCropper().cropImage( - sourcePath: pickedFile.path, - androidUiSettings: const AndroidUiSettings( - toolbarTitle: 'برش تصویر'), - iosUiSettings: const IOSUiSettings( - title: 'برش تصویر', - doneButtonTitle: 'تایید', - cancelButtonTitle: 'بازگشت', - ), - compressQuality: 30, - ); + state.update(); + }, + ), + if (historyState.bot!.attachmentType!.contains('image')) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: MessageBarBtn( + enable: true, + icon: CupertinoIcons.photo, + click: () async { + MediaService.onLoadingPickFile(context); - if (file == null) { - await Future.delayed( - Duration.zero, - () => ActionSheetUtils(context).pop(), - ); + final pickedFile = await MediaService.pickImage( + source: ImageSource.gallery); + File? file; + if (pickedFile != null && !kIsWeb) { + file = await ImageCropper().cropImage( + sourcePath: pickedFile.path, + androidUiSettings: + const AndroidUiSettings(toolbarTitle: 'برش تصویر'), + iosUiSettings: const IOSUiSettings( + title: 'برش تصویر', + doneButtonTitle: 'تایید', + cancelButtonTitle: 'بازگشت', + ), + compressQuality: 30, + ); - return; - } - } - if (pickedFile == null) { - await Future.delayed( - Duration.zero, - () => ActionSheetUtils(context).pop(), - ); + if (file == null) { + await Future.delayed( + Duration.zero, + () => ActionSheetUtils(context).pop(), + ); - return; - } - state.file = kIsWeb - ? FilesModel(pickedFile.path, - name: pickedFile.name, - image: true, - audio: false) - : FilesModel(file!.path, - image: true, audio: false); - await Future.delayed( - Duration.zero, - () => ActionSheetUtils(context).pop(), - ); - }, - ), - // if (!kIsWeb && !Platform.isIOS) - if (historyState.bot!.attachmentType!.contains('audio')) - attachBtn( - title: "صوت", - icon: CupertinoIcons.music_note_2, - color: Colors.indigoAccent, - click: () async { - MediaService.onLoadingPickFile(context); + return; + } + } + if (pickedFile == null) { + await Future.delayed( + Duration.zero, + () => ActionSheetUtils(context).pop(), + ); - FilePickerResult? result = - await MediaService.pickAudioFile(); - if (result != null) { - if (kIsWeb) { - Uint8List? bytes = result.files.first - .bytes; // Access the bytes property - String? name = result.files.first.name; + return; + } + state.file = kIsWeb + ? FilesModel(pickedFile.path, + name: pickedFile.name, image: true, audio: false) + : FilesModel(file!.path, image: true, audio: false); + await Future.delayed( + Duration.zero, + () => ActionSheetUtils(context).pop(), + ); + openAttach = !openAttach; - // final blob = html.Blob([bytes]); - // final blobUrl = - // html.Url.createObjectUrlFromBlob(blob); - - state.file = FilesModel( - "", // No need for a file path on web - name: name, - bytes: bytes, - audio: true, - image: false, - ); - } else { - state.file = FilesModel(result.files.single.path!, - audio: true, image: false); - } - } - await Future.delayed( - Duration.zero, - () => ActionSheetUtils(context).pop(), - ); - }, - ) - ], + state.update(); + }, ), + ), + // if (!kIsWeb && !Platform.isIOS) + if (historyState.bot!.attachmentType!.contains('audio')) + MessageBarBtn( + enable: true, + icon: CupertinoIcons.music_note_2, + click: () async { + MediaService.onLoadingPickFile(context); + + FilePickerResult? result = await MediaService.pickAudioFile(); + if (result != null) { + if (kIsWeb) { + Uint8List? bytes = + result.files.first.bytes; // Access the bytes property + String? name = result.files.first.name; + + final blob = html.Blob([bytes]); + final blobUrl = html.Url.createObjectUrlFromBlob(blob); + + state.file = FilesModel( + blobUrl, // No need for a file path on web + name: name, + bytes: bytes, + audio: true, + image: false, + ); + } else { + state.file = FilesModel(result.files.single.path!, + audio: true, image: false); + } + } + await Future.delayed( + Duration.zero, + () => ActionSheetUtils(context).pop(), + ); + openAttach = !openAttach; + + state.update(); + }, + ) + ], )); } - InkWell attachBtn( - {required final String title, - required final IconData icon, - final Color? color, - final Function()? click}) { - final state = context.read(); - return InkWell( - onTap: () async { - await click?.call(); + // InkWell attachBtn( + // {required final String title, + // required final IconData icon, + // final Color? color, + // final Function()? click}) { + // final state = context.read(); + // return InkWell( + // onTap: () async { + // await click?.call(); - state.update(); - }, - child: Column( - children: [ - Container( - width: 40, - height: 40, - decoration: BoxDecoration(shape: BoxShape.circle, color: color), - padding: const EdgeInsets.all(0.8), - margin: const EdgeInsets.symmetric(horizontal: 12), - child: Icon( - icon, - size: 20, - color: Theme.of(context).colorScheme.white, - )), - const SizedBox( - height: 4, - ), - DidvanText( - title, - fontSize: 12, - ) - ], - ), - ); - } + // state.update(); + // }, + // child: Column( + // children: [ + // Container( + // width: 40, + // height: 40, + // decoration: BoxDecoration(shape: BoxShape.circle, color: color), + // padding: const EdgeInsets.all(0.8), + // margin: const EdgeInsets.symmetric(horizontal: 12), + // child: Icon( + // icon, + // size: 20, + // color: Theme.of(context).colorScheme.white, + // )), + // const SizedBox( + // height: 4, + // ), + // DidvanText( + // title, + // fontSize: 12, + // ) + // ], + // ), + // ); + // } Widget audioContainer() { final state = context.watch(); @@ -711,6 +856,7 @@ class _AiMessageBarState extends State { borderRadius: DesignConfig.mediumBorderRadius, color: Theme.of(context).colorScheme.border, ), + margin: const EdgeInsets.fromLTRB(4, 4, 4, 8), padding: const EdgeInsets.fromLTRB(12, 8, 12, 8), child: Row( children: [ diff --git a/lib/views/ai/widgets/ai_message_bar_ios.dart b/lib/views/ai/widgets/ai_message_bar_ios.dart new file mode 100644 index 0000000..1f6f6b5 --- /dev/null +++ b/lib/views/ai/widgets/ai_message_bar_ios.dart @@ -0,0 +1,791 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:didvan/config/design_config.dart'; +import 'package:didvan/config/theme_data.dart'; +import 'package:didvan/constants/app_icons.dart'; +import 'package:didvan/models/ai/bots_model.dart'; +import 'package:didvan/models/ai/chats_model.dart'; +import 'package:didvan/models/ai/files_model.dart'; +import 'package:didvan/models/ai/messages_model.dart'; +import 'package:didvan/services/media/media.dart'; +import 'package:didvan/services/media/voice.dart'; +import 'package:didvan/utils/action_sheet.dart'; +import 'package:didvan/utils/date_time.dart'; +import 'package:didvan/views/ai/ai_chat_state.dart'; +import 'package:didvan/views/ai/history_ai_chat_state.dart'; +import 'package:didvan/views/ai/widgets/audio_wave.dart'; +import 'package:didvan/views/ai/widgets/message_bar_btn.dart'; +import 'package:didvan/views/widgets/animated_visibility.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/marquee_text.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:get/get.dart'; +import 'package:image_cropper/image_cropper.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:persian_number_utility/persian_number_utility.dart'; +import 'package:provider/provider.dart'; +import 'package:record/record.dart'; + +import 'package:path/path.dart' as p; + +class AiMessageBarIOS extends StatefulWidget { + final BotsModel bot; + const AiMessageBarIOS({ + super.key, + required this.bot, + }); + + @override + State createState() => _AiMessageBarIOSState(); +} + +class _AiMessageBarIOSState extends State { + final ValueNotifier messageText = ValueNotifier(''); + bool openAttach = false; + String? path; + late HistoryAiChatState historyState = context.read(); + + @override + void initState() { + super.initState(); + } + + final record = AudioRecorder(); + + @override + void dispose() { + super.dispose(); + record.dispose(); + try { + _timer.cancel(); + } catch (e) { + e.printError(); + } + } + + late Timer _timer; + final ValueNotifier _countTimer = ValueNotifier(Duration.zero); + void startTimer() { + const oneSec = Duration(seconds: 1); + _timer = Timer.periodic( + oneSec, + (Timer timer) { + _countTimer.value = Duration(seconds: _countTimer.value.inSeconds + 1); + }, + ); + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, state, child) { + return Container( + padding: const EdgeInsets.fromLTRB(12, 24, 12, 0).copyWith( + top: (state.file != null && !state.file!.isRecorded) ? 0 : 24), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.background, + ), + child: Column( + children: [ + if (state.file != null && !(state.file!.isRecorded)) + fileContainer(), + Stack( + children: [ + Container( + // height: 50, + decoration: BoxDecoration( + boxShadow: DesignConfig.defaultShadow, + color: Theme.of(context).colorScheme.surface, + border: Border.all( + color: Theme.of(context).colorScheme.border), + borderRadius: DesignConfig.highBorderRadius), + child: Row( + children: [ + const SizedBox( + width: 8, + ), + Expanded( + child: StreamBuilder( + stream: record.onStateChanged(), + builder: (context, snapshot) { + return ValueListenableBuilder( + valueListenable: messageText, + builder: (context, value, child) { + return Row( + crossAxisAlignment: + CrossAxisAlignment.end, + children: [ + Padding( + padding: const EdgeInsets.only( + bottom: 8.0), + child: ( + // !kIsWeb && + snapshot.hasData && + snapshot.data! != + RecordState.stop) + ? MessageBarBtn( + enable: true, + icon: DidvanIcons + .stop_circle_solid, + click: () async { + path = + await record.stop(); + + Duration? duration = + await VoiceService + .getDuration( + src: path ?? + ''); + + state.file = FilesModel( + path.toString(), + name: + '${DateTime.now().millisecondsSinceEpoch ~/ 1000}.m4a', + isRecorded: true, + audio: true, + image: false, + duration: duration); + _timer.cancel(); + + state.update(); + }, + ) + : + // (!kIsWeb && + // !Platform + // .isIOS) && + widget.bot.attachmentType! + .contains( + 'audio') && + value.isEmpty && + state.file == null && + widget.bot.attachment != + 0 + ? MessageBarBtn( + enable: true, + icon: DidvanIcons + .mic_regular, + click: () async { + if (await record + .hasPermission()) { + try { + String path = + '${DateTime.now().millisecondsSinceEpoch ~/ 1000}.m4a'; + // + if (!kIsWeb) { + Directory? + downloadDir = + await getApplicationDocumentsDirectory(); + path = p.join( + downloadDir + .path, + '${DateTime.now().millisecondsSinceEpoch ~/ 1000}.m4a'); + } + setState( + () { + openAttach = + false; + }, + ); + record.start( + const RecordConfig(), + path: path); + startTimer(); + } catch (e) { + if (kDebugMode) { + print( + 'Error starting recording: $e'); + } + } + } + }, + ) + : MessageBarBtn( + enable: (state + .file != + null && + state.file! + .isRecorded) || + (widget.bot + .attachment == + 1) || + value.isNotEmpty, + icon: DidvanIcons + .send_light, + click: () async { + if ((state.file == + null || + !state.file! + .isRecorded) && + (widget.bot + .attachment != + 1) && + value.isEmpty) { + return; + } + + if (state.messages + .isNotEmpty && + DateTime.parse(state + .messages + .last + .dateTime) + .toPersianDateStr() + .contains(DateTime.parse(DateTime + .now() + .subtract( + const Duration(minutes: 210)) + .toIso8601String()) + .toPersianDateStr())) { + state.messages.last + .prompts + .add(Prompts( + error: false, + text: state + .message.text, + file: state + .file?.path, + fileName: state + .file + ?.basename, + fileLocal: + state.file, + finished: true, + role: 'user', + createdAt: DateTime + .now() + .subtract( + const Duration( + minutes: + 210)) + .toIso8601String(), + )); + } else { + state.messages.add(MessageModel( + dateTime: DateTime + .now() + .subtract(const Duration( + minutes: + 210)) + .toIso8601String(), + prompts: [ + Prompts( + error: + false, + text: state + .message + .text, + finished: + true, + file: state + .file + ?.path, + fileName: state + .file + ?.basename, + fileLocal: + state + .file, + role: + 'user', + createdAt: DateTime + .now() + .subtract(const Duration( + minutes: + 210)) + .toIso8601String(), + ) + ])); + } + state.message.clear(); + messageText.value = + state + .message.text; + await state + .postMessage( + widget.bot); + }, + ), + ), + Expanded( + child: Padding( + padding: + const EdgeInsets.symmetric( + horizontal: 8.0, + ), + child: snapshot.hasData && + (snapshot.data! == + RecordState + .record || + snapshot.data! == + RecordState.pause) + ? Padding( + padding: + const EdgeInsets.all( + 8.0), + child: Row( + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, + children: [ + Directionality( + textDirection: + TextDirection + .ltr, + child: Row( + mainAxisAlignment: + MainAxisAlignment + .center, + children: List.generate( + 4, + (index) => snapshot.data! == RecordState.pause + ? Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: List.generate( + 8, + (index) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 1.0, vertical: 12), + child: Container( + width: 3, + height: 8, + decoration: BoxDecoration(color: Theme.of(context).colorScheme.primary.withOpacity(0.4)), + ), + )), + ) + : SpinKitWave( + color: + Theme.of(context).colorScheme.primary.withOpacity(0.4), + size: + 32, + itemCount: + 10, + ))), + ), + ValueListenableBuilder< + Duration>( + valueListenable: + _countTimer, + builder: (context, + value, + child) => + DidvanText(DateTimeUtils + .normalizeTimeDuration( + value)), + ), + ], + ), + ) + : state.file != null && + state.file!.isRecorded + ? audioContainer() + : Form( + child: TextFormField( + textInputAction: + TextInputAction + .newline, + style: + Theme.of(context) + .textTheme + .bodyMedium, + minLines: 1, + maxLines: + 6, // Set this + // expands: true, // + // keyboardType: TextInputType.text, + keyboardType: + TextInputType + .multiline, + controller: + state.message, + + enabled: !(state + .file != + null && + widget.bot + .attachment == + 1), + + decoration: + InputDecoration( + border: InputBorder + .none, + hintText: + 'بنویسید...', + hintStyle: Theme.of( + context) + .textTheme + .bodySmall! + .copyWith( + color: Theme.of( + context) + .colorScheme + .disabledText), + suffixIcon: state + .isEdite + ? InkWell( + onTap: () { + state.isEdite = + false; + state + .update(); + }, + child: const Icon( + DidvanIcons + .close_circle_solid), + ) + : const SizedBox(), + ), + + onChanged: (value) { + messageText.value = + value; + state.update(); + }, + )), + ), + ), + if (snapshot.hasData) + Padding( + padding: const EdgeInsets.only( + bottom: 8.0), + child: snapshot.data! == + RecordState.record + ? MessageBarBtn( + enable: false, + icon: DidvanIcons + .pause_solid, + click: () async { + await record.pause(); + _timer.cancel(); + }, + ) + : snapshot.data! == + RecordState.pause + ? MessageBarBtn( + enable: false, + icon: DidvanIcons + .play_solid, + click: () async { + await record + .resume(); + startTimer(); + }, + ) + : const SizedBox(), + ), + const SizedBox( + width: 8, + ), + Padding( + padding: const EdgeInsets.only( + bottom: 8.0), + child: attachmentLayout( + state, context), + ), + if (!snapshot.hasData || + (snapshot.hasData && + snapshot.data == + RecordState.stop)) + Padding( + padding: + const EdgeInsets.fromLTRB( + 12, 0, 12, 8), + child: state.file != null || + openAttach + ? MessageBarBtn( + click: () { + if (openAttach) { + setState(() { + openAttach = + !openAttach; + }); + return; + } + state.file = null; + _countTimer.value = + Duration.zero; + state.update(); + }, + enable: false, + icon: DidvanIcons + .close_solid) + : widget.bot.attachmentType! + .isNotEmpty + ? MessageBarBtn( + click: () { + setState(() { + openAttach = + !openAttach; + }); + }, + enable: false, + icon: Icons + .attach_file_rounded, + ) + : const SizedBox(), + ), + ], + ); + }); + }), + ), + ], + ), + ), + if (state.onResponsing) + Positioned.fill( + child: Container( + decoration: BoxDecoration( + color: Theme.of(context) + .colorScheme + .focused + .withOpacity(0.5)), + child: Center( + child: SpinKitThreeBounce( + color: Theme.of(context).colorScheme.primary, + size: 32, + ), + ), + ), + ) + ], + ), + MediaQuery.of(context).viewInsets.bottom == 0 + ? const Padding( + padding: EdgeInsets.fromLTRB(8, 8, 8, 4), + child: DidvanText( + 'مدل‌های هوش مصنوعی می‌توانند اشتباه کنند، صحت اطلاعات مهم را بررسی کنید و از وارد کردن اطلاعات حساس بپرهیزید.', + fontSize: 12, + ), + ) + : const SizedBox( + height: 12, + ), + ], + ), + ); + }, + ); + } + + Widget audioContainer() { + final state = context.watch(); + + return SizedBox( + width: MediaQuery.sizeOf(context).width, + child: AudioWave( + file: state.file!.path, + loadingPaddingSize: 8.0, + totalDuration: _countTimer.value, + ), + ); + } + + AnimatedVisibility attachmentLayout(AiChatState state, BuildContext context) { + return AnimatedVisibility( + isVisible: openAttach, + fadeMode: FadeMode.horizontal, + duration: DesignConfig.lowAnimationDuration, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (historyState.bot!.attachmentType!.contains('pdf')) + MessageBarBtn( + enable: true, + icon: CupertinoIcons.doc_fill, + click: () async { + MediaService.onLoadingPickFile(context); + FilePickerResult? result = await MediaService.pickPdfFile(); + if (result != null) { + if (kIsWeb) { + Uint8List? bytes = + result.files.first.bytes; // Access the bytes property + String? name = result.files.first.name; + + // Store bytes and file name directly in your state or model + state.file = FilesModel( + '', // No need for a file path on web + name: name, + bytes: bytes, + audio: false, + image: false, + ); + } else { + state.file = FilesModel(result.files.single.path!, + audio: false, image: false); + } + } + Future.delayed( + Duration.zero, + () => ActionSheetUtils(context).pop(), + ); + openAttach = !openAttach; + + state.update(); + }, + ), + if (historyState.bot!.attachmentType!.contains('image')) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: MessageBarBtn( + enable: true, + icon: CupertinoIcons.photo, + click: () async { + MediaService.onLoadingPickFile(context); + + final pickedFile = await MediaService.pickImage( + source: ImageSource.gallery); + File? file; + if (pickedFile != null && !kIsWeb) { + file = await ImageCropper().cropImage( + sourcePath: pickedFile.path, + androidUiSettings: + const AndroidUiSettings(toolbarTitle: 'برش تصویر'), + iosUiSettings: const IOSUiSettings( + title: 'برش تصویر', + doneButtonTitle: 'تایید', + cancelButtonTitle: 'بازگشت', + ), + compressQuality: 30, + ); + + if (file == null) { + await Future.delayed( + Duration.zero, + () => ActionSheetUtils(context).pop(), + ); + + return; + } + } + if (pickedFile == null) { + await Future.delayed( + Duration.zero, + () => ActionSheetUtils(context).pop(), + ); + + return; + } + state.file = kIsWeb + ? FilesModel(pickedFile.path, + name: pickedFile.name, image: true, audio: false) + : FilesModel(file!.path, image: true, audio: false); + await Future.delayed( + Duration.zero, + () => ActionSheetUtils(context).pop(), + ); + openAttach = !openAttach; + + state.update(); + }, + ), + ), + // if (!kIsWeb && !Platform.isIOS) + if (historyState.bot!.attachmentType!.contains('audio')) + MessageBarBtn( + enable: true, + icon: CupertinoIcons.music_note_2, + click: () async { + MediaService.onLoadingPickFile(context); + + FilePickerResult? result = await MediaService.pickAudioFile(); + if (result != null) { + if (kIsWeb) { + Uint8List? bytes = + result.files.first.bytes; // Access the bytes property + String? name = result.files.first.name; + + state.file = FilesModel( + '', // No need for a file path on web + name: name, + bytes: bytes, + audio: true, + image: false, + ); + } else { + state.file = FilesModel(result.files.single.path!, + audio: true, image: false); + } + } + await Future.delayed( + Duration.zero, + () => ActionSheetUtils(context).pop(), + ); + openAttach = !openAttach; + + state.update(); + }, + ) + ], + )); + } + + Widget fileContainer() { + final state = context.watch(); + return Container( + decoration: BoxDecoration( + borderRadius: DesignConfig.mediumBorderRadius, + color: Theme.of(context).colorScheme.border, + ), + margin: const EdgeInsets.fromLTRB(4, 4, 4, 8), + padding: const EdgeInsets.fromLTRB(12, 8, 12, 8), + child: Row( + children: [ + state.file != null && state.file!.isImage() + ? SizedBox( + width: 32, + height: 42, + child: ClipRRect( + borderRadius: DesignConfig.lowBorderRadius, + child: state.file!.isNetwork() + ? Image.network( + state.file!.path, + fit: BoxFit.cover, + ) + : Image.file( + state.file!.main, + fit: BoxFit.cover, + ))) + : const Icon(Icons.file_copy), + const SizedBox( + width: 12, + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: 24, + child: MarqueeText( + text: state.file != null ? state.file!.basename : '', + style: const TextStyle(fontSize: 14), + stop: const Duration(seconds: 3), + ), + ), + if (state.file != null && !kIsWeb) + FutureBuilder( + future: state.file!.main.length(), + builder: (context, snapshot) { + if (!snapshot.hasData) { + return const SizedBox(); + } + return DidvanText( + 'File Size ${(snapshot.data! / 1000).round()} KB', + fontSize: 12, + ); + }) + ], + ), + ) + ], + ), + ); + } +} diff --git a/lib/views/ai/widgets/audio_wave.dart b/lib/views/ai/widgets/audio_wave.dart index 6045b3a..dcc77a2 100644 --- a/lib/views/ai/widgets/audio_wave.dart +++ b/lib/views/ai/widgets/audio_wave.dart @@ -15,16 +15,14 @@ import 'package:just_audio/just_audio.dart'; class AudioWave extends StatefulWidget { final String file; - final Uint8List? bytes; final double loadingPaddingSize; final Duration? totalDuration; - const AudioWave( - {Key? key, - required this.file, - this.loadingPaddingSize = 0, - this.totalDuration, - this.bytes}) - : super(key: key); + const AudioWave({ + Key? key, + required this.file, + this.loadingPaddingSize = 0, + this.totalDuration, + }) : super(key: key); @override _AudioWaveState createState() => _AudioWaveState(); @@ -110,8 +108,7 @@ class _AudioWaveState extends State { ? DidvanIcons.pause_solid : DidvanIcons.play_solid, click: () async { - await VoiceService.voiceHelper( - src: widget.file, bytes: widget.bytes); + await VoiceService.voiceHelper(src: widget.file); }, ); }), diff --git a/lib/views/direct/direct_state.dart b/lib/views/direct/direct_state.dart index 96e83bb..9cbd0f4 100644 --- a/lib/views/direct/direct_state.dart +++ b/lib/views/direct/direct_state.dart @@ -281,17 +281,15 @@ class DirectState extends CoreProvier { } } else { final Uint8List uploadFile = kIsWeb - ? (await http.get(Uri.parse(path!))).bodyBytes + ? (await http.get(Uri.parse(path!.replaceAll('%3A', ':')))).bodyBytes : await File(path!).readAsBytes(); - path = null; - await service.multipartBytes( file: uploadFile, method: 'POST', fieldName: 'audio', fileName: 'voice-message', - mediaExtension: 'm4a', + mediaExtension: 'mp3', mediaFormat: 'audio', ); @@ -304,8 +302,8 @@ class DirectState extends CoreProvier { for (var i = 0; i < messages.length; i++) { _addToDailyGrouped(messages[i]); } - // update(); } + path = null; } notifyListeners(); } diff --git a/lib/views/direct/widgets/message.dart b/lib/views/direct/widgets/message.dart index f13ed82..7d37f83 100644 --- a/lib/views/direct/widgets/message.dart +++ b/lib/views/direct/widgets/message.dart @@ -83,9 +83,9 @@ class Message extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ if (message.text != null) DidvanText(message.text!), - if (message.audio != null || message.audioFile != null) + if (message.audio != null) AudioWave( - file: message.audio ?? message.audioFile!.path, + file: message.audio!, totalDuration: message.duration != null ? Duration(seconds: message.duration!) : null, diff --git a/lib/views/widgets/skeleton_image.dart b/lib/views/widgets/skeleton_image.dart index f6912de..fce59a8 100644 --- a/lib/views/widgets/skeleton_image.dart +++ b/lib/views/widgets/skeleton_image.dart @@ -32,8 +32,9 @@ class SkeletonImage extends StatelessWidget { child: ClipRRect( borderRadius: borderRadius ?? BorderRadius.zero, child: CachedNetworkImage( - errorWidget: (context, url, error) => - const Text("مشکلی پیش آمده است"), + errorWidget: (context, url, error) { + return const Text("مشکلی پیش آمده است"); + }, errorListener: (value) {}, fit: BoxFit.cover, imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet, @@ -41,8 +42,8 @@ class SkeletonImage extends StatelessWidget { width: width, height: height, imageUrl: imageUrl.startsWith('http') - ? imageUrl - : RequestHelper.baseUrl + imageUrl, + ? imageUrl.replaceAll('\n', '') + : RequestHelper.baseUrl + imageUrl.replaceAll('\n', ''), placeholder: (context, _) => ShimmerPlaceholder( width: pWidth, height: pHeight, diff --git a/pubspec.lock b/pubspec.lock index 0db76d1..efbbb59 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1037,6 +1037,62 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.0" + record: + dependency: "direct main" + description: + name: record + sha256: "4a5cf4d083d1ee49e0878823c4397d073f8eb0a775f31215d388e2bc47a9e867" + url: "https://pub.dev" + source: hosted + version: "5.1.2" + record_android: + dependency: transitive + description: + name: record_android + sha256: d7af0b3119725a0f561817c72b5f5eca4d7a76d441deef519ae04e4824c0734c + url: "https://pub.dev" + source: hosted + version: "1.2.6" + record_darwin: + dependency: transitive + description: + name: record_darwin + sha256: fe90d302acb1f3cee1ade5df9c150ca5cee33b48d8cdf1cf433bf577d7f00134 + url: "https://pub.dev" + source: hosted + version: "1.1.2" + record_linux: + dependency: transitive + description: + name: record_linux + sha256: "74d41a9ebb1eb498a38e9a813dd524e8f0b4fdd627270bda9756f437b110a3e3" + url: "https://pub.dev" + source: hosted + version: "0.7.2" + record_platform_interface: + dependency: transitive + description: + name: record_platform_interface + sha256: "11f8b03ea8a0e279b0e306571dbe0db0202c0b8e866495c9fa1ad2281d5e4c15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + record_web: + dependency: transitive + description: + name: record_web + sha256: "0ef370d1e6553ad33c39dd03103b374e7861f3518b0533e64c94d73f988a5ffa" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + record_windows: + dependency: transitive + description: + name: record_windows + sha256: e653555aa3fda168aded7c34e11bd82baf0c6ac84e7624553def3c77ffefd36f + url: "https://pub.dev" + source: hosted + version: "1.0.3" rive: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index b2f62bf..233402b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -51,7 +51,7 @@ dependencies: carousel_slider: ^4.0.0 flutter_vibrate: ^1.3.0 universal_html: ^2.0.8 - # record: ^5.1.2 + record: ^5.1.2 persian_datetime_picker: ^2.6.0 persian_number_utility: ^1.1.1