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

391 lines
13 KiB
Dart

import 'package:flutter/material.dart';
import '../models/channel.dart';
import '../services/api_service.dart';
import '../services/auth_service.dart';
import 'channel_screen.dart';
import 'login_screen.dart';
import 'notifications_screen.dart';
class ChannelListScreen extends StatefulWidget {
const ChannelListScreen({super.key});
@override
State<ChannelListScreen> createState() => _ChannelListScreenState();
}
class _ChannelListScreenState extends State<ChannelListScreen> {
final _authService = AuthService();
late final ApiService _api;
List<Channel> _channels = [];
bool _loading = true;
String? _error;
int _pendingNotifCount = 0;
String? _currentUserId;
@override
void initState() {
super.initState();
_api = ApiService(_authService);
_init();
}
Future<void> _init() async {
_currentUserId = await _authService.getUserId();
await _loadChannels();
await _loadNotifCount();
}
Future<void> _loadChannels() async {
setState(() {
_loading = true;
_error = null;
});
final channels = await _api.getChannels();
if (!mounted) return;
if (channels.isEmpty && _channels.isEmpty) {
setState(() {
_loading = false;
_error = 'کانالی یافت نشد';
});
} else {
setState(() {
_channels = channels;
_loading = false;
});
}
}
Future<void> _loadNotifCount() async {
final notifs = await _api.getNotifications();
if (!mounted) return;
setState(() {
_pendingNotifCount = notifs.where((n) => n.isPending).length;
});
}
Future<void> _refresh() async {
await _loadChannels();
await _loadNotifCount();
}
Future<void> _logout() async {
await _authService.logout();
if (!mounted) return;
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (_) => const LoginScreen()),
);
}
void _enterChannel(Channel ch) {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => ChannelScreen(channel: ch, currentUserId: _currentUserId),
),
);
}
void _openNotifications() async {
await Navigator.push(
context,
MaterialPageRoute(builder: (_) => const NotificationsScreen()),
);
// refresh notif count + reload channels after returning (user may have accepted invite)
_refresh();
}
Future<void> _showCreateGroupDialog() async {
String groupName = '';
final confirmed = await showDialog<bool>(
context: context,
builder: (ctx) => _CreateGroupDialog(
onNameChanged: (v) => groupName = v,
),
);
if (confirmed == true && groupName.trim().isNotEmpty) {
final newChannel = await _api.createGroup(groupName.trim());
if (!mounted) return;
if (newChannel != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('گروه ساخته شد', style: TextStyle(fontSize: 11), textAlign: TextAlign.center),
backgroundColor: const Color(0xFF1C1C1E),
behavior: SnackBarBehavior.floating,
duration: const Duration(seconds: 2),
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 40),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
),
);
_loadChannels();
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('خطا در ساخت گروه', style: 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)),
),
);
}
}
}
@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: [
const SizedBox(width: 4),
const Icon(Icons.settings_input_antenna,
color: Color(0xFF00C853), size: 16),
const SizedBox(width: 4),
const Expanded(
child: Text(
'کانال‌ها',
style: TextStyle(
color: Colors.white,
fontSize: 13,
fontWeight: FontWeight.bold),
),
),
// Notifications icon with badge
Stack(
clipBehavior: Clip.none,
children: [
IconButton(
onPressed: _openNotifications,
padding: EdgeInsets.zero,
constraints: const BoxConstraints(minWidth: 32, minHeight: 32),
icon: const Icon(Icons.notifications_outlined,
color: Colors.white54, size: 18),
),
if (_pendingNotifCount > 0)
Positioned(
top: 4,
right: 4,
child: Container(
width: 8,
height: 8,
decoration: const BoxDecoration(
color: Color(0xFFFF1744),
shape: BoxShape.circle,
),
),
),
],
),
// Create group
IconButton(
onPressed: _showCreateGroupDialog,
padding: EdgeInsets.zero,
constraints: const BoxConstraints(minWidth: 32, minHeight: 32),
icon: const Icon(Icons.add_circle_outline,
color: Color(0xFF00C853), size: 18),
),
// Refresh
IconButton(
onPressed: _loading ? null : _refresh,
padding: EdgeInsets.zero,
constraints:
const BoxConstraints(minWidth: 32, minHeight: 32),
icon: const Icon(Icons.refresh,
color: Colors.white54, size: 18),
),
// Logout
IconButton(
onPressed: _logout,
padding: EdgeInsets.zero,
constraints:
const BoxConstraints(minWidth: 32, minHeight: 32),
icon: const Icon(Icons.logout,
color: Colors.white38, size: 16),
),
],
),
),
// Content
Expanded(
child: _loading
? const Center(
child: CircularProgressIndicator(
color: Color(0xFF00C853), strokeWidth: 2),
)
: _error != null && _channels.isEmpty
? Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.wifi_off,
color: Colors.white38, size: 24),
const SizedBox(height: 6),
Text(_error!,
style: const TextStyle(
color: Colors.white38, fontSize: 11)),
const SizedBox(height: 8),
TextButton(
onPressed: _refresh,
child: const Text('تلاش مجدد',
style: TextStyle(
color: Color(0xFF00C853),
fontSize: 11)),
),
],
),
)
: ListView.builder(
padding: const EdgeInsets.only(bottom: 4),
itemCount: _channels.length,
itemBuilder: (ctx, i) {
return _ChannelTile(
channel: _channels[i],
onTap: () => _enterChannel(_channels[i]),
);
},
),
),
],
),
),
);
}
}
class _ChannelTile extends StatelessWidget {
final Channel channel;
final VoidCallback onTap;
const _ChannelTile({required this.channel, required this.onTap});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
height: 44,
decoration: BoxDecoration(
color: const Color(0xFF1C1C1E),
borderRadius: BorderRadius.circular(14),
),
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row(
children: [
Icon(
channel.type == 'PUBLIC' ? Icons.radio : Icons.lock_outline,
color: const Color(0xFF00C853),
size: 16,
),
const SizedBox(width: 8),
Expanded(
child: Text(
channel.name,
style: const TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.w500),
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: 4),
const Icon(Icons.chevron_right, color: Colors.white24, size: 16),
],
),
),
);
}
}
// ── Create Group Dialog ────────────────────────────────────────────────────
class _CreateGroupDialog extends StatefulWidget {
final ValueChanged<String> onNameChanged;
const _CreateGroupDialog({required this.onNameChanged});
@override
State<_CreateGroupDialog> createState() => _CreateGroupDialogState();
}
class _CreateGroupDialogState extends State<_CreateGroupDialog> {
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.add_circle_outline, 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.onNameChanged,
),
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,
)),
),
),
],
),
],
),
);
}
}