diff --git a/lib/models/message_data/message_data.dart b/lib/models/message_data/message_data.dart index 7be1f65..dfc3413 100644 --- a/lib/models/message_data/message_data.dart +++ b/lib/models/message_data/message_data.dart @@ -15,7 +15,7 @@ class MessageData { final RadarAttachment? radar; final File? audioFile; final int? audioDuration; - final double? duration; + final int? duration; MessageData({ required this.id, @@ -60,4 +60,32 @@ class MessageData { 'news': news?.toJson(), 'radar': radar?.toJson(), }; + + MessageData copyWith({ + int? id, + String? text, + String? audio, + bool? writedByAdmin, + bool? readed, + String? createdAt, + NewsAttachment? news, + RadarAttachment? radar, + File? audioFile, + int? audioDuration, + int? duration, + }) { + return MessageData( + id: id ?? this.id, + text: text ?? this.text, + audio: audio ?? this.audio, + writedByAdmin: writedByAdmin ?? this.writedByAdmin, + readed: readed ?? this.readed, + news: news ?? this.news, + radar: radar ?? this.radar, + createdAt: createdAt ?? this.createdAt, + audioFile: audioFile ?? this.audioFile, + audioDuration: audioDuration ?? this.audioDuration, + duration: duration ?? this.duration, + ); + } } diff --git a/lib/views/ai/ai.dart b/lib/views/ai/ai.dart index e5a2274..861a1f8 100644 --- a/lib/views/ai/ai.dart +++ b/lib/views/ai/ai.dart @@ -154,7 +154,7 @@ class _AiState extends State { const Padding( padding: EdgeInsets.symmetric(horizontal: 20.0), child: Text( - "به هوشان, هوش مصنوعی دیدوان خوش آمدید. \nبرای شروع گفتگو پیام مورد نظر خود را در کادر زیر بنویسید.", + "به هوشان؛ هوش مصنوعی دیدوان خوش آمدید. \nبرای شروع گفتگو پیام مورد نظر خود را در کادر زیر بنویسید.", textAlign: TextAlign.center, ), ) diff --git a/lib/views/ai/ai_chat_page.dart b/lib/views/ai/ai_chat_page.dart index e3f32ea..e30a419 100644 --- a/lib/views/ai/ai_chat_page.dart +++ b/lib/views/ai/ai_chat_page.dart @@ -157,7 +157,7 @@ class _AiChatPageState extends State { height: 12, ), const DidvanText( - 'دوست دارید هوشان، چه چیزهایی را درباره شما بداند تا بتواند پاسخ‌های بهتری ارائه دهد؟ \nدستورات و اطلاعات ارائه شما، بر روی تمامی پیام‌هایی که از این به بعد ارسال می‌کنید، اعمال خواهد شد.'), + 'دوست دارید هوشان چه چیزهایی را درباره شما بداند تا بتواند پاسخ‌های بهتری ارائه دهد؟ '), const SizedBox( height: 12, ), @@ -254,7 +254,7 @@ class _AiChatPageState extends State { const Padding( padding: EdgeInsets.symmetric(horizontal: 20.0), child: Text( - "به هوشان, هوش مصنوعی دیدوان خوش آمدید. \nبرای شروع گفتگو پیام مورد نظر خود را در کادر زیر بنویسید.", + "به هوشان؛ هوش مصنوعی دیدوان خوش آمدید. \nبرای شروع گفتگو پیام مورد نظر خود را در کادر زیر بنویسید.", textAlign: TextAlign.center, ), ) diff --git a/lib/views/ai/widgets/audio_wave.dart b/lib/views/ai/widgets/audio_wave.dart index 9f5b7be..68e42e7 100644 --- a/lib/views/ai/widgets/audio_wave.dart +++ b/lib/views/ai/widgets/audio_wave.dart @@ -38,7 +38,6 @@ class _AudioWaveState extends State { @override void initState() { super.initState(); - print(widget.file); WidgetsBinding.instance.addPostFrameCallback((_) async { if (widget.totalDuration == null) { await VoiceService.getDuration(src: widget.file).then((duration) { @@ -116,64 +115,60 @@ class _AudioWaveState extends State { if (!snapshot.hasData) { return const SizedBox(); } - return totalDuration == null || + if ((totalDuration == null || (totalDuration != null && - VoiceService.audioPlayer.playing && - VoiceService.audioPlayer.duration!.inSeconds != - totalDuration!.inSeconds && - VoiceService.src == widget.file) - ? Padding( - padding: EdgeInsets.symmetric( - vertical: widget.loadingPaddingSize), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: List.generate( - 5, - (index) => SpinKitWave( - color: Theme.of(context) - .colorScheme - .primary - .withOpacity(0.4), - size: 32, - itemCount: 10, - ))), - ) - : Padding( - padding: const EdgeInsets.fromLTRB(24, 12, 12, 0), - child: ProgressBar( - thumbColor: Theme.of(context).colorScheme.title, - progressBarColor: DesignConfig.isDark - ? Theme.of(context).colorScheme.title - : Theme.of(context).colorScheme.primary, - baseBarColor: - Theme.of(context).colorScheme.border, - bufferedBarColor: - Theme.of(context).colorScheme.splash, - total: totalDuration!, - progress: VoiceService.src == widget.file - ? snapshot.data ?? Duration.zero - : Duration.zero, - thumbRadius: 6, - barHeight: 3, - // timeLabelTextStyle: TextStyle( - // fontSize: showTimer ? null : 0, - // height: showTimer ? 3 : 0, - // color: - // Theme.of(context).colorScheme.text, - // fontFamily: - // DesignConfig.fontFamily.replaceAll( - // '-FA', - // '', - // ), - // ), - onSeek: (value) { - if (VoiceService.src == widget.file) { - VoiceService.audioPlayer.seek(Duration( - milliseconds: value.inMilliseconds)); - } - }, - ), - ); + (VoiceService.audioPlayer.duration != null && + VoiceService + .audioPlayer.duration!.inSeconds != + totalDuration!.inSeconds))) && + VoiceService.src == widget.file && + VoiceService.audioPlayer.playing) { + return Padding( + padding: EdgeInsets.symmetric( + vertical: widget.loadingPaddingSize), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate( + 5, + (index) => SpinKitWave( + color: Theme.of(context) + .colorScheme + .primary + .withOpacity(0.4), + size: 32, + itemCount: 10, + ))), + ); + } + return Padding( + padding: EdgeInsets.fromLTRB( + 24, 12, 12, totalDuration != null ? 0 : 12), + child: ProgressBar( + thumbColor: Theme.of(context).colorScheme.title, + progressBarColor: DesignConfig.isDark + ? Theme.of(context).colorScheme.title + : Theme.of(context).colorScheme.primary, + baseBarColor: Theme.of(context).colorScheme.border, + bufferedBarColor: Theme.of(context).colorScheme.splash, + total: totalDuration ?? Duration.zero, + progress: VoiceService.src == widget.file + ? snapshot.data ?? Duration.zero + : Duration.zero, + thumbRadius: 6, + barHeight: 3, + timeLabelTextStyle: TextStyle( + fontSize: totalDuration != null ? null : 0, + // height: totalDuration != null ? 3 : 0, + color: Theme.of(context).colorScheme.text, + fontFamily: DesignConfig.fontFamily), + onSeek: (value) { + if (VoiceService.src == widget.file) { + VoiceService.audioPlayer.seek( + Duration(milliseconds: value.inMilliseconds)); + } + }, + ), + ); }, ), ) diff --git a/lib/views/direct/direct_state.dart b/lib/views/direct/direct_state.dart index bb6048c..c618350 100644 --- a/lib/views/direct/direct_state.dart +++ b/lib/views/direct/direct_state.dart @@ -6,6 +6,7 @@ import 'package:didvan/models/message_data/news_attachment.dart'; import 'package:didvan/models/message_data/radar_attachment.dart'; import 'package:didvan/providers/core.dart'; import 'package:didvan/services/media/media.dart'; +import 'package:didvan/services/media/voice.dart'; import 'package:didvan/services/network/request.dart'; import 'package:didvan/services/network/request_helper.dart'; import 'package:flutter/foundation.dart'; @@ -25,7 +26,9 @@ class DirectState extends CoreProvier { NewsAttachment? replyNews; RadarAttachment? replyRadar; File? recordedFile; + Uint8List? recordedFileBytes; int? audioDuration; + String? path; bool isRecording = false; @@ -74,21 +77,18 @@ class DirectState extends CoreProvier { } Future stopRecording({required bool sendImidiately}) async { - final path = await _recorder.stop(); + path = await _recorder.stop(); isRecording = false; if (path == null) { notifyListeners(); return; } if (kIsWeb) { - // final uri = Uri.file(path); - // recordedFile = File.fromUri(uri); - final Uri audioUri = Uri.parse(path); + final Uri audioUri = Uri.parse(path!); final http.Response audioResponse = await http.get(audioUri); - final bytes = audioResponse.bodyBytes; - recordedFile = File.fromRawPath(bytes); + recordedFileBytes = audioResponse.bodyBytes; } else { - recordedFile = File(path); + recordedFile = File(path!); } if (sendImidiately) { await sendMessage(); @@ -121,7 +121,8 @@ class DirectState extends CoreProvier { } Future sendMessage() async { - if ((text == null || text!.isEmpty) && recordedFile == null) return; + if ((text == null || text!.isEmpty) && + (recordedFile == null && recordedFileBytes == null)) return; MediaService.audioPlayer.stop(); final body = {}; @@ -138,17 +139,31 @@ class DirectState extends CoreProvier { body.addAll({'newsId': replyNews!.id}); } + if (replyNews != null) { + body.addAll({'newsId': replyNews!.id}); + } + + if (path != null) { + final duration = await VoiceService.getDuration(src: path!); + if (duration != null) { + body.addAll({'duration': duration.inSeconds.toString()}); + } + } + final uploadFile = recordedFile; + final uploadFileBytes = recordedFileBytes; text = null; recordedFile = null; + recordedFileBytes = null; replyRadar = null; replyNews = null; + path = null; notifyListeners(); final service = RequestService(RequestHelper.sendDirectMessage(typeId), body: body); - if (uploadFile == null) { + if (uploadFile == null && uploadFileBytes == null) { await service.post(); if (service.isSuccess) { @@ -171,7 +186,7 @@ class DirectState extends CoreProvier { createdAt: DateTime.now().subtract(const Duration(minutes: 210)).toString(), text: text, - audio: null, + audio: path, audioFile: uploadFile, radar: replyRadar, news: replyNews, @@ -185,20 +200,33 @@ class DirectState extends CoreProvier { _addToDailyGrouped(messages[i]); } - await service.multipart( - file: Platform.isIOS - ? File(uploadFile.path.replaceAll('file://', '')) - : uploadFile, - method: 'POST', - fieldName: 'audio', - fileName: 'voice-message', - mediaExtension: 'm4a', - mediaFormat: 'audio', - ); + if (kIsWeb) { + await service.multipartBytes( + file: uploadFileBytes!, + method: 'POST', + fieldName: 'audio', + fileName: 'voice-message', + mediaExtension: 'm4a', + mediaFormat: 'audio', + ); + } else { + await service.multipart( + file: Platform.isIOS + ? File(uploadFile!.path.replaceAll('file://', '')) + : uploadFile, + method: 'POST', + fieldName: 'audio', + fileName: 'voice-message', + mediaExtension: 'm4a', + mediaFormat: 'audio', + ); + } if (service.isSuccess) { final message = service.result['message']; - messages[0].id = MessageData.fromJson(message).id; + final m = MessageData.fromJson(message); + messages[0] = m; + update(); } } } diff --git a/lib/views/direct/widgets/message.dart b/lib/views/direct/widgets/message.dart index 1d35e27..f13ed82 100644 --- a/lib/views/direct/widgets/message.dart +++ b/lib/views/direct/widgets/message.dart @@ -2,16 +2,13 @@ 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/message_data/message_data.dart'; -import 'package:didvan/services/media/voice.dart'; import 'package:didvan/utils/date_time.dart'; import 'package:didvan/views/ai/widgets/audio_wave.dart'; import 'package:didvan/views/direct/direct_state.dart'; -import 'package:didvan/views/direct/widgets/audio_widget.dart'; import 'package:didvan/views/widgets/didvan/divider.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/skeleton_image.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:provider/provider.dart'; import 'package:persian_number_utility/persian_number_utility.dart'; @@ -89,6 +86,9 @@ class Message extends StatelessWidget { if (message.audio != null || message.audioFile != null) AudioWave( file: message.audio ?? message.audioFile!.path, + totalDuration: message.duration != null + ? Duration(seconds: message.duration!) + : null, ), if (message.radar != null || message.news != null) const DidvanDivider(), diff --git a/lib/views/direct/widgets/message_box.dart b/lib/views/direct/widgets/message_box.dart index 3624160..c0906f5 100644 --- a/lib/views/direct/widgets/message_box.dart +++ b/lib/views/direct/widgets/message_box.dart @@ -1,9 +1,9 @@ import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/app_icons.dart'; +import 'package:didvan/views/ai/widgets/audio_wave.dart'; import 'package:didvan/views/ai/widgets/message_bar_btn.dart'; import 'package:didvan/views/direct/direct_state.dart'; -import 'package:didvan/views/direct/widgets/audio_widget.dart'; import 'package:didvan/views/widgets/didvan/icon_button.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:flutter/material.dart'; @@ -64,7 +64,9 @@ class MessageBox extends StatelessWidget { builder: (context, state, child) { if (state.isRecording) { return const _Recording(); - } else if (!state.isRecording && state.recordedFile != null) { + } else if (!state.isRecording && + (state.recordedFile != null || + state.recordedFileBytes != null)) { return const _RecordChecking(); } return const _Typing(); @@ -223,9 +225,8 @@ class _RecordChecking extends StatelessWidget { Expanded( child: Padding( padding: const EdgeInsets.symmetric(vertical: 8), - child: AudioWidget( - audioFile: state.recordedFile!, - id: 0, + child: AudioWave( + file: state.path!, // deleteClidk: () => state.deleteRecordedFile, ), ), diff --git a/lib/views/home/bookmarks/bookmarks.dart b/lib/views/home/bookmarks/bookmarks.dart index d5bb2c4..06ff760 100644 --- a/lib/views/home/bookmarks/bookmarks.dart +++ b/lib/views/home/bookmarks/bookmarks.dart @@ -154,7 +154,7 @@ class _BookmarksState extends State { : Column( children: [ DidvanText( - 'در قسمت رصدخانه من، تمامی محتواهایی که در قسمت‌های مختلف سوپراپلیکیشن دیدوان، بوکمارک (نشان‌دار) کرده‌اید، به تفکیک نمایش داده می‌شوند. هم‌چنین امکان درج یادداشت شخصی بصورت ضمیمه برای هر محتوا وجود دارد.', + 'در قسمت رصدخانه من، تمامی مطالبی که در قسمت‌های مختلف سوپراپلیکیشن دیدوان، بوکمارک (نشان‌دار) کرده‌اید، به تفکیک نمایش داده می‌شوند. هم‌چنین امکان درج یادداشت شخصی بصورت ضمیمه برای هر محتوا وجود دارد.', fontSize: 14, color: Theme.of(context).colorScheme.title, textAlign: TextAlign.justify, diff --git a/lib/views/profile/profile.dart b/lib/views/profile/profile.dart index 13fa527..d052917 100644 --- a/lib/views/profile/profile.dart +++ b/lib/views/profile/profile.dart @@ -348,7 +348,7 @@ class _ProfilePageState extends State { ), const SizedBox(height: 16), DidvanText( - 'نسخه نرم‌افزار: 3.3.0', + 'نسخه نرم‌افزار: 3.3.1', style: Theme.of(context).textTheme.bodySmall, ), ], diff --git a/lib/views/webview/web_view.dart b/lib/views/webview/web_view.dart index 962eea6..c6619b0 100644 --- a/lib/views/webview/web_view.dart +++ b/lib/views/webview/web_view.dart @@ -1,7 +1,8 @@ // ignore_for_file: library_private_types_in_public_api, deprecated_member_use import 'package:didvan/constants/assets.dart'; -import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:webview_flutter/webview_flutter.dart'; @@ -18,25 +19,33 @@ class _WebViewState extends State { bool loading = true; int progress = 0; + final Set> gestureRecognizers = { + Factory( + () => VerticalDragGestureRecognizer()) + }; + + void _onProgress(int progress) { + setState(() { + this.progress = progress; + }); + } + + void _onPageFinished(String url) async { + await Future.delayed(const Duration(seconds: 2)); + setState(() { + if (progress == 100) loading = false; + }); + } + @override void initState() { controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setNavigationDelegate( NavigationDelegate( - onProgress: (int progress) { - // Update loading bar. - setState(() { - this.progress = progress; - }); - }, + onProgress: _onProgress, onPageStarted: (String url) {}, - onPageFinished: (String url) async { - await Future.delayed(const Duration(seconds: 2)); - setState(() { - if (progress == 100) loading = false; - }); - }, + onPageFinished: _onPageFinished, onHttpError: (HttpResponseError error) {}, onWebResourceError: (WebResourceError error) { // navigatorKey.currentState!.pop(); @@ -65,11 +74,12 @@ class _WebViewState extends State { } }, child: Scaffold( - appBar: AppBar( - title: const DidvanText( - 'بازگشت به دیدوان', - ), - ), + // appBar: AppBar( + // title: const DidvanText( + // 'بازگشت به دیدوان', + // ), + // leading: const BackButton(), + // ), body: loading ? Center( child: Image.asset( @@ -78,7 +88,16 @@ class _WebViewState extends State { height: 60, ), ) - : WebViewWidget(controller: controller), + : LayoutBuilder(builder: (context, constraints) { + return SizedBox( + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height, + child: WebViewWidget( + controller: controller, + gestureRecognizers: gestureRecognizers, + ), + ); + }), )); } } diff --git a/pubspec.yaml b/pubspec.yaml index bbb68b9..9295f55 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 3.3.0+3300 +version: 3.3.1+3300 environment: sdk: ">=2.19.0 <3.0.0"