This commit is contained in:
OkaykOrhmn 2024-09-22 15:48:24 +03:30
parent 20643b6075
commit 34b9d46d71
16 changed files with 383 additions and 379 deletions

View File

@ -7,6 +7,7 @@ import 'package:didvan/models/ai/files_model.dart';
import 'package:didvan/services/storage/storage.dart'; import 'package:didvan/services/storage/storage.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:http_parser/http_parser.dart';
import 'package:mime/mime.dart'; import 'package:mime/mime.dart';
import 'package:path/path.dart' as p; import 'package:path/path.dart' as p;
import 'package:http_parser/http_parser.dart' as parser; import 'package:http_parser/http_parser.dart' as parser;
@ -36,26 +37,11 @@ class AiApiService {
request.fields['edit'] = edite.toString().toLowerCase(); request.fields['edit'] = edite.toString().toLowerCase();
} }
if (file != null) { 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; Uint8List bytes;
String filename;
String mimeType;
if (kIsWeb) { if (kIsWeb) {
// For web platform
if (file.bytes != null) { if (file.bytes != null) {
bytes = file.bytes!; bytes = file.bytes!;
} else { } else {
@ -63,42 +49,24 @@ class AiApiService {
final http.Response audioResponse = await http.get(audioUri); final http.Response audioResponse = await http.get(audioUri);
bytes = audioResponse.bodyBytes; bytes = audioResponse.bodyBytes;
} }
} else {
// 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( request.files.add(http.MultipartFile.fromBytes(
'file', 'file',
bytes, bytes,
filename: file.isAudio() filename: filename,
? 'wav' contentType: MediaType.parse(mimeType),
: 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
);
}
} }
// print("req: ${request.files}"); // print("req: ${request.files}");

View File

@ -22,11 +22,34 @@ class MediaService {
static Duration? get duration => audioPlayer.duration; static Duration? get duration => audioPlayer.duration;
static Future<Duration?> 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<void> handleAudioPlayback({ static Future<void> handleAudioPlayback({
required dynamic audioSource, required dynamic audioSource,
required int id, required int id,
bool isNetworkAudio = true, bool isNetworkAudio = true,
bool isVoiceMessage = true, bool isVoiceMessage = true,
bool isBlob = false,
void Function(bool isNext)? onTrackChanged, void Function(bool isNext)? onTrackChanged,
}) async { }) async {
try { try {
@ -66,6 +89,9 @@ class MediaService {
"title": currentPodcast?.title ?? '', "title": currentPodcast?.title ?? '',
}, },
); );
} else {
if (isBlob) {
audioPlayer.setAudioSource(AudioSource.uri(Uri.parse(audioSource)));
} else { } else {
audioPlayer.setFilePath( audioPlayer.setFilePath(
audioSource, audioSource,
@ -77,6 +103,7 @@ class MediaService {
}, },
); );
} }
}
await audioPlayer.play(); await audioPlayer.play();
// await audioPlayer.open( // await audioPlayer.open(
// audio, // audio,

View File

@ -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<AudioSource> 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<Duration?> 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<bool> 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<void> resetVoicePlayer() async {
index = null;
await audioPlayer.stop();
}
}

View File

