feat: add an accept button in view invitations page
This commit is contained in:
parent
c9316b5190
commit
80a280a4aa
@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import '../../models/invitation_models.dart';
|
import '../../models/invitation_models.dart';
|
||||||
import '../../services/invitations_service.dart';
|
import '../../services/invitations_service.dart';
|
||||||
|
import '../../services/user_service.dart';
|
||||||
import '../../utils/invitation_utils.dart';
|
import '../../utils/invitation_utils.dart';
|
||||||
|
|
||||||
class InvitationDetailsPage extends StatefulWidget {
|
class InvitationDetailsPage extends StatefulWidget {
|
||||||
@ -23,11 +24,14 @@ class _InvitationDetailsPageState extends State<InvitationDetailsPage> {
|
|||||||
InvitationDetails? _invitationDetails;
|
InvitationDetails? _invitationDetails;
|
||||||
bool _isLoading = true;
|
bool _isLoading = true;
|
||||||
bool _isCancelling = false;
|
bool _isCancelling = false;
|
||||||
|
bool _isAccepting = false;
|
||||||
|
bool _isCurrentlyParticipant = false;
|
||||||
String? _errorMessage;
|
String? _errorMessage;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
_isCurrentlyParticipant = widget.isParticipant;
|
||||||
_loadInvitationDetails();
|
_loadInvitationDetails();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,6 +52,11 @@ class _InvitationDetailsPageState extends State<InvitationDetailsPage> {
|
|||||||
_errorMessage = result['message'];
|
_errorMessage = result['message'];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Update participation status after loading details
|
||||||
|
if (result['success']) {
|
||||||
|
await _updateParticipationStatus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,6 +159,52 @@ class _InvitationDetailsPageState extends State<InvitationDetailsPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _updateParticipationStatus() async {
|
||||||
|
if (_invitationDetails == null) return;
|
||||||
|
|
||||||
|
final userResult = await UserService.getCurrentUser();
|
||||||
|
if (userResult['success'] && userResult['data'] != null) {
|
||||||
|
final currentUserId = userResult['data']['id'];
|
||||||
|
final isParticipant = _invitationDetails!.attendees.any((attendee) => attendee.id == currentUserId);
|
||||||
|
setState(() {
|
||||||
|
_isCurrentlyParticipant = isParticipant;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _acceptInvitation() async {
|
||||||
|
setState(() {
|
||||||
|
_isAccepting = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
final result = await InvitationsService.acceptInvitation(widget.invitationId);
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_isAccepting = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result['success']) {
|
||||||
|
// Reload invitation details to reflect the new state
|
||||||
|
await _loadInvitationDetails();
|
||||||
|
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('Invitation accepted successfully!'),
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(result['message'] ?? 'Failed to accept invitation'),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return PopScope(
|
return PopScope(
|
||||||
@ -509,7 +564,45 @@ class _InvitationDetailsPageState extends State<InvitationDetailsPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
if (widget.isParticipant) ...[
|
|
||||||
|
// Accept button for non-participants who are not owners
|
||||||
|
if (!_isCurrentlyParticipant && !widget.isOwner) ...[
|
||||||
|
SizedBox(height: 32),
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 56,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: _isAccepting ? null : _acceptInvitation,
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Color(0xFF6A4C93),
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
elevation: 2,
|
||||||
|
),
|
||||||
|
child: _isAccepting
|
||||||
|
? SizedBox(
|
||||||
|
height: 20,
|
||||||
|
width: 20,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2,
|
||||||
|
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Text(
|
||||||
|
'Accept Invitation',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
|
||||||
|
// Cancel/Leave button for participants
|
||||||
|
if (_isCurrentlyParticipant) ...[
|
||||||
SizedBox(height: 32),
|
SizedBox(height: 32),
|
||||||
Container(
|
Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
|
|||||||
@ -49,7 +49,9 @@ class _InvitationsPageState extends State<InvitationsPage>
|
|||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
_errorMessage = null;
|
_errorMessage = null;
|
||||||
});
|
});
|
||||||
print('Invitations UI updated with ${updatedInvitationsData.created.length + updatedInvitationsData.accepted.length + updatedInvitationsData.available.length} total invitations');
|
print(
|
||||||
|
'Invitations UI updated with ${updatedInvitationsData.created.length + updatedInvitationsData.accepted.length + updatedInvitationsData.available.length} total invitations',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError: (error) {
|
onError: (error) {
|
||||||
@ -69,7 +71,9 @@ class _InvitationsPageState extends State<InvitationsPage>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
final result = await InvitationsService.getAllInvitations(forceRefresh: forceRefresh);
|
final result = await InvitationsService.getAllInvitations(
|
||||||
|
forceRefresh: forceRefresh,
|
||||||
|
);
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -537,70 +541,73 @@ class _InvitationsPageState extends State<InvitationsPage>
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(
|
title: Text(
|
||||||
'Invitations',
|
'Invitations',
|
||||||
style: TextStyle(fontWeight: FontWeight.w600),
|
style: TextStyle(fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
foregroundColor: Color(0xFF6A4C93),
|
||||||
|
elevation: 0,
|
||||||
|
bottom: PreferredSize(
|
||||||
|
preferredSize: Size.fromHeight(1),
|
||||||
|
child: Container(height: 1, color: Colors.grey[200]),
|
||||||
|
),
|
||||||
|
automaticallyImplyLeading: false,
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.refresh),
|
||||||
|
onPressed: () => _loadInvitations(forceRefresh: true),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
backgroundColor: Colors.white,
|
body: _isLoading
|
||||||
foregroundColor: Color(0xFF6A4C93),
|
? Center(child: CircularProgressIndicator(color: Color(0xFF6A4C93)))
|
||||||
elevation: 0,
|
: _errorMessage != null
|
||||||
bottom: PreferredSize(
|
? _buildErrorState()
|
||||||
preferredSize: Size.fromHeight(1),
|
: _invitationsData?.isEmpty ?? true
|
||||||
child: Container(height: 1, color: Colors.grey[200]),
|
? _buildEmptyState()
|
||||||
),
|
: RefreshIndicator(
|
||||||
automaticallyImplyLeading: false,
|
onRefresh: () => _loadInvitations(forceRefresh: true),
|
||||||
actions: [
|
color: Color(0xFF6A4C93),
|
||||||
IconButton(icon: Icon(Icons.refresh), onPressed: () => _loadInvitations(forceRefresh: true)),
|
child: SingleChildScrollView(
|
||||||
],
|
physics: AlwaysScrollableScrollPhysics(),
|
||||||
),
|
child: Column(
|
||||||
body: _isLoading
|
children: [
|
||||||
? Center(child: CircularProgressIndicator(color: Color(0xFF6A4C93)))
|
SizedBox(height: 16),
|
||||||
: _errorMessage != null
|
_buildInvitationSection(
|
||||||
? _buildErrorState()
|
'My Invitations',
|
||||||
: _invitationsData?.isEmpty ?? true
|
_invitationsData?.created ?? [],
|
||||||
? _buildEmptyState()
|
'created',
|
||||||
: RefreshIndicator(
|
),
|
||||||
onRefresh: () => _loadInvitations(forceRefresh: true),
|
_buildInvitationSection(
|
||||||
color: Color(0xFF6A4C93),
|
'Accepted Invitations',
|
||||||
child: SingleChildScrollView(
|
_invitationsData?.accepted ?? [],
|
||||||
physics: AlwaysScrollableScrollPhysics(),
|
'accepted',
|
||||||
child: Column(
|
),
|
||||||
children: [
|
_buildInvitationSection(
|
||||||
SizedBox(height: 16),
|
'Available Invitations',
|
||||||
_buildInvitationSection(
|
_invitationsData?.available ?? [],
|
||||||
'My Invitations',
|
'available',
|
||||||
_invitationsData?.created ?? [],
|
),
|
||||||
'created',
|
SizedBox(height: 80),
|
||||||
),
|
],
|
||||||
_buildInvitationSection(
|
),
|
||||||
'Accepted Invitations',
|
|
||||||
_invitationsData?.accepted ?? [],
|
|
||||||
'accepted',
|
|
||||||
),
|
|
||||||
_buildInvitationSection(
|
|
||||||
'Available Invitations',
|
|
||||||
_invitationsData?.available ?? [],
|
|
||||||
'available',
|
|
||||||
),
|
|
||||||
SizedBox(height: 80),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
floatingActionButton: FloatingActionButton(
|
||||||
floatingActionButton: FloatingActionButton(
|
onPressed: () async {
|
||||||
onPressed: () async {
|
final result = await Navigator.push(
|
||||||
final result = await Navigator.push(
|
context,
|
||||||
context,
|
MaterialPageRoute(builder: (context) => CreateInvitationPage()),
|
||||||
MaterialPageRoute(builder: (context) => CreateInvitationPage()),
|
);
|
||||||
);
|
if (result == true) {
|
||||||
if (result == true) {
|
_loadInvitations(forceRefresh: true);
|
||||||
_loadInvitations(forceRefresh: true);
|
}
|
||||||
}
|
},
|
||||||
},
|
backgroundColor: Color(0xFF6A4C93),
|
||||||
backgroundColor: Color(0xFF6A4C93),
|
child: Icon(Icons.add, color: Colors.white),
|
||||||
child: Icon(Icons.add, color: Colors.white),
|
),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -747,366 +754,366 @@ class _CreateInvitationPageState extends State<CreateInvitationPage> {
|
|||||||
canPop: true, // Allow back navigation to go back to invitations page only
|
canPop: true, // Allow back navigation to go back to invitations page only
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
body: Container(
|
body: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
begin: Alignment.topCenter,
|
begin: Alignment.topCenter,
|
||||||
end: Alignment.bottomCenter,
|
end: Alignment.bottomCenter,
|
||||||
colors: [Color(0xFF32B0A5), Color(0xFF4600B9)],
|
colors: [Color(0xFF32B0A5), Color(0xFF4600B9)],
|
||||||
stops: [0.0, 0.5],
|
stops: [0.0, 0.5],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
child: SafeArea(
|
||||||
child: SafeArea(
|
child: Column(
|
||||||
child: Column(
|
children: [
|
||||||
children: [
|
Padding(
|
||||||
Padding(
|
padding: EdgeInsets.all(16),
|
||||||
padding: EdgeInsets.all(16),
|
child: Row(
|
||||||
child: Row(
|
children: [
|
||||||
children: [
|
IconButton(
|
||||||
IconButton(
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
icon: Icon(Icons.arrow_back, color: Colors.white),
|
||||||
icon: Icon(Icons.arrow_back, color: Colors.white),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'Create Invitation',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 20,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
),
|
||||||
),
|
Text(
|
||||||
],
|
'Create Invitation',
|
||||||
),
|
style: TextStyle(
|
||||||
),
|
fontSize: 20,
|
||||||
Expanded(
|
fontWeight: FontWeight.w600,
|
||||||
child: Container(
|
color: Colors.white,
|
||||||
margin: EdgeInsets.all(16),
|
),
|
||||||
padding: EdgeInsets.all(24),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.black.withOpacity(0.1),
|
|
||||||
blurRadius: 20,
|
|
||||||
offset: Offset(0, 5),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
child: Form(
|
),
|
||||||
key: _formKey,
|
Expanded(
|
||||||
child: SingleChildScrollView(
|
child: Container(
|
||||||
child: Column(
|
margin: EdgeInsets.all(16),
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
padding: EdgeInsets.all(24),
|
||||||
children: [
|
decoration: BoxDecoration(
|
||||||
Text(
|
color: Colors.white,
|
||||||
'Create New Invitation',
|
borderRadius: BorderRadius.circular(16),
|
||||||
style: TextStyle(
|
boxShadow: [
|
||||||
fontSize: 24,
|
BoxShadow(
|
||||||
fontWeight: FontWeight.bold,
|
color: Colors.black.withOpacity(0.1),
|
||||||
color: Colors.black87,
|
blurRadius: 20,
|
||||||
),
|
offset: Offset(0, 5),
|
||||||
textAlign: TextAlign.center,
|
),
|
||||||
),
|
],
|
||||||
SizedBox(height: 24),
|
),
|
||||||
|
child: Form(
|
||||||
TextFormField(
|
key: _formKey,
|
||||||
controller: _titleController,
|
child: SingleChildScrollView(
|
||||||
decoration: InputDecoration(
|
child: Column(
|
||||||
labelText: 'Title *',
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
prefixIcon: Icon(Icons.title),
|
children: [
|
||||||
border: OutlineInputBorder(
|
Text(
|
||||||
borderRadius: BorderRadius.circular(12),
|
'Create New Invitation',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.black87,
|
||||||
),
|
),
|
||||||
focusedBorder: OutlineInputBorder(
|
textAlign: TextAlign.center,
|
||||||
borderRadius: BorderRadius.circular(12),
|
),
|
||||||
borderSide: BorderSide(
|
SizedBox(height: 24),
|
||||||
color: Color(0xFF6A4C93),
|
|
||||||
|
TextFormField(
|
||||||
|
controller: _titleController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'Title *',
|
||||||
|
prefixIcon: Icon(Icons.title),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: Color(0xFF6A4C93),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.trim().isEmpty) {
|
||||||
|
return 'Please enter a title';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
|
||||||
|
TextFormField(
|
||||||
|
controller: _descriptionController,
|
||||||
|
maxLines: 3,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'Description *',
|
||||||
|
prefixIcon: Icon(Icons.description),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: Color(0xFF6A4C93),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
alignLabelWithHint: true,
|
||||||
|
),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.trim().isEmpty) {
|
||||||
|
return 'Please enter a description';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: InkWell(
|
||||||
|
onTap: _selectDate,
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
vertical: 16,
|
||||||
|
horizontal: 12,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
color: Colors.grey[400]!,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.calendar_today,
|
||||||
|
color: Colors.grey[600],
|
||||||
|
),
|
||||||
|
SizedBox(width: 12),
|
||||||
|
Text(
|
||||||
|
_selectedDate == null
|
||||||
|
? 'Select Date (Optional)'
|
||||||
|
: '${_selectedDate!.day}/${_selectedDate!.month}/${_selectedDate!.year}',
|
||||||
|
style: TextStyle(
|
||||||
|
color: _selectedDate == null
|
||||||
|
? Colors.grey[600]
|
||||||
|
: Colors.black87,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: InkWell(
|
||||||
|
onTap: _selectTime,
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
vertical: 16,
|
||||||
|
horizontal: 12,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
color: Colors.grey[400]!,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.access_time,
|
||||||
|
color: Colors.grey[600],
|
||||||
|
),
|
||||||
|
SizedBox(width: 12),
|
||||||
|
Text(
|
||||||
|
_selectedTime == null
|
||||||
|
? 'Select Time (Optional)'
|
||||||
|
: '${_selectedTime!.hour}:${_selectedTime!.minute.toString().padLeft(2, '0')}',
|
||||||
|
style: TextStyle(
|
||||||
|
color: _selectedTime == null
|
||||||
|
? Colors.grey[600]
|
||||||
|
: Colors.black87,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
|
||||||
|
TextFormField(
|
||||||
|
controller: _locationController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'Location (Optional)',
|
||||||
|
prefixIcon: Icon(Icons.location_on),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: Color(0xFF6A4C93),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
validator: (value) {
|
SizedBox(height: 16),
|
||||||
if (value == null || value.trim().isEmpty) {
|
|
||||||
return 'Please enter a title';
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
SizedBox(height: 16),
|
|
||||||
|
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: _descriptionController,
|
controller: _maxParticipantsController,
|
||||||
maxLines: 3,
|
keyboardType: TextInputType.number,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Description *',
|
labelText: 'Maximum Participants *',
|
||||||
prefixIcon: Icon(Icons.description),
|
prefixIcon: Icon(Icons.people),
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
focusedBorder: OutlineInputBorder(
|
focusedBorder: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
color: Color(0xFF6A4C93),
|
color: Color(0xFF6A4C93),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
alignLabelWithHint: true,
|
validator: (value) {
|
||||||
|
if (value == null || value.trim().isEmpty) {
|
||||||
|
return 'Please enter maximum participants';
|
||||||
|
}
|
||||||
|
final intValue = int.tryParse(value);
|
||||||
|
if (intValue == null || intValue < 1) {
|
||||||
|
return 'Please enter a valid number greater than 0';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
),
|
),
|
||||||
validator: (value) {
|
SizedBox(height: 20),
|
||||||
if (value == null || value.trim().isEmpty) {
|
|
||||||
return 'Please enter a description';
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
SizedBox(height: 16),
|
|
||||||
|
|
||||||
Row(
|
Text(
|
||||||
children: [
|
'Select Tag *',
|
||||||
Expanded(
|
style: TextStyle(
|
||||||
child: InkWell(
|
fontSize: 16,
|
||||||
onTap: _selectDate,
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Colors.black87,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 12),
|
||||||
|
|
||||||
|
Wrap(
|
||||||
|
spacing: 8,
|
||||||
|
runSpacing: 8,
|
||||||
|
children: List.generate(_availableTags.length, (
|
||||||
|
index,
|
||||||
|
) {
|
||||||
|
final tag = _availableTags[index];
|
||||||
|
final isSelected = _selectedTagIndex == index;
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_selectedTagIndex = index;
|
||||||
|
});
|
||||||
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: EdgeInsets.symmetric(
|
padding: EdgeInsets.symmetric(
|
||||||
vertical: 16,
|
|
||||||
horizontal: 12,
|
horizontal: 12,
|
||||||
|
vertical: 8,
|
||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border.all(
|
|
||||||
color: Colors.grey[400]!,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.calendar_today,
|
|
||||||
color: Colors.grey[600],
|
|
||||||
),
|
|
||||||
SizedBox(width: 12),
|
|
||||||
Text(
|
|
||||||
_selectedDate == null
|
|
||||||
? 'Select Date (Optional)'
|
|
||||||
: '${_selectedDate!.day}/${_selectedDate!.month}/${_selectedDate!.year}',
|
|
||||||
style: TextStyle(
|
|
||||||
color: _selectedDate == null
|
|
||||||
? Colors.grey[600]
|
|
||||||
: Colors.black87,
|
|
||||||
fontSize: 16,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: 12),
|
|
||||||
Expanded(
|
|
||||||
child: InkWell(
|
|
||||||
onTap: _selectTime,
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.symmetric(
|
|
||||||
vertical: 16,
|
|
||||||
horizontal: 12,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border.all(
|
|
||||||
color: Colors.grey[400]!,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.access_time,
|
|
||||||
color: Colors.grey[600],
|
|
||||||
),
|
|
||||||
SizedBox(width: 12),
|
|
||||||
Text(
|
|
||||||
_selectedTime == null
|
|
||||||
? 'Select Time (Optional)'
|
|
||||||
: '${_selectedTime!.hour}:${_selectedTime!.minute.toString().padLeft(2, '0')}',
|
|
||||||
style: TextStyle(
|
|
||||||
color: _selectedTime == null
|
|
||||||
? Colors.grey[600]
|
|
||||||
: Colors.black87,
|
|
||||||
fontSize: 16,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
SizedBox(height: 16),
|
|
||||||
|
|
||||||
TextFormField(
|
|
||||||
controller: _locationController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'Location (Optional)',
|
|
||||||
prefixIcon: Icon(Icons.location_on),
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
focusedBorder: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
borderSide: BorderSide(
|
|
||||||
color: Color(0xFF6A4C93),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: 16),
|
|
||||||
|
|
||||||
TextFormField(
|
|
||||||
controller: _maxParticipantsController,
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'Maximum Participants *',
|
|
||||||
prefixIcon: Icon(Icons.people),
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
focusedBorder: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
borderSide: BorderSide(
|
|
||||||
color: Color(0xFF6A4C93),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
validator: (value) {
|
|
||||||
if (value == null || value.trim().isEmpty) {
|
|
||||||
return 'Please enter maximum participants';
|
|
||||||
}
|
|
||||||
final intValue = int.tryParse(value);
|
|
||||||
if (intValue == null || intValue < 1) {
|
|
||||||
return 'Please enter a valid number greater than 0';
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
SizedBox(height: 20),
|
|
||||||
|
|
||||||
Text(
|
|
||||||
'Select Tag *',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: Colors.black87,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: 12),
|
|
||||||
|
|
||||||
Wrap(
|
|
||||||
spacing: 8,
|
|
||||||
runSpacing: 8,
|
|
||||||
children: List.generate(_availableTags.length, (
|
|
||||||
index,
|
|
||||||
) {
|
|
||||||
final tag = _availableTags[index];
|
|
||||||
final isSelected = _selectedTagIndex == index;
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
setState(() {
|
|
||||||
_selectedTagIndex = index;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.symmetric(
|
|
||||||
horizontal: 12,
|
|
||||||
vertical: 8,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: isSelected
|
|
||||||
? InvitationUtils.getColorFromHex(
|
|
||||||
tag['color_hex'],
|
|
||||||
)
|
|
||||||
: Colors.grey[100],
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
border: Border.all(
|
|
||||||
color: isSelected
|
color: isSelected
|
||||||
? InvitationUtils.getColorFromHex(
|
? InvitationUtils.getColorFromHex(
|
||||||
tag['color_hex'],
|
tag['color_hex'],
|
||||||
)
|
)
|
||||||
: Colors.grey[300]!,
|
: Colors.grey[100],
|
||||||
width: 2,
|
borderRadius: BorderRadius.circular(20),
|
||||||
),
|
border: Border.all(
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
InvitationUtils.getIconFromName(
|
|
||||||
tag['icon_name'],
|
|
||||||
),
|
|
||||||
size: 18,
|
|
||||||
color: isSelected
|
color: isSelected
|
||||||
? Colors.white
|
? InvitationUtils.getColorFromHex(
|
||||||
: InvitationUtils.getColorFromHex(
|
|
||||||
tag['color_hex'],
|
tag['color_hex'],
|
||||||
),
|
)
|
||||||
|
: Colors.grey[300]!,
|
||||||
|
width: 2,
|
||||||
),
|
),
|
||||||
SizedBox(width: 6),
|
),
|
||||||
Text(
|
child: Row(
|
||||||
tag['name'],
|
mainAxisSize: MainAxisSize.min,
|
||||||
style: TextStyle(
|
children: [
|
||||||
|
Icon(
|
||||||
|
InvitationUtils.getIconFromName(
|
||||||
|
tag['icon_name'],
|
||||||
|
),
|
||||||
|
size: 18,
|
||||||
color: isSelected
|
color: isSelected
|
||||||
? Colors.white
|
? Colors.white
|
||||||
: Colors.black87,
|
: InvitationUtils.getColorFromHex(
|
||||||
fontWeight: FontWeight.w500,
|
tag['color_hex'],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 6),
|
||||||
|
Text(
|
||||||
|
tag['name'],
|
||||||
|
style: TextStyle(
|
||||||
|
color: isSelected
|
||||||
|
? Colors.white
|
||||||
|
: Colors.black87,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
SizedBox(height: 32),
|
||||||
|
|
||||||
|
Container(
|
||||||
|
height: 56,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: _isSubmitting ? null : _handleSubmit,
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Color(0xFF6A4C93),
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
elevation: 2,
|
||||||
|
),
|
||||||
|
child: _isSubmitting
|
||||||
|
? SizedBox(
|
||||||
|
height: 20,
|
||||||
|
width: 20,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2,
|
||||||
|
valueColor:
|
||||||
|
AlwaysStoppedAnimation<Color>(
|
||||||
|
Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Text(
|
||||||
|
'Create Invitation',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
SizedBox(height: 32),
|
|
||||||
|
|
||||||
Container(
|
|
||||||
height: 56,
|
|
||||||
child: ElevatedButton(
|
|
||||||
onPressed: _isSubmitting ? null : _handleSubmit,
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: Color(0xFF6A4C93),
|
|
||||||
foregroundColor: Colors.white,
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
elevation: 2,
|
|
||||||
),
|
),
|
||||||
child: _isSubmitting
|
|
||||||
? SizedBox(
|
|
||||||
height: 20,
|
|
||||||
width: 20,
|
|
||||||
child: CircularProgressIndicator(
|
|
||||||
strokeWidth: 2,
|
|
||||||
valueColor:
|
|
||||||
AlwaysStoppedAnimation<Color>(
|
|
||||||
Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: Text(
|
|
||||||
'Create Invitation',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user