feat: integrate real AudioPlayer, real ImagePicker for Camera/Gallery, and on-the-fly OGG-to-MP3 converter on server

This commit is contained in:
Hamza-Ayed
2026-05-18 17:14:43 +03:00
parent 25bdf1fba1
commit 4ccd90dad3
11 changed files with 368 additions and 41 deletions

View File

@@ -3,6 +3,7 @@ import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:get/get.dart';
import 'package:audioplayers/audioplayers.dart';
import '../models/message_model.dart';
import '../theme/app_theme.dart';
import '../services/whatsapp_service.dart';
@@ -146,46 +147,80 @@ class _InteractiveMediaWidgetState extends State<InteractiveMediaWidget> {
final WhatsAppService _svc = Get.find<WhatsAppService>();
bool _isLoading = false;
// Audio simulation state
// Audio player state
final AudioPlayer _player = AudioPlayer();
StreamSubscription? _posSub;
StreamSubscription? _durSub;
StreamSubscription? _stateSub;
bool _isPlaying = false;
double _audioProgress = 0.0;
int _audioDurationSeconds = 12;
int _audioDurationSeconds = 1;
int _audioCurrentSeconds = 0;
Timer? _audioTimer;
@override
void dispose() {
_audioTimer?.cancel();
super.dispose();
}
void _toggleAudioPlayback() {
if (_isPlaying) {
_audioTimer?.cancel();
setState(() {
_isPlaying = false;
});
} else {
setState(() {
_isPlaying = true;
});
const intervalMs = 100;
_audioTimer = Timer.periodic(const Duration(milliseconds: intervalMs), (timer) {
if (!mounted) {
timer.cancel();
return;
}
void initState() {
super.initState();
_posSub = _player.onPositionChanged.listen((p) {
if (mounted) {
setState(() {
_audioProgress += intervalMs / (_audioDurationSeconds * 1000);
_audioCurrentSeconds = (_audioProgress * _audioDurationSeconds).floor();
if (_audioProgress >= 1.0) {
_audioCurrentSeconds = p.inSeconds;
if (_audioDurationSeconds > 0) {
_audioProgress = p.inMilliseconds / (_audioDurationSeconds * 1000);
if (_audioProgress > 1.0) _audioProgress = 1.0;
}
});
}
});
_durSub = _player.onDurationChanged.listen((d) {
if (mounted) {
setState(() {
_audioDurationSeconds = d.inSeconds > 0 ? d.inSeconds : 1;
});
}
});
_stateSub = _player.onPlayerStateChanged.listen((s) {
if (mounted) {
setState(() {
_isPlaying = s == PlayerState.playing;
if (s == PlayerState.completed) {
_audioProgress = 0.0;
_audioCurrentSeconds = 0;
_isPlaying = false;
timer.cancel();
}
});
});
}
});
}
@override
void dispose() {
_posSub?.cancel();
_durSub?.cancel();
_stateSub?.cancel();
_player.dispose();
super.dispose();
}
void _toggleAudioPlayback(String base64Data) async {
try {
if (_isPlaying) {
await _player.pause();
} else {
final bytes = base64Decode(base64Data);
await _player.play(BytesSource(bytes));
}
} catch (e) {
print('[AUDIO PLAYBACK ERROR] $e');
Get.snackbar(
'Playback Error',
'Could not play audio message: $e',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.redAccent.withOpacity(0.8),
colorText: Colors.white,
);
}
}
@@ -292,7 +327,7 @@ class _InteractiveMediaWidgetState extends State<InteractiveMediaWidget> {
color: AppTheme.primary,
size: 24,
),
onPressed: _toggleAudioPlayback,
onPressed: () => _toggleAudioPlayback(base64Data),
),
Expanded(
child: Padding(