import 'package:flutter/material.dart'; import '../main.dart'; import '../services/collection_service.dart'; import '../theme/app_colors.dart'; /// Screen to manage a collection: rename, invite/remove members, delete. class ManageCollectionScreen extends StatefulWidget { final Collection collection; const ManageCollectionScreen({super.key, required this.collection}); @override State createState() => _ManageCollectionScreenState(); } class _ManageCollectionScreenState extends State { late Collection _collection; List _members = []; bool _isLoading = true; bool _isInviting = false; @override void initState() { super.initState(); _collection = widget.collection; _loadMembers(); } Future _loadMembers() async { setState(() => _isLoading = true); try { final members = await CollectionService.getMembers(_collection.id); if (!mounted) return; setState(() { _members = members; _isLoading = false; }); } catch (e) { if (!mounted) return; setState(() => _isLoading = false); showGlobalError( e, fallback: 'Failed to load members. Please try again.', ); } } Future _rename() async { final formKey = GlobalKey(); final ctrl = TextEditingController(text: _collection.name); final descCtrl = TextEditingController(text: _collection.description ?? ''); final result = await showDialog( context: context, builder: (_) => AlertDialog( title: const Text('Rename Collection'), content: Form( key: formKey, child: Column( mainAxisSize: MainAxisSize.min, children: [ TextFormField( controller: ctrl, autofocus: true, maxLength: 50, decoration: const InputDecoration(labelText: 'Name'), validator: (value) { final trimmed = value?.trim() ?? ''; if (trimmed.isEmpty) return 'Name is required'; if (trimmed.length < 2) return 'Name must be at least 2 characters'; if (trimmed.length > 50) return 'Name must be 50 characters or fewer'; return null; }, ), const SizedBox(height: 12), TextField( controller: descCtrl, decoration: const InputDecoration(labelText: 'Description (optional)'), ), ], ), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Cancel'), ), ElevatedButton( onPressed: () { if (formKey.currentState!.validate()) { Navigator.pop(context, true); } }, child: const Text('Save'), ), ], ), ); if (result != true) return; final trimmedDesc = descCtrl.text.trim(); final String? description = trimmedDesc.isEmpty ? null : trimmedDesc; try { await CollectionService.update( collectionId: _collection.id, name: ctrl.text.trim(), description: description, ); setState(() { _collection = Collection( id: _collection.id, name: ctrl.text.trim(), description: description, ownerId: _collection.ownerId, createdAt: _collection.createdAt, role: _collection.role, itemCount: _collection.itemCount, memberCount: _collection.memberCount, ); }); showGlobalSuccess('Collection renamed!'); } catch (e) { showGlobalError( e, fallback: 'Could not rename collection. Please try again.', ); } } Future _inviteMember() async { if (_isInviting) return; final emailCtrl = TextEditingController(); String inviteRole = 'member'; final result = await showDialog( context: context, builder: (_) => StatefulBuilder( builder: (context, setSheetState) => AlertDialog( icon: Container( padding: const EdgeInsets.all(12), decoration: const BoxDecoration( gradient: AppColors.brandGradient, shape: BoxShape.circle, ), child: const Icon(Icons.person_add, color: Colors.white, size: 28), ), title: const Text('Invite Member'), content: SingleChildScrollView( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 420), child: Column( mainAxisSize: MainAxisSize.min, children: [ const Text( 'Enter the email address of the person you want to invite. ' 'They must already have an account.', style: TextStyle( fontSize: 13, color: AppColors.textSecondary, ), ), const SizedBox(height: 16), TextField( controller: emailCtrl, autofocus: true, keyboardType: TextInputType.emailAddress, decoration: const InputDecoration( labelText: 'Email address', hintText: 'user@example.com', prefixIcon: Icon(Icons.email_outlined), ), ), const SizedBox(height: 12), DropdownButtonFormField( isExpanded: true, value: inviteRole, decoration: const InputDecoration( labelText: 'Role', prefixIcon: Icon(Icons.security_outlined), ), items: const [ DropdownMenuItem(value: 'member', child: Text('Member')), DropdownMenuItem(value: 'viewer', child: Text('Viewer')), ], onChanged: (value) { if (value == null) return; setSheetState(() => inviteRole = value); }, ), const SizedBox(height: 8), Align( alignment: Alignment.centerLeft, child: Text( inviteRole == 'viewer' ? 'Viewer: read-only access.' : 'Member: can add, copy, edit, and remove cars.', style: const TextStyle( fontSize: 12, color: AppColors.textSecondary, ), ), ), ], ), ), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Cancel'), ), ElevatedButton( onPressed: () { if (emailCtrl.text.trim().isEmpty) return; Navigator.pop(context, true); }, child: const Text('Invite'), ), ], ), ), ); if (result != true) return; final email = emailCtrl.text.trim(); final emailRegex = RegExp(r'^[^@\s]+@[^@\s]+\.[^@\s]+$'); if (!emailRegex.hasMatch(email)) { showGlobalSnackBar('Please enter a valid email address.', isError: true); return; } try { setState(() => _isInviting = true); await CollectionService.inviteByEmail( collectionId: _collection.id, email: email, role: inviteRole, ); showGlobalSuccess( inviteRole == 'viewer' ? 'Viewer invited!' : 'Member invited!', ); await _loadMembers(); } catch (e) { showGlobalError( e, fallback: 'Could not send invitation. Please try again.', ); } finally { if (mounted) setState(() => _isInviting = false); } } Future _removeMember(CollectionMember member) async { final confirmed = await showDialog( context: context, builder: (_) => AlertDialog( title: const Text('Remove Member?'), content: Text( 'Remove ${member.email} from this collection?'), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: const Text('Cancel'), ), ElevatedButton( onPressed: () => Navigator.pop(context, true), style: ElevatedButton.styleFrom(backgroundColor: AppColors.error), child: const Text('Remove'), ), ], ), ); if (confirmed != true) return; try { await CollectionService.removeMember( collectionId: _collection.id, memberUserId: member.userId, ); showGlobalSuccess('Member removed.'); await _loadMembers(); } catch (e) { showGlobalError( e, fallback: 'Could not remove member. Please try again.', ); } } Future _leaveCollection() async { final confirmed = await showDialog( context: context, builder: (_) => AlertDialog( title: const Text('Leave Collection?'), content: Text( 'You will lose access to "${_collection.name}". ' 'This action cannot be undone.'), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: const Text('Cancel'), ), ElevatedButton( onPressed: () => Navigator.pop(context, true), style: ElevatedButton.styleFrom(backgroundColor: AppColors.error), child: const Text('Leave'), ), ], ), ); if (confirmed != true) return; try { await CollectionService.leave(_collection.id); showGlobalSuccess('Left "${_collection.name}".'); if (mounted) Navigator.pop(context); } catch (e) { showGlobalError( e, fallback: 'Could not leave collection. Please try again.', ); } } Future _deleteCollection() async { final confirmed = await showDialog( context: context, builder: (_) => AlertDialog( title: const Text('Delete Collection?'), content: Text( 'Delete "${_collection.name}" and ALL its items? ' 'This cannot be undone.'), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: const Text('Cancel'), ), ElevatedButton( onPressed: () => Navigator.pop(context, true), style: ElevatedButton.styleFrom(backgroundColor: AppColors.error), child: const Text('Delete Forever'), ), ], ), ); if (confirmed != true) return; try { await CollectionService.delete(_collection.id); showGlobalSuccess('Collection deleted.'); if (mounted) Navigator.pop(context); } catch (e) { showGlobalError( e, fallback: 'Could not delete collection. Please try again.', ); } } @override Widget build(BuildContext context) { final theme = Theme.of(context); final currentUserId = supabase.auth.currentUser?.id; return Scaffold( appBar: AppBar( title: Text(_collection.name), actions: [ if (_collection.isOwner) IconButton( icon: const Icon(Icons.edit), tooltip: 'Rename', onPressed: _rename, ), ], ), body: RefreshIndicator( onRefresh: _loadMembers, child: ListView( padding: const EdgeInsets.all(16), children: [ if (_collection.description != null && _collection.description!.isNotEmpty) ...[ Text( _collection.description!, style: const TextStyle( fontSize: 14, color: AppColors.textSecondary), ), const SizedBox(height: 16), ], Row( children: [ Text( 'Members', style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, ), ), const Spacer(), if (_collection.isOwner) TextButton.icon( onPressed: _isInviting ? null : _inviteMember, icon: const Icon(Icons.person_add, size: 18), label: Text(_isInviting ? 'Inviting…' : 'Invite'), ), ], ), const SizedBox(height: 8), if (_isLoading) const Center( child: Padding( padding: EdgeInsets.all(24), child: CircularProgressIndicator(), ), ) else ...List.generate(_members.length, (i) { final member = _members[i]; return Card( margin: const EdgeInsets.only(bottom: 8), child: ListTile( leading: CircleAvatar( backgroundColor: member.isOwner ? AppColors.orange : AppColors.navy, child: Icon( member.isOwner ? Icons.star : Icons.person, color: Colors.white, size: 20, ), ), title: Text( member.email, style: const TextStyle(fontWeight: FontWeight.w500), ), subtitle: Text( _roleLabel(member.role), style: const TextStyle(fontSize: 12), ), trailing: (!member.isOwner && _collection.isOwner && member.userId != currentUserId) ? IconButton( icon: const Icon(Icons.remove_circle_outline, color: AppColors.error), onPressed: () => _removeMember(member), ) : null, ), ); }), const SizedBox(height: 32), const Divider(), const SizedBox(height: 16), Text( 'Danger Zone', style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, color: AppColors.error, ), ), const SizedBox(height: 12), if (!_collection.isOwner) SizedBox( width: double.infinity, child: OutlinedButton.icon( onPressed: _leaveCollection, icon: const Icon(Icons.exit_to_app, color: AppColors.error), label: const Text('Leave Collection', style: TextStyle(color: AppColors.error)), style: OutlinedButton.styleFrom( side: const BorderSide(color: AppColors.error), padding: const EdgeInsets.symmetric(vertical: 14), ), ), ) else ...[ const Text( 'As owner, you cannot leave this collection. You can delete it instead.', style: TextStyle( fontSize: 12, color: AppColors.textSecondary, ), ), const SizedBox(height: 10), SizedBox( width: double.infinity, child: OutlinedButton.icon( onPressed: _deleteCollection, icon: const Icon(Icons.delete_forever, color: AppColors.error), label: const Text('Delete Collection', style: TextStyle(color: AppColors.error)), style: OutlinedButton.styleFrom( side: const BorderSide(color: AppColors.error), padding: const EdgeInsets.symmetric(vertical: 14), ), ), ), ], ], ), ), ); } String _roleLabel(String role) { switch (role) { case 'owner': return 'Owner'; case 'viewer': return 'Viewer (read-only)'; default: return 'Member'; } } }