diff --git a/lib/services/media/media.dart b/lib/services/media/media.dart index 0d175d9..7d6c40e 100644 --- a/lib/services/media/media.dart +++ b/lib/services/media/media.dart @@ -1,4 +1,7 @@ +import 'dart:io'; + import 'package:didvan/constants/assets.dart'; +import 'package:didvan/models/ai/files_model.dart'; import 'package:didvan/models/requests/studio.dart'; import 'package:didvan/models/studio_details_data.dart'; import 'package:didvan/models/view/action_sheet_data.dart'; @@ -11,7 +14,12 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:image_picker/image_picker.dart'; import 'package:just_audio/just_audio.dart'; +import 'package:mime/mime.dart'; +import 'package:path/path.dart' as p; import 'package:file_picker/file_picker.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:http/http.dart' as http; +import 'package:universal_html/html.dart' as html; class MediaService { static final audioPlayer = AudioPlayer(); @@ -141,6 +149,42 @@ class MediaService { } } + static Future downloadFile(String url) async { + final basename = p.basename(url).split('?accessToken=').first; + Directory? dir; + try { + if (Platform.isIOS) { + dir = await getApplicationDocumentsDirectory(); // for iOS + } else { + dir = Directory('/storage/emulated/0/Download/'); // for android + if (!await dir.exists()) dir = (await getExternalStorageDirectory())!; + } + } catch (err) { + print("Cannot get download folder path $err"); + } + String path = "${dir?.path}$basename"; + + File file = File(path); + + try { + final http.Response audioResponse = await http.get(Uri.parse(url)); + final bytes = audioResponse.bodyBytes; + file.writeAsBytes(bytes); + } catch (e) { + print("Exception$e"); + return null; + } + + return path; + } + + static String downloadFileFromWeb(String url) { + html.AnchorElement anchorElement = html.AnchorElement(href: url); + anchorElement.download = url; + anchorElement.click(); + return anchorElement.pathname!; + } + static onLoadingPickFile(BuildContext context) { ActionSheetUtils(context).openDialog( barrierDismissible: false, diff --git a/lib/views/ai/ai_chat_page.dart b/lib/views/ai/ai_chat_page.dart index 6f83d27..6c4aef5 100644 --- a/lib/views/ai/ai_chat_page.dart +++ b/lib/views/ai/ai_chat_page.dart @@ -16,6 +16,9 @@ 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/services/media/media.dart'; +import 'package:didvan/services/network/request.dart'; +import 'package:didvan/services/network/request_helper.dart'; import 'package:didvan/utils/action_sheet.dart'; import 'package:didvan/utils/date_time.dart'; import 'package:didvan/views/ai/ai_chat_state.dart'; @@ -27,6 +30,7 @@ import 'package:didvan/views/widgets/didvan/icon_button.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/marquee_text.dart'; import 'package:didvan/views/widgets/skeleton_image.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; @@ -313,13 +317,14 @@ class _AiChatPageState extends State { bottomSheet: Column( mainAxisSize: MainAxisSize.min, children: [ - Platform.isIOS - ? AiMessageBarIOS( - bot: widget.args.bot, - ) - : AiMessageBar( - bot: widget.args.bot, - ), + // Platform.isIOS + // ? AiMessageBarIOS( + // bot: widget.args.bot, + // ) + // : + AiMessageBar( + bot: widget.args.bot, + ), ], )), ), @@ -590,6 +595,39 @@ class _AiChatPageState extends State { ), ), ), + if (message.file != null) + Padding( + padding: const EdgeInsets.all(8.0), + child: InkWell( + onTap: () async { + final url = + '${RequestHelper.baseUrl + message.file.toString()}?accessToken=${RequestService.token}'; + final download = kIsWeb + ? MediaService + .downloadFileFromWeb(url) + : await MediaService.downloadFile( + url); + AlertData alertData = AlertData( + message: 'دانلود موفقیت آمیز بود', + aLertType: ALertType.success); + if (download == null) { + alertData = AlertData( + message: + 'دانلود موفقیت آمیز نبود', + aLertType: ALertType.error); + } + ActionSheetUtils(context) + .showAlert(alertData); + }, + child: Icon( + DidvanIcons.download_solid, + size: 18, + color: Theme.of(context) + .colorScheme + .focusedBorder, + ), + ), + ), if (message.error != null && message.error!) Padding( padding: const EdgeInsets.all(8.0), @@ -613,33 +651,41 @@ class _AiChatPageState extends State { ), ), ), - Padding( - padding: const EdgeInsets.all(8.0), - child: InkWell( - onTap: () async { - await Clipboard.setData(ClipboardData( - text: state.messages[mIndex] - .prompts[index].text - .toString())); - Future.delayed( - Duration.zero, - () => ActionSheetUtils(context) - .showAlert(AlertData( - message: - "متن با موفقیت کپی شد", - aLertType: - ALertType.success)), - ); - }, - child: Icon( - DidvanIcons.copy_regular, - size: 18, - color: Theme.of(context) - .colorScheme - .focusedBorder, + if (state.messages[mIndex].prompts[index] + .text != + null || + (state.messages[mIndex].prompts[index] + .text != + null && + state.messages[mIndex].prompts[index] + .text!.isNotEmpty)) + Padding( + padding: const EdgeInsets.all(8.0), + child: InkWell( + onTap: () async { + await Clipboard.setData(ClipboardData( + text: state.messages[mIndex] + .prompts[index].text + .toString())); + Future.delayed( + Duration.zero, + () => ActionSheetUtils(context) + .showAlert(AlertData( + message: + "متن با موفقیت کپی شد", + aLertType: + ALertType.success)), + ); + }, + child: Icon( + DidvanIcons.copy_regular, + size: 18, + color: Theme.of(context) + .colorScheme + .focusedBorder, + ), ), ), - ), Padding( padding: const EdgeInsets.all(8.0), child: InkWell( diff --git a/lib/views/ai/ai_chat_state.dart b/lib/views/ai/ai_chat_state.dart index b534842..3d27dbf 100644 --- a/lib/views/ai/ai_chat_state.dart +++ b/lib/views/ai/ai_chat_state.dart @@ -143,7 +143,7 @@ class AiChatState extends CoreProvier { messages.last.prompts.add(Prompts( finished: false, error: false, - text: '...', + text: '', role: 'bot', createdAt: DateTime.now() .subtract(const Duration(minutes: 210)) diff --git a/lib/views/ai/widgets/ai_message_bar.dart b/lib/views/ai/widgets/ai_message_bar.dart index 6c3cd3c..7aaa2ec 100644 --- a/lib/views/ai/widgets/ai_message_bar.dart +++ b/lib/views/ai/widgets/ai_message_bar.dart @@ -281,12 +281,6 @@ class _AiMessageBarState extends State { .colorScheme .focused .withOpacity(0.5)), - child: Center( - child: SpinKitThreeBounce( - color: Theme.of(context).colorScheme.primary, - size: 32, - ), - ), ), ) ], @@ -310,6 +304,7 @@ class _AiMessageBarState extends State { Row recorderAndTextMessageHandler(BuildContext context, AiChatState state) { return Row( + crossAxisAlignment: CrossAxisAlignment.end, children: _mRecorder!.isPaused ? [ Expanded( @@ -412,28 +407,33 @@ class _AiMessageBarState extends State { }, )), ), - 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(); - }, - ), - ) + Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: Row(children: [ + 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(); + }, + ), + ) + ]), + ) ], ); }