@ -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/didvan/text.dart';
import 'package:didvan/views/widgets/marquee_text.dart'; import 'package:didvan/views/widgets/marquee_text.dart';
import 'package:didvan/views/widgets/skeleton_image.dart'; import 'package:didvan/views/widgets/skeleton_image.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -234,17 +235,15 @@ class _AiChatPageState extends State<AiChatPage> {
const SizedBox( const SizedBox(
height: 24, height: 24,
), ),
Container( SizedBox(
width: MediaQuery.sizeOf(context).height / 5, width: MediaQuery.sizeOf(context).height / 5,
height: MediaQuery.sizeOf(context).height / 5, height: MediaQuery.sizeOf(context).height / 5,
decoration: BoxDecoration( child: ClipOval(
borderRadius: DesignConfig.highBorderRadius,
color: Theme.of(context).colorScheme.focused),
padding: const EdgeInsets.all(12),
child: CachedNetworkImage( child: CachedNetworkImage(
imageUrl: widget.args.bot.image.toString(), imageUrl: widget.args.bot.image.toString(),
), ),
), ),
),
const SizedBox( const SizedBox(
height: 24, height: 24,
), ),
@ -425,7 +424,9 @@ class _AiChatPageState extends State<AiChatPage> {
children: [ children: [
if (message.role.toString().contains('user') && if (message.role.toString().contains('user') &&
file != null) file != null)
(file.isAudio() && (!kIsWeb && !Platform.isIOS)) (file.isAudio()
// && (!kIsWeb && !Platform.isIOS)
)
? Padding( ? Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 8), horizontal: 16, vertical: 8),

View File

@ -234,7 +234,9 @@ class _AiMessageBarState extends State<AiMessageBar> {
} }
state.file = kIsWeb state.file = kIsWeb
? FilesModel(pickedFile.path, ? FilesModel(pickedFile.path,
image: true, audio: false) name: pickedFile.name,
image: true,
audio: false)
: FilesModel(file!.path, : FilesModel(file!.path,
image: true, audio: false); image: true, audio: false);
openAttach = false; openAttach = false;
@ -244,7 +246,7 @@ class _AiMessageBarState extends State<AiMessageBar> {
); );
}, },
), ),
if (!kIsWeb && !Platform.isIOS) // if (!kIsWeb && !Platform.isIOS)
if (historyState.bot!.attachmentType! if (historyState.bot!.attachmentType!
.contains('audio')) .contains('audio'))
attachBtn( attachBtn(
@ -258,16 +260,17 @@ class _AiMessageBarState extends State<AiMessageBar> {
await MediaService.pickAudioFile(); await MediaService.pickAudioFile();
if (result != null) { if (result != null) {
if (kIsWeb) { if (kIsWeb) {
Uint8List bytes = result.files.first Uint8List? bytes = result.files.first
.bytes!; // Access the bytes property .bytes; // Access the bytes property
String? name = result.files.first.name;
File file = File.fromRawPath(bytes); state.file = FilesModel(
'', // No need for a file path on web
state.file = FilesModel(file.path, name: name,
name: result.files.first.name,
bytes: bytes, bytes: bytes,
audio: true, audio: true,
image: false); image: false,
);
} else { } else {
state.file = FilesModel( state.file = FilesModel(
result.files.single.path!, result.files.single.path!,
@ -320,10 +323,12 @@ class _AiMessageBarState extends State<AiMessageBar> {
Padding( Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
bottom: 8.0), bottom: 8.0),
child: (!kIsWeb && child: (
// !kIsWeb &&
snapshot.hasData && snapshot.hasData &&
snapshot.data! != snapshot.data! !=
RecordState.stop) RecordState
.stop)
? MessageBarBtn( ? MessageBarBtn(
enable: true, enable: true,
icon: DidvanIcons icon: DidvanIcons
@ -334,6 +339,8 @@ class _AiMessageBarState extends State<AiMessageBar> {
state.file = FilesModel( state.file = FilesModel(
path.toString(), path.toString(),
name:
'${DateTime.now().millisecondsSinceEpoch ~/ 1000}.m4a',
isRecorded: true, isRecorded: true,
audio: true, audio: true,
image: false); image: false);
@ -342,11 +349,11 @@ class _AiMessageBarState extends State<AiMessageBar> {
state.update(); state.update();
}, },
) )
: (!kIsWeb && :
!Platform // (!kIsWeb &&
.isIOS) && // !Platform
widget.bot // .isIOS) &&
.attachmentType! widget.bot.attachmentType!
.contains( .contains(
'audio') && 'audio') &&
value.isEmpty && value.isEmpty &&
@ -373,7 +380,7 @@ class _AiMessageBarState extends State<AiMessageBar> {
path = p.join( path = p.join(
downloadDir downloadDir
.path, .path,
'${DateTime.now().millisecondsSinceEpoch ~/ 1000}.wav'); '${DateTime.now().millisecondsSinceEpoch ~/ 1000}.m4a');
} }
record.start( record.start(

View File

@ -2,11 +2,17 @@
import 'dart:math'; 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/constants/app_icons.dart';
import 'package:didvan/services/media/media.dart';
import 'package:didvan/services/network/request.dart'; import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/network/request_helper.dart'; import 'package:didvan/services/network/request_helper.dart';
import 'package:didvan/utils/date_time.dart'; import 'package:didvan/utils/date_time.dart';
import 'package:didvan/views/ai/widgets/message_bar_btn.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:didvan/views/widgets/didvan/text.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -25,104 +31,29 @@ class AudioWave extends StatefulWidget {
} }
class _AudioWaveState extends State<AudioWave> { class _AudioWaveState extends State<AudioWave> {
final int itemCount = 35; bool loading = false;
final AudioPlayer audioPlayer = AudioPlayer()..setPitch(1); Duration? totalDuration;
final ValueNotifier<List<double>> randoms = ValueNotifier([]);
final ValueNotifier<List<double>> randomsDisable = ValueNotifier([]);
Duration totalDuration = Duration.zero; final int id =
double currentPosition = 0; DateTime.now().millisecondsSinceEpoch ~/ 1000 * Random().nextInt(1000000);
bool loading = true;
bool faile = false;
bool onChanging = false;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
try {
WidgetsBinding.instance.addPostFrameCallback((_) async { WidgetsBinding.instance.addPostFrameCallback((_) async {
await init(); await MediaService.getDuration(src: widget.file).then((duration) {
});
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<void> 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(() { setState(() {
loading = false; totalDuration = duration;
print(totalDuration!.inSeconds);
}); });
} catch (e) {
setState(() {
faile = true;
loading = false;
}); });
if (kDebugMode) {
print('Error occurred: $e');
}
}
}
Future<void> 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 @override
void dispose() async { void dispose() {
await audioPlayer.stop(); MediaService.resetAudioPlayer();
audioPlayer.dispose();
super.dispose(); super.dispose();
} }
@ -130,10 +61,55 @@ class _AudioWaveState extends State<AudioWave> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SizedBox( return SizedBox(
height: 46, height: 46,
child: loading child: Directionality(
textDirection: TextDirection.ltr,
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
StreamBuilder<PlayerState>(
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<Duration>(
stream: MediaService.audioPlayer.positionStream,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const SizedBox();
}
print("Position: ${snapshot.data}");
return Expanded(
child: totalDuration == null
? Padding( ? Padding(
padding: padding: EdgeInsets.symmetric(
EdgeInsets.symmetric(vertical: widget.loadingPaddingSize), vertical: widget.loadingPaddingSize),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: List.generate( children: List.generate(
@ -147,147 +123,58 @@ class _AudioWaveState extends State<AudioWave> {
itemCount: 10, itemCount: 10,
))), ))),
) )
: Directionality( : Row(
textDirection: TextDirection.ltr,
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
StreamBuilder<PlayerState>( Expanded(
stream: audioPlayer.playerStateStream, child: Padding(
builder: (context, snapshot) { padding: const EdgeInsets.symmetric(
if (!snapshot.hasData) { horizontal: 12.0),
return const SizedBox(); child: Expanded(
} child: ProgressBar(
return MessageBarBtn( thumbColor:
enable: true, Theme.of(context).colorScheme.title,
icon: faile progressBarColor: DesignConfig.isDark
? DidvanIcons.refresh_solid ? Theme.of(context).colorScheme.title
: snapshot.data!.playing : Theme.of(context)
? 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<Duration>(
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 .colorScheme
.primary .primary,
.withOpacity(0.4)), baseBarColor:
if (totalDuration != Duration.zero) Theme.of(context).colorScheme.border,
Positioned.fill( bufferedBarColor:
child: Opacity( Theme.of(context).colorScheme.splash,
opacity: 0, total: totalDuration!,
child: Theme( progress: MediaService.audioPlayerTag ==
data: Theme.of(context) 'message-$id'
.copyWith( ? snapshot.data ?? Duration.zero
sliderTheme: : Duration.zero,
SliderThemeData( thumbRadius: 6,
thumbShape: barHeight: 3,
SliderComponentShape // timeLabelTextStyle: TextStyle(
.noThumb, // fontSize: showTimer ? null : 0,
minThumbSeparation: 0, // height: showTimer ? 3 : 0,
), // color:
splashColor: // Theme.of(context).colorScheme.text,
Colors.transparent, // fontFamily:
), // DesignConfig.fontFamily.replaceAll(
child: Slider( // '-FA',
value: currentPosition, // '',
max: totalDuration // ),
.inMilliseconds // ),
.toDouble() + onSeek: (value) {
const Duration( if (MediaService.audioPlayerTag ==
'message-$id') {
MediaService.audioPlayer.seek(
Duration(
milliseconds: milliseconds:
10) value.inMilliseconds));
.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();
}, },
), ),
), ),
), ),
), ),
], DidvanText(DateTimeUtils.normalizeTimeDuration(
)); snapshot.data ?? Duration.zero)),
},
),
DidvanText(
DateTimeUtils.normalizeTimeDuration(
snapshot.data! == Duration.zero
? totalDuration
: snapshot.data!)),
], ],
), ),
); );
@ -295,8 +182,7 @@ class _AudioWaveState extends State<AudioWave> {
) )
], ],
), ),
), ));
);
} }
Row noise({required final List<double> values, final Color? color}) { Row noise({required final List<double> values, final Color? color}) {

View File

@ -25,6 +25,6 @@ class DownloadableAudioWidget extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SizedBox( return SizedBox(
width: MediaQuery.sizeOf(context).width / 1.6, width: MediaQuery.sizeOf(context).width / 1.6,
child: AudioWave(file: audioUrl != null ? audioUrl! : audioFile!.path)); child: AudioWave(file: audioUrl ?? audioFile!.path));
} }
} }

