157 lines
4.9 KiB
Dart
157 lines
4.9 KiB
Dart
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<ChatMessageAudioPlayer> createState() => _ChatMessageAudioPlayerState();
|
|
}
|
|
|
|
class _ChatMessageAudioPlayerState extends State<ChatMessageAudioPlayer> {
|
|
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<void> _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,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
} |