Neda/Front/lib/screens/group_members_screen.dart
2026-03-19 11:56:16 +03:30

456 lines
15 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,
// استفاده از Stack برای قرار دادن دکمه شناور روی محتوا
body: Stack(
children: [
SafeArea(
child: Column(
children: [
// هدر ساده شده (فقط نمایش اطلاعات، بدون دکمه‌های کناری)
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
child: Column(
children: [
// نام گروه و تعداد اعضا وسط‌چین
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
widget.channel.type == 'PUBLIC'
? Icons.public
: Icons.lock_outline,
color: const Color(0xFF00C853),
size: 12,
),
const SizedBox(width: 4),
Flexible(
child: Text(
widget.channel.name,
style: const TextStyle(
color: Color(0xFF00C853),
fontSize: 12,
fontWeight: FontWeight.bold,
),
overflow: TextOverflow.ellipsis,
),
),
],
),
if (!_loading)
Text(
'${_members.length} عضو',
style: const TextStyle(
color: Colors.white38,
fontSize: 10,
),
),
],
),
),
const Divider(height: 1, color: Color(0xFF333333)),
// لیست اعضا
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: 60,
), // فضای خالی برای دکمه شناور پایین
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),
);
},
),
),
],
),
),
// دکمه شناور افزودن عضو (فقط برای مدیر) در پایین صفحه وسط
if (_isManager)
Positioned(
bottom: 10,
left: 0,
right: 0,
child: Center(
child: FloatingActionButton(
heroTag: "invite_btn", // جلوگیری از تداخل HeroTag
mini: true, // سایز کوچکتر مناسب ساعت
onPressed: _showInviteDialog,
backgroundColor: const Color(0xFF00C853),
child: const Icon(
Icons.person_add,
color: Colors.black,
size: 20,
),
),
),
),
],
),
);
}
}
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: 36, // کاهش ارتفاع آیتم
decoration: BoxDecoration(
color: const Color(0xFF1C1C1E),
borderRadius: BorderRadius.circular(10),
),
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Row(
children: [
// Online indicator
Container(
width: 6,
height: 6,
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: 4, vertical: 1),
decoration: BoxDecoration(
color: const Color(0xFF00C853).withOpacity(0.15),
borderRadius: BorderRadius.circular(6),
),
child: const Text(
'مدیر',
style: TextStyle(color: Color(0xFF00C853), fontSize: 8),
),
),
// 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.withOpacity(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,
),
),
),
),
],
),
],
),
);
}
}