Neda/Front/lib/screens/channel_list_screen.dart

424 lines
14 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 centered for watch face
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.settings_input_antenna,
color: Color(0xFF00C853), size: 14),
const SizedBox(width: 6),
const Text(
'کانال‌ها',
style: TextStyle(
color: Colors.white,
fontSize: 13,
fontWeight: FontWeight.bold),
),
const SizedBox(width: 10),
// Menu button uses child: to avoid M3 size inflation
Stack(
clipBehavior: Clip.none,
children: [
PopupMenuButton<String>(
padding: EdgeInsets.zero,
child: const Icon(Icons.menu,
color: Colors.white, size: 22),
onSelected: (value) {
switch (value) {
case 'notifications':
_openNotifications();
case 'create':
_showCreateGroupDialog();
case 'refresh':
if (!_loading) _refresh();
case 'logout':
_logout();
}
},
itemBuilder: (context) => [
PopupMenuItem(
value: 'notifications',
child: Row(
children: [
const Icon(Icons.notifications_outlined,
size: 16),
const SizedBox(width: 8),
Text(_pendingNotifCount > 0
? 'اعلان‌ها ($_pendingNotifCount)'
: 'اعلان‌ها'),
],
),
),
const PopupMenuItem(
value: 'create',
child: Row(
children: [
Icon(Icons.add_circle_outline,
size: 16, color: Color(0xFF00C853)),
SizedBox(width: 8),
Text('گروه جدید'),
],
),
),
PopupMenuItem(
value: 'refresh',
enabled: !_loading,
child: const Row(
children: [
Icon(Icons.refresh, size: 16),
SizedBox(width: 8),
Text('بروزرسانی'),
],
),
),
const PopupMenuItem(
value: 'logout',
child: Row(
children: [
Icon(Icons.logout,
size: 16, color: Colors.redAccent),
SizedBox(width: 8),
Text('خروج',
style:
TextStyle(color: Colors.redAccent)),
],
),
),
],
),
if (_pendingNotifCount > 0)
Positioned(
top: -2,
right: -2,
child: Container(
width: 7,
height: 7,
decoration: const BoxDecoration(
color: Color(0xFFFF1744),
shape: BoxShape.circle,
),
),
),
],
),
],
),
),
// 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,
)),
),
),
],
),
],
),
);
}
}