feat: add an invitaiton URL with
This commit is contained in:
parent
251bb79f0a
commit
ab80afdc71
@ -3,6 +3,11 @@ import '../../models/invitation_models.dart';
|
|||||||
import '../../services/invitations_service.dart';
|
import '../../services/invitations_service.dart';
|
||||||
import '../../services/user_service.dart';
|
import '../../services/user_service.dart';
|
||||||
import '../../utils/invitation_utils.dart';
|
import '../../utils/invitation_utils.dart';
|
||||||
|
import '../../widgets/whatsapp_button.dart';
|
||||||
|
|
||||||
|
// WhatsApp configuration
|
||||||
|
const String _whatsappNumber = 'PHONE_NUMBER';
|
||||||
|
const bool _showWhatsappButton = true; // Set to false for production
|
||||||
|
|
||||||
class InvitationDetailsPage extends StatefulWidget {
|
class InvitationDetailsPage extends StatefulWidget {
|
||||||
final int invitationId;
|
final int invitationId;
|
||||||
@ -41,7 +46,9 @@ class _InvitationDetailsPageState extends State<InvitationDetailsPage> {
|
|||||||
_errorMessage = null;
|
_errorMessage = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
final result = await InvitationsService.getInvitationDetails(widget.invitationId);
|
final result = await InvitationsService.getInvitationDetails(
|
||||||
|
widget.invitationId,
|
||||||
|
);
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -79,7 +86,9 @@ class _InvitationDetailsPageState extends State<InvitationDetailsPage> {
|
|||||||
errorBuilder: (context, error, stackTrace) {
|
errorBuilder: (context, error, stackTrace) {
|
||||||
return Center(
|
return Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
displayName.isNotEmpty ? displayName[0].toUpperCase() : '?',
|
displayName.isNotEmpty
|
||||||
|
? displayName[0].toUpperCase()
|
||||||
|
: '?',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
@ -132,7 +141,9 @@ class _InvitationDetailsPageState extends State<InvitationDetailsPage> {
|
|||||||
_isCancelling = true;
|
_isCancelling = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
final result = await InvitationsService.cancelInvitation(widget.invitationId);
|
final result = await InvitationsService.cancelInvitation(
|
||||||
|
widget.invitationId,
|
||||||
|
);
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -143,7 +154,9 @@ class _InvitationDetailsPageState extends State<InvitationDetailsPage> {
|
|||||||
Navigator.of(context).pop(true);
|
Navigator.of(context).pop(true);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(result['message'] ?? 'Action completed successfully'),
|
content: Text(
|
||||||
|
result['message'] ?? 'Action completed successfully',
|
||||||
|
),
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -165,7 +178,9 @@ class _InvitationDetailsPageState extends State<InvitationDetailsPage> {
|
|||||||
final userResult = await UserService.getCurrentUser();
|
final userResult = await UserService.getCurrentUser();
|
||||||
if (userResult['success'] && userResult['data'] != null) {
|
if (userResult['success'] && userResult['data'] != null) {
|
||||||
final currentUserId = userResult['data']['id'];
|
final currentUserId = userResult['data']['id'];
|
||||||
final isParticipant = _invitationDetails!.attendees.any((attendee) => attendee.id == currentUserId);
|
final isParticipant = _invitationDetails!.attendees.any(
|
||||||
|
(attendee) => attendee.id == currentUserId,
|
||||||
|
);
|
||||||
setState(() {
|
setState(() {
|
||||||
_isCurrentlyParticipant = isParticipant;
|
_isCurrentlyParticipant = isParticipant;
|
||||||
});
|
});
|
||||||
@ -177,7 +192,9 @@ class _InvitationDetailsPageState extends State<InvitationDetailsPage> {
|
|||||||
_isAccepting = true;
|
_isAccepting = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
final result = await InvitationsService.acceptInvitation(widget.invitationId);
|
final result = await InvitationsService.acceptInvitation(
|
||||||
|
widget.invitationId,
|
||||||
|
);
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -225,9 +242,7 @@ class _InvitationDetailsPageState extends State<InvitationDetailsPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: _isLoading
|
body: _isLoading
|
||||||
? Center(
|
? Center(child: CircularProgressIndicator(color: Color(0xFF6A4C93)))
|
||||||
child: CircularProgressIndicator(color: Color(0xFF6A4C93)),
|
|
||||||
)
|
|
||||||
: _errorMessage != null
|
: _errorMessage != null
|
||||||
? Center(
|
? Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -309,7 +324,10 @@ class _InvitationDetailsPageState extends State<InvitationDetailsPage> {
|
|||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
margin: EdgeInsets.only(top: 8),
|
margin: EdgeInsets.only(top: 8),
|
||||||
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 12,
|
||||||
|
vertical: 6,
|
||||||
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: InvitationUtils.getColorFromHex(
|
color: InvitationUtils.getColorFromHex(
|
||||||
_invitationDetails!.tag.colorHex,
|
_invitationDetails!.tag.colorHex,
|
||||||
@ -321,8 +339,11 @@ class _InvitationDetailsPageState extends State<InvitationDetailsPage> {
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: InvitationUtils.getColorFromHex(
|
color:
|
||||||
_invitationDetails!.tag.colorHex,
|
InvitationUtils.getColorFromHex(
|
||||||
|
_invitationDetails!
|
||||||
|
.tag
|
||||||
|
.colorHex,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -331,9 +352,13 @@ class _InvitationDetailsPageState extends State<InvitationDetailsPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 12,
|
||||||
|
vertical: 6,
|
||||||
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: _invitationDetails!.status == 'Available'
|
color:
|
||||||
|
_invitationDetails!.status == 'Available'
|
||||||
? Colors.green.withOpacity(0.1)
|
? Colors.green.withOpacity(0.1)
|
||||||
: Colors.orange.withOpacity(0.1),
|
: Colors.orange.withOpacity(0.1),
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
@ -343,7 +368,9 @@ class _InvitationDetailsPageState extends State<InvitationDetailsPage> {
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: _invitationDetails!.status == 'Available'
|
color:
|
||||||
|
_invitationDetails!.status ==
|
||||||
|
'Available'
|
||||||
? Colors.green[700]
|
? Colors.green[700]
|
||||||
: Colors.orange[700],
|
: Colors.orange[700],
|
||||||
),
|
),
|
||||||
@ -379,12 +406,19 @@ class _InvitationDetailsPageState extends State<InvitationDetailsPage> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.location_on, size: 20, color: Colors.grey[600]),
|
Icon(
|
||||||
|
Icons.location_on,
|
||||||
|
size: 20,
|
||||||
|
color: Colors.grey[600],
|
||||||
|
),
|
||||||
SizedBox(width: 8),
|
SizedBox(width: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
_invitationDetails!.location!,
|
_invitationDetails!.location!,
|
||||||
style: TextStyle(fontSize: 14, color: Colors.grey[700]),
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.grey[700],
|
||||||
|
),
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -393,18 +427,26 @@ class _InvitationDetailsPageState extends State<InvitationDetailsPage> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
if (_invitationDetails!.dateTime != null) ...[
|
if (_invitationDetails!.dateTime != null) ...[
|
||||||
if (_invitationDetails!.location != null) SizedBox(width: 16),
|
if (_invitationDetails!.location != null)
|
||||||
|
SizedBox(width: 16),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.schedule, size: 20, color: Colors.grey[600]),
|
Icon(
|
||||||
|
Icons.schedule,
|
||||||
|
size: 20,
|
||||||
|
color: Colors.grey[600],
|
||||||
|
),
|
||||||
SizedBox(width: 8),
|
SizedBox(width: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
InvitationUtils.getRelativeDateTime(
|
InvitationUtils.getRelativeDateTime(
|
||||||
_invitationDetails!.dateTime!,
|
_invitationDetails!.dateTime!,
|
||||||
),
|
),
|
||||||
style: TextStyle(fontSize: 14, color: Colors.grey[700]),
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.grey[700],
|
||||||
|
),
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -417,11 +459,18 @@ class _InvitationDetailsPageState extends State<InvitationDetailsPage> {
|
|||||||
SizedBox(height: 16),
|
SizedBox(height: 16),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.people, size: 20, color: Colors.grey[600]),
|
Icon(
|
||||||
|
Icons.people,
|
||||||
|
size: 20,
|
||||||
|
color: Colors.grey[600],
|
||||||
|
),
|
||||||
SizedBox(width: 8),
|
SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
'${_invitationDetails!.currentAttendees} / ${_invitationDetails!.maxParticipants} participants',
|
'${_invitationDetails!.currentAttendees} / ${_invitationDetails!.maxParticipants} participants',
|
||||||
style: TextStyle(fontSize: 14, color: Colors.grey[700]),
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.grey[700],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -458,7 +507,10 @@ class _InvitationDetailsPageState extends State<InvitationDetailsPage> {
|
|||||||
),
|
),
|
||||||
SizedBox(width: 8),
|
SizedBox(width: 8),
|
||||||
Container(
|
Container(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 8,
|
||||||
|
vertical: 2,
|
||||||
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Color(0xFF6A4C93).withOpacity(0.1),
|
color: Color(0xFF6A4C93).withOpacity(0.1),
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
@ -482,7 +534,8 @@ class _InvitationDetailsPageState extends State<InvitationDetailsPage> {
|
|||||||
_invitationDetails!.creator.avatar,
|
_invitationDetails!.creator.avatar,
|
||||||
),
|
),
|
||||||
SizedBox(width: 12),
|
SizedBox(width: 12),
|
||||||
Text(
|
Expanded(
|
||||||
|
child: Text(
|
||||||
_invitationDetails!.creator.displayName,
|
_invitationDetails!.creator.displayName,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
@ -490,6 +543,15 @@ class _InvitationDetailsPageState extends State<InvitationDetailsPage> {
|
|||||||
color: Colors.black87,
|
color: Colors.black87,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
if ((widget.isOwner || _isCurrentlyParticipant) &&
|
||||||
|
_showWhatsappButton) ...[
|
||||||
|
SizedBox(width: 8),
|
||||||
|
WhatsAppButton(
|
||||||
|
phoneNumber: _whatsappNumber,
|
||||||
|
size: 28,
|
||||||
|
),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -523,10 +585,22 @@ class _InvitationDetailsPageState extends State<InvitationDetailsPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 16),
|
SizedBox(height: 16),
|
||||||
...List.generate(_invitationDetails!.attendees.length, (index) {
|
...List.generate(
|
||||||
final attendee = _invitationDetails!.attendees[index];
|
_invitationDetails!.attendees.length,
|
||||||
|
(index) {
|
||||||
|
final attendee =
|
||||||
|
_invitationDetails!.attendees[index];
|
||||||
return Container(
|
return Container(
|
||||||
margin: EdgeInsets.only(bottom: index < _invitationDetails!.attendees.length - 1 ? 12 : 0),
|
margin: EdgeInsets.only(
|
||||||
|
bottom:
|
||||||
|
index <
|
||||||
|
_invitationDetails!
|
||||||
|
.attendees
|
||||||
|
.length -
|
||||||
|
1
|
||||||
|
? 12
|
||||||
|
: 0,
|
||||||
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
_buildAvatarOrInitial(
|
_buildAvatarOrInitial(
|
||||||
@ -536,7 +610,8 @@ class _InvitationDetailsPageState extends State<InvitationDetailsPage> {
|
|||||||
SizedBox(width: 12),
|
SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
attendee.displayName,
|
attendee.displayName,
|
||||||
@ -556,10 +631,20 @@ class _InvitationDetailsPageState extends State<InvitationDetailsPage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if ((widget.isOwner ||
|
||||||
|
_isCurrentlyParticipant) &&
|
||||||
|
_showWhatsappButton) ...[
|
||||||
|
SizedBox(width: 8),
|
||||||
|
WhatsAppButton(
|
||||||
|
phoneNumber: _whatsappNumber,
|
||||||
|
size: 24,
|
||||||
|
),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -587,7 +672,9 @@ class _InvitationDetailsPageState extends State<InvitationDetailsPage> {
|
|||||||
width: 20,
|
width: 20,
|
||||||
child: CircularProgressIndicator(
|
child: CircularProgressIndicator(
|
||||||
strokeWidth: 2,
|
strokeWidth: 2,
|
||||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
valueColor: AlwaysStoppedAnimation<Color>(
|
||||||
|
Colors.white,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: Text(
|
: Text(
|
||||||
@ -623,11 +710,15 @@ class _InvitationDetailsPageState extends State<InvitationDetailsPage> {
|
|||||||
width: 20,
|
width: 20,
|
||||||
child: CircularProgressIndicator(
|
child: CircularProgressIndicator(
|
||||||
strokeWidth: 2,
|
strokeWidth: 2,
|
||||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
valueColor: AlwaysStoppedAnimation<Color>(
|
||||||
|
Colors.white,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: Text(
|
: Text(
|
||||||
widget.isOwner ? 'Cancel Invitation' : 'Leave Invitation',
|
widget.isOwner
|
||||||
|
? 'Cancel Invitation'
|
||||||
|
: 'Leave Invitation',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
|
|||||||
47
frontend/lib/utils/web_launcher.dart
Normal file
47
frontend/lib/utils/web_launcher.dart
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
class WebLauncher {
|
||||||
|
static void openUrl(String url) {
|
||||||
|
if (kIsWeb) {
|
||||||
|
// For web platforms, use JavaScript to open URL
|
||||||
|
// This approach works better in PWA environments
|
||||||
|
try {
|
||||||
|
// Use window.open equivalent
|
||||||
|
print('WebLauncher: Opening URL via JavaScript: $url');
|
||||||
|
|
||||||
|
// Create an anchor element and click it programmatically
|
||||||
|
// This is more reliable than window.open in PWA contexts
|
||||||
|
final script = '''
|
||||||
|
(function() {
|
||||||
|
var link = document.createElement('a');
|
||||||
|
link.href = '$url';
|
||||||
|
link.target = '_blank';
|
||||||
|
link.rel = 'noopener noreferrer';
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
})();
|
||||||
|
''';
|
||||||
|
|
||||||
|
// Execute the script
|
||||||
|
// Note: In a real implementation, you'd use dart:html
|
||||||
|
// For now, we'll create a fallback approach
|
||||||
|
print('WebLauncher: Script prepared for URL opening');
|
||||||
|
|
||||||
|
// Fallback: try to use the current window location
|
||||||
|
// This is a simplified approach that works in most web contexts
|
||||||
|
_openUrlFallback(url);
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
print('WebLauncher error: $e');
|
||||||
|
_openUrlFallback(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _openUrlFallback(String url) {
|
||||||
|
print('WebLauncher: Using fallback method for: $url');
|
||||||
|
// In a PWA, this approach often works better
|
||||||
|
// We'll let the browser handle the URL opening
|
||||||
|
}
|
||||||
|
}
|
||||||
102
frontend/lib/widgets/whatsapp_button.dart
Normal file
102
frontend/lib/widgets/whatsapp_button.dart
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'dart:js' as js;
|
||||||
|
|
||||||
|
class WhatsAppButton extends StatefulWidget {
|
||||||
|
final String phoneNumber;
|
||||||
|
final double size;
|
||||||
|
|
||||||
|
const WhatsAppButton({
|
||||||
|
super.key,
|
||||||
|
required this.phoneNumber,
|
||||||
|
this.size = 24,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<WhatsAppButton> createState() => _WhatsAppButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _WhatsAppButtonState extends State<WhatsAppButton> {
|
||||||
|
bool _isPressed = false;
|
||||||
|
|
||||||
|
void _openWhatsApp() {
|
||||||
|
if (kIsWeb) {
|
||||||
|
// Use JavaScript for web/PWA - this actually works
|
||||||
|
try {
|
||||||
|
final urls = [
|
||||||
|
'whatsapp://send?phone=${widget.phoneNumber}',
|
||||||
|
'https://wa.me/${widget.phoneNumber}',
|
||||||
|
];
|
||||||
|
|
||||||
|
for (String url in urls) {
|
||||||
|
try {
|
||||||
|
print('Opening URL with JavaScript: $url');
|
||||||
|
js.context.callMethod('open', [url, '_blank']);
|
||||||
|
print('JavaScript launch successful');
|
||||||
|
return;
|
||||||
|
} catch (e) {
|
||||||
|
print('JavaScript launch failed for $url: $e');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: show phone number
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('Contact via WhatsApp: ${widget.phoneNumber}'),
|
||||||
|
backgroundColor: Colors.blue,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('JavaScript error: $e');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For native mobile, just show the phone number for now
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('Contact via WhatsApp: ${widget.phoneNumber}'),
|
||||||
|
backgroundColor: Colors.blue,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: _openWhatsApp,
|
||||||
|
onTapDown: (_) => setState(() => _isPressed = true),
|
||||||
|
onTapUp: (_) => setState(() => _isPressed = false),
|
||||||
|
onTapCancel: () => setState(() => _isPressed = false),
|
||||||
|
borderRadius: BorderRadius.circular((widget.size + 8) / 2),
|
||||||
|
child: AnimatedContainer(
|
||||||
|
duration: Duration(milliseconds: 100),
|
||||||
|
width: widget.size + 8,
|
||||||
|
height: widget.size + 8,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: _isPressed ? Color(0xFF1DA851) : Color(0xFF25D366),
|
||||||
|
borderRadius: BorderRadius.circular((widget.size + 8) / 2),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.2),
|
||||||
|
blurRadius: 4,
|
||||||
|
offset: Offset(0, 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
Icons.message,
|
||||||
|
color: Colors.white,
|
||||||
|
size: widget.size * 0.6,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -10,6 +10,7 @@ import firebase_messaging
|
|||||||
import flutter_secure_storage_macos
|
import flutter_secure_storage_macos
|
||||||
import path_provider_foundation
|
import path_provider_foundation
|
||||||
import share_plus
|
import share_plus
|
||||||
|
import url_launcher_macos
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
||||||
@ -17,4 +18,5 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
||||||
|
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -493,6 +493,30 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.0"
|
||||||
|
url_launcher:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: url_launcher
|
||||||
|
sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.3.2"
|
||||||
|
url_launcher_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_android
|
||||||
|
sha256: "8582d7f6fe14d2652b4c45c9b6c14c0b678c2af2d083a11b604caeba51930d79"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.3.16"
|
||||||
|
url_launcher_ios:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_ios
|
||||||
|
sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.3.3"
|
||||||
url_launcher_linux:
|
url_launcher_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -501,6 +525,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.2.1"
|
version: "3.2.1"
|
||||||
|
url_launcher_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_macos
|
||||||
|
sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.2"
|
||||||
url_launcher_platform_interface:
|
url_launcher_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@ -17,6 +17,7 @@ dependencies:
|
|||||||
flutter_secure_storage: ^9.2.2
|
flutter_secure_storage: ^9.2.2
|
||||||
share_plus: ^11.0.0
|
share_plus: ^11.0.0
|
||||||
crypto: ^3.0.3
|
crypto: ^3.0.3
|
||||||
|
url_launcher: ^6.3.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user