From 34b9d46d714b0faba8bc5490a015d16660e6f82a Mon Sep 17 00:00:00 2001 From: OkaykOrhmn Date: Sun, 22 Sep 2024 15:48:24 +0330 Subject: [PATCH] idk --- lib/services/ai/ai_api_service.dart | 72 +--- lib/services/media/media.dart | 45 +- lib/services/media/voice.dart | 65 +++ lib/views/ai/ai_chat_page.dart | 17 +- lib/views/ai/widgets/ai_message_bar.dart | 97 +++-- lib/views/ai/widgets/audio_wave.dart | 394 +++++++----------- .../widgets/downloadable_audio_widget.dart | 2 +- lib/views/direct/widgets/message.dart | 3 +- lib/views/direct/widgets/message_box.dart | 3 +- lib/views/home/home_state.dart | 5 +- .../search/widgets/search_result_item.dart | 41 +- lib/views/podcasts/podcasts.dart | 2 +- lib/views/podcasts/widgets/tab_bar.dart | 2 +- lib/views/radar/radar.dart | 4 +- lib/views/widgets/audio/audio_slider.dart | 1 + lib/views/widgets/search_app_bar.dart | 9 + 16 files changed, 383 insertions(+), 379 deletions(-) create mode 100644 lib/services/media/voice.dart diff --git a/lib/services/ai/ai_api_service.dart b/lib/services/ai/ai_api_service.dart index 6e8b42f..0cc91c4 100644 --- a/lib/services/ai/ai_api_service.dart +++ b/lib/services/ai/ai_api_service.dart @@ -7,6 +7,7 @@ import 'package:didvan/models/ai/files_model.dart'; import 'package:didvan/services/storage/storage.dart'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; +import 'package:http_parser/http_parser.dart'; import 'package:mime/mime.dart'; import 'package:path/path.dart' as p; import 'package:http_parser/http_parser.dart' as parser; @@ -36,26 +37,11 @@ class AiApiService { request.fields['edit'] = edite.toString().toLowerCase(); } if (file != null) { - // final file = file; - // final filePath = file.path; - // final mimeType = filePath != null ? lookupMimeType(filePath) : null; - // final contentType = mimeType != null ? MediaType.parse(mimeType) : null; - - // final fileReadStream = file.readStream; - // if (fileReadStream == null) { - // throw Exception('Cannot read file from null stream'); - // } - // final stream = http.ByteStream(fileReadStream); - // final multipartFile = http.MultipartFile( - // 'file', - // stream, - // file.size, - // filename: file.name, - // contentType: contentType, - // ); - // request.files.add(multipartFile); Uint8List bytes; + String filename; + String mimeType; if (kIsWeb) { + // For web platform if (file.bytes != null) { bytes = file.bytes!; } else { @@ -63,42 +49,24 @@ class AiApiService { final http.Response audioResponse = await http.get(audioUri); bytes = audioResponse.bodyBytes; } - request.files.add(http.MultipartFile.fromBytes( - 'file', - bytes, - filename: file.isAudio() - ? 'wav' - : file.isImage() - ? 'png' - : 'pdf', // You can set a filename here - contentType: parser.MediaType.parse(file.isAudio() - ? 'audio/mpeg' - : file.isImage() - ? 'image/png' - : 'application/pdf'), // Set the MIME type - ) // Use MediaType.parse to parse the MIME type - ); } else { - int length = 0; - try { - length = await file.main.length(); - // ... - } catch (e) { - // Handle the error or return an error response - } - String? mimeType = lookupMimeType( - file.path); // Use MIME type instead of file extension - mimeType ??= 'application/octet-stream'; - if (mimeType.startsWith('audio')) { - mimeType = 'audio/${p.extension(file.path).replaceAll('.', '')}'; - } - request.files.add( - http.MultipartFile('file', file.main.readAsBytes().asStream(), length, - filename: file.basename, - contentType: parser.MediaType.parse( - mimeType)), // Use MediaType.parse to parse the MIME type - ); + // For other platforms + bytes = await file.main.readAsBytes(); } + filename = file.basename; + + mimeType = file.isAudio() + ? 'audio/m4a' + : file.isImage() + ? 'image/png' + : 'application/pdf'; + + request.files.add(http.MultipartFile.fromBytes( + 'file', + bytes, + filename: filename, + contentType: MediaType.parse(mimeType), + )); } // print("req: ${request.files}"); diff --git a/lib/services/media/media.dart b/lib/services/media/media.dart index 9c24b0f..fb0f5b5 100644 --- a/lib/services/media/media.dart +++ b/lib/services/media/media.dart @@ -22,11 +22,34 @@ class MediaService { static Duration? get duration => audioPlayer.duration; + static Future getDuration({required String src}) async { + final ap = AudioPlayer(); + Duration? duration; + + try { + if (src.startsWith('/uploads')) { + duration = await ap.setUrl( + '${RequestHelper.baseUrl + src}?accessToken=${RequestService.token}'); + } else if (src.startsWith('blob:')) { + duration = await ap.setAudioSource(AudioSource.uri(Uri.parse(src))); + } else { + duration = await ap.setFilePath(src); + } + } catch (e) { + print('Error setting audio source: $e'); + } finally { + await ap.dispose(); + } + + return duration; + } + static Future handleAudioPlayback({ required dynamic audioSource, required int id, bool isNetworkAudio = true, bool isVoiceMessage = true, + bool isBlob = false, void Function(bool isNext)? onTrackChanged, }) async { try { @@ -67,15 +90,19 @@ class MediaService { }, ); } else { - audioPlayer.setFilePath( - audioSource, - tag: isVoiceMessage - ? null - : { - "artist": 'استودیو دیدوان', - "title": currentPodcast?.title ?? '', - }, - ); + if (isBlob) { + audioPlayer.setAudioSource(AudioSource.uri(Uri.parse(audioSource))); + } else { + audioPlayer.setFilePath( + audioSource, + tag: isVoiceMessage + ? null + : { + "artist": 'استودیو دیدوان', + "title": currentPodcast?.title ?? '', + }, + ); + } } await audioPlayer.play(); // await audioPlayer.open( diff --git a/lib/services/media/voice.dart b/lib/services/media/voice.dart new file mode 100644 index 0000000..09a567a --- /dev/null +++ b/lib/services/media/voice.dart @@ -0,0 +1,65 @@ +import 'package:didvan/services/network/request.dart'; +import 'package:didvan/services/network/request_helper.dart'; +import 'package:just_audio/just_audio.dart'; + +class VoiceService { + static final audioPlayer = AudioPlayer(); + static int? index; + static ConcatenatingAudioSource? _playlist; + + VoiceService({required final List audios}) { + _playlist = ConcatenatingAudioSource( + // Start loading next item just before reaching it + useLazyPreparation: true, + // Customise the shuffle algorithm + shuffleOrder: DefaultShuffleOrder(), + // Specify the playlist items + children: audios, + ); + } + + static Future getDuration({ + required String src, + }) async { + if (src.startsWith('/uploads')) { + return await audioPlayer.setUrl( + '${RequestHelper.baseUrl + src}?accessToken=${RequestService.token}'); + } else if (src.startsWith('blob:')) { + return await audioPlayer.setAudioSource(AudioSource.uri(Uri.parse(src))); + } else { + return await audioPlayer.setFilePath(src); + } + } + + static Future voiceHelper({required int index}) async { + try { + if (VoiceService.index == index) { + if (audioPlayer.playerState == + PlayerState(true, ProcessingState.ready)) { + await audioPlayer.pause(); + } else { + await audioPlayer.play(); + } + + return true; + } + VoiceService.index = index; + await audioPlayer.setAudioSource(_playlist!, + initialIndex: index, initialPosition: Duration.zero); + await audioPlayer + .setLoopMode(LoopMode.off); // Set playlist to loop (off|all|one) + await audioPlayer.play(); + return true; + } catch (e) { + resetVoicePlayer(); + // rethrow; + return false; + } + } + + static Future resetVoicePlayer() async { + index = null; + + await audioPlayer.stop(); + } +} diff --git a/lib/views/ai/ai_chat_page.dart b/lib/views/ai/ai_chat_page.dart index 6dab66c..623afdf 100644 --- a/lib/views/ai/ai_chat_page.dart +++ b/lib/views/ai/ai_chat_page.dart @@ -23,6 +23,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/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -234,15 +235,13 @@ class _AiChatPageState extends State { const SizedBox( height: 24, ), - Container( + SizedBox( width: MediaQuery.sizeOf(context).height / 5, height: MediaQuery.sizeOf(context).height / 5, - decoration: BoxDecoration( - borderRadius: DesignConfig.highBorderRadius, - color: Theme.of(context).colorScheme.focused), - padding: const EdgeInsets.all(12), - child: CachedNetworkImage( - imageUrl: widget.args.bot.image.toString(), + child: ClipOval( + child: CachedNetworkImage( + imageUrl: widget.args.bot.image.toString(), + ), ), ), const SizedBox( @@ -425,7 +424,9 @@ class _AiChatPageState extends State { children: [ if (message.role.toString().contains('user') && file != null) - (file.isAudio() && (!kIsWeb && !Platform.isIOS)) + (file.isAudio() + // && (!kIsWeb && !Platform.isIOS) + ) ? Padding( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 8), diff --git a/lib/views/ai/widgets/ai_message_bar.dart b/lib/views/ai/widgets/ai_message_bar.dart index 92c54e2..e53b278 100644 --- a/lib/views/ai/widgets/ai_message_bar.dart +++ b/lib/views/ai/widgets/ai_message_bar.dart @@ -234,7 +234,9 @@ class _AiMessageBarState extends State { } state.file = kIsWeb ? FilesModel(pickedFile.path, - image: true, audio: false) + name: pickedFile.name, + image: true, + audio: false) : FilesModel(file!.path, image: true, audio: false); openAttach = false; @@ -244,44 +246,45 @@ class _AiMessageBarState extends State { ); }, ), - 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); + // 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 + 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; - File file = File.fromRawPath(bytes); - - state.file = FilesModel(file.path, - name: result.files.first.name, - bytes: bytes, - audio: true, - image: false); - } else { - state.file = FilesModel( - result.files.single.path!, - audio: true, - image: false); - } - openAttach = false; + 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 = false; + } + await Future.delayed( + Duration.zero, + () => ActionSheetUtils(context).pop(), + ); + }, + ) ], ), )), @@ -320,10 +323,12 @@ class _AiMessageBarState extends State { Padding( padding: const EdgeInsets.only( bottom: 8.0), - child: (!kIsWeb && + child: ( + // !kIsWeb && snapshot.hasData && - snapshot.data! != - RecordState.stop) + snapshot.data! != + RecordState + .stop) ? MessageBarBtn( enable: true, icon: DidvanIcons @@ -334,6 +339,8 @@ class _AiMessageBarState extends State { state.file = FilesModel( path.toString(), + name: + '${DateTime.now().millisecondsSinceEpoch ~/ 1000}.m4a', isRecorded: true, audio: true, image: false); @@ -342,11 +349,11 @@ class _AiMessageBarState extends State { state.update(); }, ) - : (!kIsWeb && - !Platform - .isIOS) && - widget.bot - .attachmentType! + : + // (!kIsWeb && + // !Platform + // .isIOS) && + widget.bot.attachmentType! .contains( 'audio') && value.isEmpty && @@ -373,7 +380,7 @@ class _AiMessageBarState extends State { path = p.join( downloadDir .path, - '${DateTime.now().millisecondsSinceEpoch ~/ 1000}.wav'); + '${DateTime.now().millisecondsSinceEpoch ~/ 1000}.m4a'); } record.start( diff --git a/lib/views/ai/widgets/audio_wave.dart b/lib/views/ai/widgets/audio_wave.dart index b3db1ba..e6ef9df 100644 --- a/lib/views/ai/widgets/audio_wave.dart +++ b/lib/views/ai/widgets/audio_wave.dart @@ -2,11 +2,17 @@ import 'dart:math'; +import 'package:audio_video_progress_bar/audio_video_progress_bar.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/services/media/media.dart'; import 'package:didvan/services/network/request.dart'; import 'package:didvan/services/network/request_helper.dart'; import 'package:didvan/utils/date_time.dart'; import 'package:didvan/views/ai/widgets/message_bar_btn.dart'; +import 'package:didvan/views/direct/widgets/message.dart'; +import 'package:didvan/views/widgets/audio/audio_slider.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -25,278 +31,158 @@ class AudioWave extends StatefulWidget { } class _AudioWaveState extends State { - final int itemCount = 35; - final AudioPlayer audioPlayer = AudioPlayer()..setPitch(1); - final ValueNotifier> randoms = ValueNotifier([]); - final ValueNotifier> randomsDisable = ValueNotifier([]); + bool loading = false; + Duration? totalDuration; - Duration totalDuration = Duration.zero; - double currentPosition = 0; - bool loading = true; - bool faile = false; - bool onChanging = false; + final int id = + DateTime.now().millisecondsSinceEpoch ~/ 1000 * Random().nextInt(1000000); @override void initState() { super.initState(); - try { - WidgetsBinding.instance.addPostFrameCallback((_) async { - await init(); + WidgetsBinding.instance.addPostFrameCallback((_) async { + await MediaService.getDuration(src: widget.file).then((duration) { + setState(() { + totalDuration = duration; + print(totalDuration!.inSeconds); + }); }); - listeners(); - } catch (e) { - if (kDebugMode) { - print('Error occurred: $e'); - } - - rethrow; - } - } - - void setRandoms() { - for (var i = 0; i < itemCount; i++) { - randoms.value.add(0); - randomsDisable.value.add(1 + Random().nextDouble() * (38 - 1)); - } - } - - Future init() async { - try { - final path = widget.file; - if (widget.file.startsWith('blob:')) { - totalDuration = await audioPlayer - .setAudioSource(AudioSource.uri(Uri.parse(path))) ?? - Duration.zero; - } else if (widget.file.startsWith('/uploads')) { - AudioSource.uri(Uri.parse( - '${RequestHelper.baseUrl + path}?accessToken=${RequestService.token}')); - totalDuration = await audioPlayer.setUrl( - '${RequestHelper.baseUrl + path}?accessToken=${RequestService.token}') ?? - Duration.zero; - } else { - totalDuration = await audioPlayer.setFilePath(path) ?? Duration.zero; - } - setRandoms(); - setState(() { - loading = false; - }); - } catch (e) { - setState(() { - faile = true; - loading = false; - }); - - if (kDebugMode) { - print('Error occurred: $e'); - } - } - } - - Future listeners() async { - audioPlayer.positionStream.listen((position) async { - if (randomsDisable.value.isEmpty || onChanging) return; - - try { - for (var i = 0; i < itemCount; i++) { - if (i < randomsDisable.value.length && - i < - ((position.inMilliseconds * 40) / - totalDuration.inMilliseconds)) { - final ran = randomsDisable.value[i]; - randoms.value[i] = ran; - } else { - randoms.value[i] = 0; - } - } - } catch (e) { - e.printError(info: 'listener Error'); - } - - if (position.inMilliseconds >= totalDuration.inMilliseconds) { - audioPlayer.stop(); - audioPlayer.seek(Duration.zero); - } }); + // listeners(); } @override - void dispose() async { - await audioPlayer.stop(); - audioPlayer.dispose(); + void dispose() { + MediaService.resetAudioPlayer(); super.dispose(); } @override Widget build(BuildContext context) { return SizedBox( - height: 46, - child: loading - ? 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, - ))), - ) - : Directionality( - textDirection: TextDirection.ltr, - child: Row( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - StreamBuilder( - stream: audioPlayer.playerStateStream, - builder: (context, snapshot) { - if (!snapshot.hasData) { - return const SizedBox(); - } - return MessageBarBtn( - enable: true, - icon: faile - ? DidvanIcons.refresh_solid - : snapshot.data!.playing - ? DidvanIcons.pause_solid - : DidvanIcons.play_solid, - click: () async { - if (faile) { - randoms.value.clear(); - randomsDisable.value.clear(); - setState(() { - loading = true; - faile = false; - }); - init(); - return; - } - if (snapshot.data!.playing) { - await audioPlayer.pause(); - } else { - await audioPlayer.play(); - } - }, - ); - }), - faile - ? const Padding( - padding: EdgeInsets.symmetric(horizontal: 8.0), - child: DidvanText( - 'خطا در بارگزاری فایل صوتی', - fontSize: 12, - ), - ) - : StreamBuilder( - stream: audioPlayer.positionStream, - builder: (context, snapshot) { - if (!snapshot.hasData) { - return const SizedBox(); - } - currentPosition = - snapshot.data!.inMilliseconds.toDouble(); - return Expanded( - child: Row( - children: [ - ValueListenableBuilder( - valueListenable: randoms, - builder: (context, value, child) { - return Expanded( - child: Stack( - alignment: Alignment.center, - children: [ - noise(values: randoms.value), - noise( - values: randomsDisable.value, - color: Theme.of(context) - .colorScheme - .primary - .withOpacity(0.4)), - if (totalDuration != Duration.zero) - Positioned.fill( - child: Opacity( - opacity: 0, - child: Theme( - data: Theme.of(context) - .copyWith( - sliderTheme: - SliderThemeData( - thumbShape: - SliderComponentShape - .noThumb, - minThumbSeparation: 0, - ), - splashColor: - Colors.transparent, - ), - child: Slider( - value: currentPosition, - max: totalDuration - .inMilliseconds - .toDouble() + - const Duration( - milliseconds: - 10) - .inMilliseconds - .toDouble(), - onChangeStart: (value) { - // audioPlayer.pause(); - }, - onChanged: (value) { - // for (var i = 0; - // i < itemCount; - // i++) { - // if (i < - // ((value * 40) / - // totalDuration - // .inMilliseconds)) { - // final ran = - // randomsDisable - // .value[i]; - // randoms.value[i] = - // ran; - // } else { - // randoms.value[i] = 0; - // } - // } - setState(() { - currentPosition = value; - }); - }, - onChangeEnd: (value) { - audioPlayer.seek(Duration( - milliseconds: - value.round())); - audioPlayer.play(); - }, - ), - ), - ), - ), - ], - )); - }, + height: 46, + child: Directionality( + textDirection: TextDirection.ltr, + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + StreamBuilder( + stream: MediaService.audioPlayer.playerStateStream, + builder: (context, snapshot) { + if (!snapshot.hasData) { + return const SizedBox(); + } + // if (snapshot.data!.processingState == + // ProcessingState.completed) { + // MediaService.audioPlayer.pause(); + // MediaService.audioPlayer.seek(Duration.zero); + // } + print(snapshot.data); + return MessageBarBtn( + enable: true, + icon: snapshot.data!.playing && + snapshot.data!.processingState != + ProcessingState.completed && + MediaService.audioPlayerTag == 'message-$id' + ? DidvanIcons.pause_solid + : DidvanIcons.play_solid, + click: () async { + await MediaService.handleAudioPlayback( + audioSource: widget.file, + id: id, + isNetworkAudio: widget.file.startsWith('/uploads'), + isVoiceMessage: true, + isBlob: widget.file.startsWith('blob:')); + MediaService.audioPlayerTag = 'message-$id'; + }, + ); + }), + StreamBuilder( + stream: MediaService.audioPlayer.positionStream, + builder: (context, snapshot) { + if (!snapshot.hasData) { + return const SizedBox(); + } + print("Position: ${snapshot.data}"); + return Expanded( + child: totalDuration == null + ? 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, + ))), + ) + : Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12.0), + child: Expanded( + 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: MediaService.audioPlayerTag == + 'message-$id' + ? 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 (MediaService.audioPlayerTag == + 'message-$id') { + MediaService.audioPlayer.seek( + Duration( + milliseconds: + value.inMilliseconds)); + } + }, + ), ), - DidvanText( - DateTimeUtils.normalizeTimeDuration( - snapshot.data! == Duration.zero - ? totalDuration - : snapshot.data!)), - ], + ), ), - ); - }, - ) - ], - ), - ), - ); + DidvanText(DateTimeUtils.normalizeTimeDuration( + snapshot.data ?? Duration.zero)), + ], + ), + ); + }, + ) + ], + ), + )); } Row noise({required final List values, final Color? color}) { diff --git a/lib/views/direct/widgets/downloadable_audio_widget.dart b/lib/views/direct/widgets/downloadable_audio_widget.dart index da3ded1..f1c46b5 100644 --- a/lib/views/direct/widgets/downloadable_audio_widget.dart +++ b/lib/views/direct/widgets/downloadable_audio_widget.dart @@ -25,6 +25,6 @@ class DownloadableAudioWidget extends StatelessWidget { Widget build(BuildContext context) { return SizedBox( width: MediaQuery.sizeOf(context).width / 1.6, - child: AudioWave(file: audioUrl != null ? audioUrl! : audioFile!.path)); + child: AudioWave(file: audioUrl ?? audioFile!.path)); } } diff --git a/lib/views/direct/widgets/message.dart b/lib/views/direct/widgets/message.dart index a3fa72c..46ff977 100644 --- a/lib/views/direct/widgets/message.dart +++ b/lib/views/direct/widgets/message.dart @@ -4,6 +4,7 @@ import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/models/message_data/message_data.dart'; import 'package:didvan/utils/date_time.dart'; import 'package:didvan/views/direct/direct_state.dart'; +import 'package:didvan/views/direct/widgets/audio_widget.dart'; import 'package:didvan/views/direct/widgets/downloadable_audio_widget.dart'; import 'package:didvan/views/widgets/didvan/divider.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; @@ -84,7 +85,7 @@ class Message extends StatelessWidget { children: [ if (message.text != null) DidvanText(message.text!), if (message.audio != null || message.audioFile != null) - DownloadableAudioWidget( + AudioWidget( audioFile: message.audioFile, audioUrl: message.audio, id: message.id, diff --git a/lib/views/direct/widgets/message_box.dart b/lib/views/direct/widgets/message_box.dart index 74cd9fa..1e60cbe 100644 --- a/lib/views/direct/widgets/message_box.dart +++ b/lib/views/direct/widgets/message_box.dart @@ -3,6 +3,7 @@ import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/app_icons.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/direct/widgets/downloadable_audio_widget.dart'; import 'package:didvan/views/widgets/didvan/icon_button.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; @@ -227,7 +228,7 @@ class _RecordChecking extends StatelessWidget { Expanded( child: Padding( padding: const EdgeInsets.symmetric(vertical: 8), - child: DownloadableAudioWidget( + child: AudioWidget( audioFile: state.recordedFile!, id: 0, // deleteClidk: () => state.deleteRecordedFile, diff --git a/lib/views/home/home_state.dart b/lib/views/home/home_state.dart index 0af3c2a..7c17260 100644 --- a/lib/views/home/home_state.dart +++ b/lib/views/home/home_state.dart @@ -134,11 +134,10 @@ class HomeState extends CoreProvier { final categoryFilters = [ CategoryData(id: 1, label: 'پویش افق'), CategoryData(id: 2, label: 'دنیای فولاد'), - CategoryData(id: 3, label: 'ویدیوکست'), - CategoryData(id: 4, label: 'پادکست'), + CategoryData(id: 3, label: 'استودیو آینده'), CategoryData(id: 5, label: 'رادارهای استراتژیک'), CategoryData(id: 6, label: 'سها'), - CategoryData(id: 7, label: 'هوشان'), + CategoryData(id: 7, label: 'اینفوگرافی'), ]; void refresh() async { diff --git a/lib/views/home/search/widgets/search_result_item.dart b/lib/views/home/search/widgets/search_result_item.dart index 0a5739f..284248a 100644 --- a/lib/views/home/search/widgets/search_result_item.dart +++ b/lib/views/home/search/widgets/search_result_item.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/models/overview_data.dart'; @@ -68,9 +70,44 @@ class SearchResultItem extends StatelessWidget { if (item.type == 'delphi') { return DidvanIcons.saha_light; } + if (item.type == 'infography') { + return DidvanIcons.infography_regular; + } return DidvanIcons.radar_light; } + void _openInteractiveViewer(BuildContext context, String image) { + showDialog( + context: context, + builder: (context) => Stack( + children: [ + Positioned.fill( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: InteractiveViewer( + child: Center( + child: SkeletonImage( + width: min(MediaQuery.of(context).size.width, + MediaQuery.of(context).size.height), + imageUrl: image, + ), + ), + ), + ), + ), + Positioned( + right: 24, + top: 24, + child: Container( + decoration: const BoxDecoration( + color: Colors.white, shape: BoxShape.circle), + child: const BackButton()), + ), + ], + ), + ); + } + @override Widget build(BuildContext context) { return DidvanCard( @@ -90,7 +127,9 @@ class SearchResultItem extends StatelessWidget { ); return; } - if (_targetPageRouteName == null && item.link != null) { + if (item.type == "infography") { + _openInteractiveViewer(context, item.image); + } else if (_targetPageRouteName == null && item.link != null) { AppInitializer.openWebLink( context, '${item.link!}?accessToken=${RequestService.token}', diff --git a/lib/views/podcasts/podcasts.dart b/lib/views/podcasts/podcasts.dart index 1d416b8..700ff38 100644 --- a/lib/views/podcasts/podcasts.dart +++ b/lib/views/podcasts/podcasts.dart @@ -87,7 +87,7 @@ class _PodcastsState extends State { Padding( padding: const EdgeInsets.all(16.0), child: SearchField( - title: state.videosSelected ? 'ویدیو' : 'پادکست', + title: state.videosSelected ? 'ویدیوکست' : 'پادکست', onChanged: _onChanged, focusNode: _focusNode, isFiltered: false, diff --git a/lib/views/podcasts/widgets/tab_bar.dart b/lib/views/podcasts/widgets/tab_bar.dart index 31fa32f..f7ab0fe 100644 --- a/lib/views/podcasts/widgets/tab_bar.dart +++ b/lib/views/podcasts/widgets/tab_bar.dart @@ -30,7 +30,7 @@ class StudioTabBar extends StatelessWidget { child: _StudioTypeButton( icon: DidvanIcons.video_solid, selectedColor: Theme.of(context).colorScheme.focusedBorder, - title: 'ویدیو', + title: 'ویدیوکست', onTap: () => state.videosSelected = true, isSelected: state.videosSelected, ), diff --git a/lib/views/radar/radar.dart b/lib/views/radar/radar.dart index b896407..21b93a1 100644 --- a/lib/views/radar/radar.dart +++ b/lib/views/radar/radar.dart @@ -105,10 +105,10 @@ class _RadarState extends State { ), ), ), - if (state.searching || state.filtering) + if (state.isColapsed || state.searching || state.filtering) const SliverToBoxAdapter( child: SizedBox( - height: 72, + height: 160, ), ), SliverStateHandler( diff --git a/lib/views/widgets/audio/audio_slider.dart b/lib/views/widgets/audio/audio_slider.dart index 8309963..2f62b01 100644 --- a/lib/views/widgets/audio/audio_slider.dart +++ b/lib/views/widgets/audio/audio_slider.dart @@ -21,6 +21,7 @@ class AudioSlider extends StatelessWidget { @override Widget build(BuildContext context) { + print(MediaService.audioPlayerTag); return IgnorePointer( ignoring: !_isPlaying, child: Directionality( diff --git a/lib/views/widgets/search_app_bar.dart b/lib/views/widgets/search_app_bar.dart index 0e607cf..a752bca 100644 --- a/lib/views/widgets/search_app_bar.dart +++ b/lib/views/widgets/search_app_bar.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:didvan/constants/app_icons.dart'; +import 'package:didvan/models/category.dart'; import 'package:didvan/models/view/action_sheet_data.dart'; import 'package:didvan/utils/action_sheet.dart'; import 'package:didvan/views/home/home_state.dart'; @@ -143,9 +144,17 @@ class SearchAppBar extends StatelessWidget implements PreferredSizeWidget { onChanged: (value) { if (value) { state.selectedCats.add(state.categoryFilters[i]); + if (state.categoryFilters[i].id == 3) { + state.selectedCats + .add(CategoryData(id: 4, label: 'پادکست')); + } return; } state.selectedCats.remove(state.categoryFilters[i]); + if (state.categoryFilters[i].id == 3) { + state.selectedCats + .remove(CategoryData(id: 4, label: 'پادکست')); + } }, ), ),