View File

@ -4,6 +4,7 @@ import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/models/message_data/message_data.dart'; import 'package:didvan/models/message_data/message_data.dart';
import 'package:didvan/utils/date_time.dart'; import 'package:didvan/utils/date_time.dart';
import 'package:didvan/views/direct/direct_state.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/direct/widgets/downloadable_audio_widget.dart';
import 'package:didvan/views/widgets/didvan/divider.dart'; import 'package:didvan/views/widgets/didvan/divider.dart';
import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/didvan/text.dart';
@ -84,7 +85,7 @@ class Message extends StatelessWidget {
children: [ children: [
if (message.text != null) DidvanText(message.text!), if (message.text != null) DidvanText(message.text!),
if (message.audio != null || message.audioFile != null) if (message.audio != null || message.audioFile != null)
DownloadableAudioWidget( AudioWidget(
audioFile: message.audioFile, audioFile: message.audioFile,
audioUrl: message.audio, audioUrl: message.audio,
id: message.id, id: message.id,

View File

@ -3,6 +3,7 @@ import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/views/ai/widgets/message_bar_btn.dart'; import 'package:didvan/views/ai/widgets/message_bar_btn.dart';
import 'package:didvan/views/direct/direct_state.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/direct/widgets/downloadable_audio_widget.dart';
import 'package:didvan/views/widgets/didvan/icon_button.dart'; import 'package:didvan/views/widgets/didvan/icon_button.dart';
import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/didvan/text.dart';
@ -227,7 +228,7 @@ class _RecordChecking extends StatelessWidget {
Expanded( Expanded(
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8), padding: const EdgeInsets.symmetric(vertical: 8),
child: DownloadableAudioWidget( child: AudioWidget(
audioFile: state.recordedFile!, audioFile: state.recordedFile!,
id: 0, id: 0,
// deleteClidk: () => state.deleteRecordedFile, // deleteClidk: () => state.deleteRecordedFile,

View File

@ -134,11 +134,10 @@ class HomeState extends CoreProvier {
final categoryFilters = [ final categoryFilters = [
CategoryData(id: 1, label: 'پویش افق'), CategoryData(id: 1, label: 'پویش افق'),
CategoryData(id: 2, label: 'دنیای فولاد'), CategoryData(id: 2, label: 'دنیای فولاد'),
CategoryData(id: 3, label: 'ویدیوکست'), CategoryData(id: 3, label: 'استودیو آینده'),
CategoryData(id: 4, label: 'پادکست'),
CategoryData(id: 5, label: 'رادارهای استراتژیک'), CategoryData(id: 5, label: 'رادارهای استراتژیک'),
CategoryData(id: 6, label: 'سها'), CategoryData(id: 6, label: 'سها'),
CategoryData(id: 7, label: 'هوشان'), CategoryData(id: 7, label: 'اینفوگرافی'),
]; ];
void refresh() async { void refresh() async {

View File

@ -1,3 +1,5 @@
import 'dart:math';
import 'package:didvan/config/theme_data.dart'; import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/models/overview_data.dart'; import 'package:didvan/models/overview_data.dart';
@ -68,9 +70,44 @@ class SearchResultItem extends StatelessWidget {
if (item.type == 'delphi') { if (item.type == 'delphi') {
return DidvanIcons.saha_light; return DidvanIcons.saha_light;
} }
if (item.type == 'infography') {
return DidvanIcons.infography_regular;
}
return DidvanIcons.radar_light; 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return DidvanCard( return DidvanCard(
@ -90,7 +127,9 @@ class SearchResultItem extends StatelessWidget {
); );
return; return;
} }
if (_targetPageRouteName == null && item.link != null) { if (item.type == "infography") {
_openInteractiveViewer(context, item.image);
} else if (_targetPageRouteName == null && item.link != null) {
AppInitializer.openWebLink( AppInitializer.openWebLink(
context, context,
'${item.link!}?accessToken=${RequestService.token}', '${item.link!}?accessToken=${RequestService.token}',

View File

@ -87,7 +87,7 @@ class _PodcastsState extends State<Podcasts> {
Padding( Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: SearchField( child: SearchField(
title: state.videosSelected ? 'ویدیو' : 'پادکست', title: state.videosSelected ? 'ویدیوکست' : 'پادکست',
onChanged: _onChanged, onChanged: _onChanged,
focusNode: _focusNode, focusNode: _focusNode,
isFiltered: false, isFiltered: false,

View File

@ -30,7 +30,7 @@ class StudioTabBar extends StatelessWidget {
child: _StudioTypeButton( child: _StudioTypeButton(
icon: DidvanIcons.video_solid, icon: DidvanIcons.video_solid,
selectedColor: Theme.of(context).colorScheme.focusedBorder, selectedColor: Theme.of(context).colorScheme.focusedBorder,
title: 'ویدیو', title: 'ویدیوکست',
onTap: () => state.videosSelected = true, onTap: () => state.videosSelected = true,
isSelected: state.videosSelected, isSelected: state.videosSelected,
), ),

View File

@ -105,10 +105,10 @@ class _RadarState extends State<Radar> {
), ),
), ),
), ),
if (state.searching || state.filtering) if (state.isColapsed || state.searching || state.filtering)
const SliverToBoxAdapter( const SliverToBoxAdapter(
child: SizedBox( child: SizedBox(
height: 72, height: 160,
), ),
), ),
SliverStateHandler<RadarState>( SliverStateHandler<RadarState>(

View File

@ -21,6 +21,7 @@ class AudioSlider extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
print(MediaService.audioPlayerTag);
return IgnorePointer( return IgnorePointer(
ignoring: !_isPlaying, ignoring: !_isPlaying,
child: Directionality( child: Directionality(

View File

@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:didvan/constants/app_icons.dart'; 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/models/view/action_sheet_data.dart';
import 'package:didvan/utils/action_sheet.dart'; import 'package:didvan/utils/action_sheet.dart';
import 'package:didvan/views/home/home_state.dart'; import 'package:didvan/views/home/home_state.dart';
@ -143,9 +144,17 @@ class SearchAppBar extends StatelessWidget implements PreferredSizeWidget {
onChanged: (value) { onChanged: (value) {
if (value) { if (value) {
state.selectedCats.add(state.categoryFilters[i]); state.selectedCats.add(state.categoryFilters[i]);
if (state.categoryFilters[i].id == 3) {
state.selectedCats
.add(CategoryData(id: 4, label: 'پادکست'));
}
return; return;
} }
state.selectedCats.remove(state.categoryFilters[i]); state.selectedCats.remove(state.categoryFilters[i]);
if (state.categoryFilters[i].id == 3) {
state.selectedCats
.remove(CategoryData(id: 4, label: 'پادکست'));
}
}, },
), ),
), ),