Neda/Front/lib/screens/group_members_screen.dart
2026-03-08 09:53:45 +03:30

403 lines
14 KiB
Dart

import 'package:flutter/material.dart';
import '../models/channel.dart';
import '../models/group_member.dart';
import '../services/api_service.dart';
import '../services/auth_service.dart';
class GroupMembersScreen extends StatefulWidget {
final Channel channel;
final String? currentUserId;
const GroupMembersScreen({
super.key,
required this.channel,
this.currentUserId,
});
@override
State<GroupMembersScreen> createState() => _GroupMembersScreenState();
}
class _GroupMembersScreenState extends State<GroupMembersScreen> {
late final ApiService _api;
List<GroupMember> _members = [];
bool _loading = true;
bool _isManager = false;
@override
void initState() {
super.initState();
_api = ApiService(AuthService());
_load();
}
Future<void> _load() async {
setState(() => _loading = true);
final list = await _api.getGroupMembers(widget.channel.id);
if (!mounted) return;
final isManager = list.any(
(m) => m.userId == widget.currentUserId && m.isManager,
);
setState(() {
_members = list;
_isManager = isManager;
_loading = false;
});
}
Future<void> _showInviteDialog() async {
String username = '';
final confirmed = await showDialog<bool>(
context: context,
builder: (ctx) => _InviteDialog(
onUsernameChanged: (v) => username = v,
),
);
if (confirmed == true && username.trim().isNotEmpty) {
final err = await _api.inviteMember(widget.channel.id, username.trim());
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
err ?? 'دعوت‌نامه ارسال شد',
style: const TextStyle(fontSize: 11),
textAlign: TextAlign.center,
),
backgroundColor: err == null ? const Color(0xFF1C1C1E) : const Color(0xFF333333),
behavior: SnackBarBehavior.floating,
duration: const Duration(seconds: 2),
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 40),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
),
);
}
}
Future<void> _removeMember(GroupMember member) async {
final confirmed = await showDialog<bool>(
context: context,
builder: (ctx) => AlertDialog(
backgroundColor: const Color(0xFF1C1C1E),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
contentPadding: const EdgeInsets.all(16),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.person_remove_outlined, color: Colors.red, size: 28),
const SizedBox(height: 8),
Text(
'حذف ${member.username}؟',
style: const TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: TextButton(
onPressed: () => Navigator.pop(ctx, false),
child: const Text('انصراف', style: TextStyle(color: Colors.white54, fontSize: 11)),
),
),
Expanded(
child: TextButton(
onPressed: () => Navigator.pop(ctx, true),
child: const Text('حذف', style: TextStyle(color: Colors.red, fontSize: 11)),
),
),
],
),
],
),
),
);
if (confirmed == true) {
final err = await _api.removeMember(widget.channel.id, member.userId);
if (!mounted) return;
if (err != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(err, style: const TextStyle(fontSize: 11), textAlign: TextAlign.center),
backgroundColor: const Color(0xFF333333),
behavior: SnackBarBehavior.floating,
duration: const Duration(seconds: 2),
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 40),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
),
);
} else {
_load();
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: SafeArea(
child: Column(
children: [
// Header
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
child: Row(
children: [
IconButton(
onPressed: () => Navigator.pop(context),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(minWidth: 28, minHeight: 28),
icon: const Icon(Icons.arrow_back_ios_new, color: Colors.white70, size: 14),
),
const SizedBox(width: 4),
const Icon(Icons.group_outlined, color: Color(0xFF00C853), size: 14),
const SizedBox(width: 4),
const Expanded(
child: Text(
'اعضا',
style: TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold),
),
),
// Invite button
IconButton(
onPressed: _showInviteDialog,
padding: EdgeInsets.zero,
constraints: const BoxConstraints(minWidth: 28, minHeight: 28),
icon: const Icon(Icons.person_add_outlined, color: Color(0xFF00C853), size: 16),
),
IconButton(
onPressed: _loading ? null : _load,
padding: EdgeInsets.zero,
constraints: const BoxConstraints(minWidth: 28, minHeight: 28),
icon: const Icon(Icons.refresh, color: Colors.white54, size: 16),
),
],
),
),
// Group name chip
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 2),
child: Row(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: const Color(0xFF00C853).withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(20),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
widget.channel.type == 'PUBLIC'
? Icons.public
: Icons.lock_outline,
color: const Color(0xFF00C853),
size: 10,
),
const SizedBox(width: 4),
Text(
widget.channel.name,
style: const TextStyle(color: Color(0xFF00C853), fontSize: 10),
),
],
),
),
if (!_loading) ...[
const SizedBox(width: 6),
Text(
'${_members.length} عضو',
style: const TextStyle(color: Colors.white38, fontSize: 10),
),
],
],
),
),
const SizedBox(height: 4),
// Content
Expanded(
child: _loading
? const Center(
child: CircularProgressIndicator(color: Color(0xFF00C853), strokeWidth: 2),
)
: _members.isEmpty
? const Center(
child: Text('عضوی یافت نشد',
style: TextStyle(color: Colors.white38, fontSize: 11)),
)
: ListView.builder(
padding: const EdgeInsets.only(bottom: 4),
itemCount: _members.length,
itemBuilder: (ctx, i) {
final m = _members[i];
final isMe = m.userId == widget.currentUserId;
return _MemberTile(
member: m,
isMe: isMe,
canRemove: _isManager && !isMe && !m.isManager,
onRemove: () => _removeMember(m),
);
},
),
),
],
),
),
);
}
}
class _MemberTile extends StatelessWidget {
final GroupMember member;
final bool isMe;
final bool canRemove;
final VoidCallback onRemove;
const _MemberTile({
required this.member,
required this.isMe,
required this.canRemove,
required this.onRemove,
});
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
height: 40,
decoration: BoxDecoration(
color: const Color(0xFF1C1C1E),
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Row(
children: [
// Online indicator
Container(
width: 7,
height: 7,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: member.isOnline ? const Color(0xFF00C853) : Colors.white24,
),
),
const SizedBox(width: 8),
// Username
Expanded(
child: Text(
member.username + (isMe ? ' (من)' : ''),
style: TextStyle(
color: isMe ? const Color(0xFF00C853) : Colors.white,
fontSize: 11,
fontWeight: member.isManager ? FontWeight.bold : FontWeight.normal,
),
overflow: TextOverflow.ellipsis,
),
),
// Role badge
if (member.isManager)
Container(
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 2),
decoration: BoxDecoration(
color: const Color(0xFF00C853).withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(8),
),
child: const Text(
'مدیر',
style: TextStyle(color: Color(0xFF00C853), fontSize: 9),
),
),
// Remove button
if (canRemove) ...[
const SizedBox(width: 4),
GestureDetector(
onTap: onRemove,
child: const Icon(Icons.remove_circle_outline, color: Colors.red, size: 16),
),
],
],
),
);
}
}
// ── Invite Dialog ──────────────────────────────────────────────────────────
class _InviteDialog extends StatefulWidget {
final ValueChanged<String> onUsernameChanged;
const _InviteDialog({required this.onUsernameChanged});
@override
State<_InviteDialog> createState() => _InviteDialogState();
}
class _InviteDialogState extends State<_InviteDialog> {
final _ctrl = TextEditingController();
@override
void dispose() {
_ctrl.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AlertDialog(
backgroundColor: const Color(0xFF1C1C1E),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
contentPadding: const EdgeInsets.all(16),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.person_add_outlined, color: Color(0xFF00C853), size: 26),
const SizedBox(height: 8),
const Text(
'دعوت عضو',
style: TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold),
),
const SizedBox(height: 10),
TextField(
controller: _ctrl,
autofocus: true,
textAlign: TextAlign.center,
style: const TextStyle(color: Colors.white, fontSize: 12),
decoration: InputDecoration(
hintText: 'نام کاربری',
hintStyle: const TextStyle(color: Colors.white38, fontSize: 11),
filled: true,
fillColor: Colors.white.withValues(alpha: 0.05),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide.none,
),
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
),
onChanged: widget.onUsernameChanged,
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('انصراف', style: TextStyle(color: Colors.white54, fontSize: 11)),
),
),
Expanded(
child: TextButton(
onPressed: () => Navigator.pop(context, true),
child: const Text('ارسال', style: TextStyle(color: Color(0xFF00C853), fontSize: 11, fontWeight: FontWeight.bold)),
),
),
],
),
],
),
);
}
}