import 'dart:async'; import 'package:flutter/material.dart'; import 'package:audioplayers/audioplayers.dart'; import 'package:lba/res/colors.dart'; class ChatMessageAudioPlayer extends StatefulWidget { final String audioPath; final bool isUser; final Duration? audioDuration; const ChatMessageAudioPlayer({ super.key, required this.audioPath, required this.isUser, this.audioDuration, }); @override State createState() => _ChatMessageAudioPlayerState(); } class _ChatMessageAudioPlayerState extends State { final AudioPlayer _audioPlayer = AudioPlayer(); PlayerState _playerState = PlayerState.stopped; Duration _currentPosition = Duration.zero; Duration _totalDuration = Duration.zero; StreamSubscription? _durationSubscription; StreamSubscription? _positionSubscription; StreamSubscription? _playerCompleteSubscription; StreamSubscription? _playerStateSubscription; @override void initState() { super.initState(); if (widget.audioDuration != null) { _totalDuration = widget.audioDuration!; } _playerStateSubscription = _audioPlayer.onPlayerStateChanged.listen((state) { if (mounted) { setState(() { _playerState = state; }); } }); _positionSubscription = _audioPlayer.onPositionChanged.listen((position) { if (mounted) { setState(() { _currentPosition = position; }); } }); _playerCompleteSubscription = _audioPlayer.onPlayerComplete.listen((event) { if (mounted) { setState(() { _currentPosition = Duration.zero; }); } }); } @override void dispose() { _durationSubscription?.cancel(); _positionSubscription?.cancel(); _playerCompleteSubscription?.cancel(); _playerStateSubscription?.cancel(); _audioPlayer.dispose(); super.dispose(); } Future _togglePlayPause() async { if (_playerState == PlayerState.playing) { await _audioPlayer.pause(); } else { await _audioPlayer.play(DeviceFileSource(widget.audioPath)); _audioPlayer.getDuration().then((duration) { if (mounted && duration != null) { setState(() { _totalDuration = duration; }); } }); } } String _formatDuration(Duration duration) { String twoDigits(int n) => n.toString().padLeft(2, "0"); String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60)); String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60)); return "$twoDigitMinutes:$twoDigitSeconds"; } @override Widget build(BuildContext context) { final color = widget.isUser ? AppColors.surface : AppColors.primary; final backgroundColor = widget.isUser ? AppColors.surface.withOpacity(0.24) : AppColors.divider; final bool isPlaying = _playerState == PlayerState.playing; return Row( mainAxisSize: MainAxisSize.min, children: [ GestureDetector( onTap: _togglePlayPause, child: Icon( isPlaying ? Icons.pause_circle_filled : Icons.play_circle_filled, color: color, size: 32, ), ), const SizedBox(width: 8), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ SliderTheme( data: SliderTheme.of(context).copyWith( trackHeight: 3.0, thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 6.0), overlayShape: const RoundSliderOverlayShape(overlayRadius: 12.0), thumbColor: color, activeTrackColor: color, inactiveTrackColor: backgroundColor, overlayColor: color.withOpacity(0.2), ), child: Slider( value: _currentPosition.inMilliseconds.toDouble().clamp(0.0, _totalDuration.inMilliseconds.toDouble()), min: 0.0, max: _totalDuration.inMilliseconds.toDouble() > 0 ? _totalDuration.inMilliseconds.toDouble() : 1.0, onChanged: (value) async { final position = Duration(milliseconds: value.toInt()); await _audioPlayer.seek(position); }, ), ), const SizedBox(height: 2), Text( "${_formatDuration(_currentPosition)} / ${_formatDuration(_totalDuration)}", style: TextStyle( color: widget.isUser ? AppColors.surface.withOpacity(0.7) : AppColors.textPrimary.withOpacity(0.45), fontSize: 11.0, ), ), ], ), ), ], ); } }