feat: integrate real AudioPlayer, real ImagePicker for Camera/Gallery, and on-the-fly OGG-to-MP3 converter on server
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import '../controllers/chat_controller.dart';
|
||||
import '../models/conversation_model.dart';
|
||||
import '../models/message_model.dart';
|
||||
@@ -232,19 +234,21 @@ class ChatScreen extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
_buildAttachmentItem(
|
||||
icon: Icons.photo,
|
||||
color: Colors.purple,
|
||||
label: 'Photo',
|
||||
icon: Icons.camera_alt,
|
||||
color: Colors.green,
|
||||
label: 'Camera',
|
||||
onTap: () {
|
||||
Get.back();
|
||||
// Real red dot 5x5 pixel PNG base64
|
||||
const base64Photo = 'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==';
|
||||
ctrl.sendMediaMessage(
|
||||
base64Photo,
|
||||
'image/png',
|
||||
'photo.png',
|
||||
caption: '📸 Photo sent from Mywhatsapp App!',
|
||||
);
|
||||
_pickAndSendImage(ctrl, ImageSource.camera);
|
||||
},
|
||||
),
|
||||
_buildAttachmentItem(
|
||||
icon: Icons.photo_library,
|
||||
color: Colors.purple,
|
||||
label: 'Gallery',
|
||||
onTap: () {
|
||||
Get.back();
|
||||
_pickAndSendImage(ctrl, ImageSource.gallery);
|
||||
},
|
||||
),
|
||||
_buildAttachmentItem(
|
||||
@@ -272,6 +276,41 @@ class ChatScreen extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
void _pickAndSendImage(ChatController ctrl, ImageSource source) async {
|
||||
try {
|
||||
final ImagePicker picker = ImagePicker();
|
||||
final XFile? image = await picker.pickImage(
|
||||
source: source,
|
||||
imageQuality: 75,
|
||||
);
|
||||
if (image == null) return;
|
||||
|
||||
final bytes = await image.readAsBytes();
|
||||
final base64String = base64Encode(bytes);
|
||||
|
||||
String mimetype = 'image/jpeg';
|
||||
if (image.path.toLowerCase().endsWith('.png')) {
|
||||
mimetype = 'image/png';
|
||||
} else if (image.path.toLowerCase().endsWith('.gif')) {
|
||||
mimetype = 'image/gif';
|
||||
}
|
||||
|
||||
await ctrl.sendMediaMessage(
|
||||
base64String,
|
||||
mimetype,
|
||||
image.name,
|
||||
caption: '📸 Photo sent via Mywhatsapp!',
|
||||
);
|
||||
} catch (e) {
|
||||
Get.snackbar(
|
||||
'Error picking image',
|
||||
e.toString(),
|
||||
backgroundColor: Colors.redAccent.withOpacity(0.8),
|
||||
colorText: Colors.white,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildAttachmentItem({
|
||||
required IconData icon,
|
||||
required Color color,
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user