Sync update: 2026-05-18 16:14:25

This commit is contained in:
Hamza-Ayed
2026-05-18 16:14:25 +03:00
parent 7b6e4b3111
commit 905819a1d5
9 changed files with 388 additions and 60 deletions

View File

@@ -1,7 +1,10 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:get/get.dart';
import '../models/message_model.dart';
import '../theme/app_theme.dart';
import '../services/whatsapp_service.dart';
class MessageBubble extends StatelessWidget {
final MessageModel message;
@@ -58,9 +61,9 @@ class MessageBubble extends StatelessWidget {
const SizedBox(height: 4),
],
// Media placeholder if it is media
// Interactive Media widget if message has media
if (message.hasMedia) ...[
_buildMediaPlaceholder(),
InteractiveMediaWidget(message: message),
const SizedBox(height: 6),
],
@@ -108,49 +111,6 @@ class MessageBubble extends StatelessWidget {
);
}
Widget _buildMediaPlaceholder() {
IconData iconData = Icons.insert_drive_file;
String label = "File Attachment";
switch (message.type) {
case "image":
iconData = Icons.photo;
label = "Image";
break;
case "video":
iconData = Icons.videocam;
label = "Video";
break;
case "audio":
iconData = Icons.audiotrack;
label = "Audio File";
break;
case "sticker":
iconData = Icons.emoji_emotions;
label = "Sticker";
break;
}
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.15),
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(iconData, color: AppTheme.textSecondary, size: 32),
const SizedBox(width: 12),
Text(
label,
style: const TextStyle(color: AppTheme.textPrimary, fontWeight: FontWeight.w500),
),
],
),
);
}
Widget _buildAckIcon(int ack) {
switch (ack) {
case 1: // Pending
@@ -172,3 +132,184 @@ class MessageBubble extends StatelessWidget {
return DateFormat('h:mm a').format(dt);
}
}
class InteractiveMediaWidget extends StatefulWidget {
final MessageModel message;
const InteractiveMediaWidget({super.key, required this.message});
@override
State<InteractiveMediaWidget> createState() => _InteractiveMediaWidgetState();
}
class _InteractiveMediaWidgetState extends State<InteractiveMediaWidget> {
final WhatsAppService _svc = Get.find<WhatsAppService>();
bool _isLoading = false;
@override
Widget build(BuildContext context) {
return Obx(() {
final cachedMedia = _svc.mediaCache[widget.message.id];
if (cachedMedia != null) {
return _buildDownloadedMedia(cachedMedia);
}
if (_isLoading) {
return Container(
padding: const EdgeInsets.all(16),
width: 140,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.15),
borderRadius: BorderRadius.circular(8),
),
child: const SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(strokeWidth: 2, color: AppTheme.primary),
),
);
}
// Tap to download media placeholder
return GestureDetector(
onTap: _downloadMedia,
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.15),
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(_getIcon(), color: AppTheme.textSecondary, size: 32),
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
_getLabel(),
style: const TextStyle(color: AppTheme.textPrimary, fontWeight: FontWeight.w500, fontSize: 13),
),
const SizedBox(height: 2),
const Text(
'Tap to download',
style: TextStyle(color: AppTheme.textSecondary, fontSize: 10),
),
],
),
],
),
),
);
});
}
Future<void> _downloadMedia() async {
setState(() => _isLoading = true);
await _svc.downloadAndCacheMedia(widget.message.id);
if (mounted) {
setState(() => _isLoading = false);
}
}
Widget _buildDownloadedMedia(String base64Data) {
final bytes = base64Decode(base64Data);
if (widget.message.type == "image" || widget.message.type == "sticker") {
return ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.memory(
bytes,
fit: BoxFit.cover,
maxHeight: 250,
width: double.infinity,
),
);
}
if (widget.message.type == "audio") {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.15),
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.play_arrow, color: AppTheme.primary, size: 24),
onPressed: () {
Get.snackbar(
'Audio Playback',
'Playing voice note/audio file...',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: AppTheme.surfaceLight,
colorText: AppTheme.textPrimary,
);
},
),
const Expanded(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 8),
child: LinearProgressIndicator(
value: 0.0,
backgroundColor: AppTheme.surfaceLight,
color: AppTheme.primary,
),
),
),
const Text(
'Voice Note',
style: TextStyle(color: AppTheme.textSecondary, fontSize: 11),
),
],
),
);
}
// Default download complete file placeholder
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.15),
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.check_circle_outline, color: AppTheme.primary, size: 32),
const SizedBox(width: 12),
Text(
_getLabel(),
style: const TextStyle(color: AppTheme.textPrimary, fontWeight: FontWeight.w500),
),
],
),
);
}
IconData _getIcon() {
switch (widget.message.type) {
case "image": return Icons.photo_outlined;
case "video": return Icons.videocam_outlined;
case "audio": return Icons.audiotrack_outlined;
case "sticker": return Icons.emoji_emotions_outlined;
default: return Icons.insert_drive_file_outlined;
}
}
String _getLabel() {
switch (widget.message.type) {
case "image": return "Image Attachment";
case "video": return "Video Attachment";
case "audio": return "Audio / Voice Note";
case "sticker": return "Sticker Attachment";
default: return "File Attachment";
}
}
}