// ignore_for_file: implementation_imports, library_private_types_in_public_api import 'dart:math'; import 'package:didvan/constants/app_icons.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/utils/media.dart'; import 'package:didvan/views/ai/widgets/message_bar_btn.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:just_audio/just_audio.dart'; class AudioWave extends StatefulWidget { final String file; final double loadingPaddingSize; const AudioWave({Key? key, required this.file, this.loadingPaddingSize = 0}) : super(key: key); @override _AudioWaveState createState() => _AudioWaveState(); } class _AudioWaveState extends State { final int itemCount = 35; final AudioPlayer audioPlayer = AudioPlayer(); final ValueNotifier> randoms = ValueNotifier([]); final ValueNotifier> randomsDisable = ValueNotifier([]); Duration totalDuration = Duration.zero; double currentPosition = 0; bool loading = true; bool faile = false; @override void initState() { super.initState(); try { init(); 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(5.74.w() * Random().nextDouble() + .26.w()); } } Future init() async { try { final path = widget.file; if (widget.file.startsWith('/uploads')) { final audioSource = LockCachingAudioSource(Uri.parse( '${RequestHelper.baseUrl + path}?accessToken=${RequestService.token}')); totalDuration = await audioPlayer.setAudioSource(audioSource) ?? 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 { for (var i = 0; i < itemCount; i++) { if (i < ((position.inMilliseconds * 40) / totalDuration.inMilliseconds)) { final ran = randomsDisable.value[i]; randoms.value[i] = ran; } else { randoms.value[i] = 0; } } if (position.inMilliseconds >= totalDuration.inMilliseconds) { audioPlayer.stop(); audioPlayer.seek(Duration.zero); } }); } @override void dispose() { audioPlayer.dispose(); 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) Opacity( opacity: 0, child: Container( width: 50.5.w(), color: Colors.transparent .withOpacity(1), 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(); }, ), ), ), ), ], )); }, ), DidvanText( DateTimeUtils.normalizeTimeDuration( snapshot.data! == Duration.zero ? totalDuration : snapshot.data!)), ], ), ); }, ) ], ), ), ); } Row noise({required final List values, final Color? color}) { return Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.spaceAround, children: values .map( (e) => Container( margin: EdgeInsets.symmetric(horizontal: .2.w()), width: .56.w(), height: e, decoration: BoxDecoration( borderRadius: BorderRadius.circular(1000), color: color ?? Theme.of(context).colorScheme.primary, ), ), ) .toList(), ); } }