315 lines
10 KiB
Dart
315 lines
10 KiB
Dart
import 'package:flutter/material.dart';
|
||
import '/domain/services/api_service.dart';
|
||
import 'package:http/http.dart' as http;
|
||
import '/core/constants.dart';
|
||
import 'dart:convert';
|
||
|
||
class AdminBroadcastScreen extends StatefulWidget {
|
||
const AdminBroadcastScreen({Key? key}) : super(key: key);
|
||
|
||
@override
|
||
State<AdminBroadcastScreen> createState() => _AdminBroadcastScreenState();
|
||
}
|
||
|
||
class _AdminBroadcastScreenState extends State<AdminBroadcastScreen> {
|
||
final TextEditingController _messageController = TextEditingController();
|
||
final ApiService _apiService = ApiService();
|
||
|
||
bool _isLoading = false;
|
||
bool _broadcastToAll = true;
|
||
Set<int> _selectedUserIds = {};
|
||
List<Map<String, dynamic>> _allUsers = [];
|
||
bool _isLoadingUsers = false;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_loadAllUsers();
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_messageController.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
Future<void> _loadAllUsers() async {
|
||
setState(() => _isLoadingUsers = true);
|
||
try {
|
||
final token = await _apiService.getAccessToken();
|
||
final response = await http.get(
|
||
Uri.parse('${AppConstants.baseUrl}/users/all'),
|
||
headers: {
|
||
'Authorization': 'Bearer $token',
|
||
'Content-Type': 'application/json',
|
||
},
|
||
);
|
||
|
||
if (response.statusCode == 200) {
|
||
final List<dynamic> data = jsonDecode(utf8.decode(response.bodyBytes));
|
||
setState(() {
|
||
_allUsers = data.map((user) {
|
||
return {
|
||
'id': user['id'],
|
||
'username': user['username'],
|
||
'name': user['name'],
|
||
};
|
||
}).toList();
|
||
});
|
||
} else {
|
||
_showErrorDialog('Не удалось загрузить список пользователей');
|
||
}
|
||
} catch (e) {
|
||
_showErrorDialog('Ошибка при загрузке пользователей: $e');
|
||
} finally {
|
||
setState(() => _isLoadingUsers = false);
|
||
}
|
||
}
|
||
|
||
Future<void> _sendBroadcast() async {
|
||
final message = _messageController.text.trim();
|
||
|
||
if (message.isEmpty) {
|
||
_showErrorDialog('Введите текст сообщения');
|
||
return;
|
||
}
|
||
|
||
if (!_broadcastToAll && _selectedUserIds.isEmpty) {
|
||
_showErrorDialog('Выберите хотя бы одного пользователя');
|
||
return;
|
||
}
|
||
|
||
setState(() => _isLoading = true);
|
||
|
||
try {
|
||
final token = await _apiService.getAccessToken();
|
||
|
||
final payload = {
|
||
'content': message,
|
||
if (!_broadcastToAll) 'user_ids': _selectedUserIds.toList(),
|
||
};
|
||
|
||
final response = await http.post(
|
||
Uri.parse('${AppConstants.baseUrl}/admin/broadcast'),
|
||
headers: {
|
||
'Authorization': 'Bearer $token',
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: jsonEncode(payload),
|
||
);
|
||
|
||
if (response.statusCode == 200) {
|
||
final data = jsonDecode(utf8.decode(response.bodyBytes));
|
||
_messageController.clear();
|
||
setState(() => _selectedUserIds.clear());
|
||
|
||
_showSuccessDialog(data['message'] ?? 'Рассылка отправлена успешно');
|
||
} else {
|
||
final errorData = jsonDecode(utf8.decode(response.bodyBytes));
|
||
_showErrorDialog(
|
||
errorData['detail'] ?? 'Не удалось отправить рассылку',
|
||
);
|
||
}
|
||
} catch (e) {
|
||
_showErrorDialog('Ошибка при отправке рассылки: $e');
|
||
} finally {
|
||
setState(() => _isLoading = false);
|
||
}
|
||
}
|
||
|
||
void _showErrorDialog(String message) {
|
||
showDialog(
|
||
context: context,
|
||
builder: (ctx) => AlertDialog(
|
||
title: const Text('Ошибка'),
|
||
content: Text(message),
|
||
actions: [
|
||
TextButton(
|
||
onPressed: () => Navigator.pop(ctx),
|
||
child: const Text('OK'),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
void _showSuccessDialog(String message) {
|
||
showDialog(
|
||
context: context,
|
||
builder: (ctx) => AlertDialog(
|
||
title: const Text('Успешно'),
|
||
content: Text(message),
|
||
actions: [
|
||
TextButton(
|
||
onPressed: () => Navigator.pop(ctx),
|
||
child: const Text('OK'),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final colorScheme = Theme.of(context).colorScheme;
|
||
|
||
return Scaffold(
|
||
body: SingleChildScrollView(
|
||
padding: const EdgeInsets.all(16),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
// Message input section
|
||
Text(
|
||
'Текст сообщения',
|
||
style: Theme.of(context).textTheme.titleMedium,
|
||
),
|
||
const SizedBox(height: 8),
|
||
TextField(
|
||
controller: _messageController,
|
||
decoration: InputDecoration(
|
||
hintText: 'Введите текст для рассылки...',
|
||
border: OutlineInputBorder(
|
||
borderRadius: BorderRadius.circular(12),
|
||
),
|
||
filled: true,
|
||
fillColor: colorScheme.surface,
|
||
),
|
||
maxLines: 5,
|
||
minLines: 3,
|
||
),
|
||
const SizedBox(height: 24),
|
||
|
||
// Recipient selection section
|
||
Text(
|
||
'Получатели',
|
||
style: Theme.of(context).textTheme.titleMedium,
|
||
),
|
||
const SizedBox(height: 12),
|
||
|
||
// "All users" toggle
|
||
Card(
|
||
child: ListTile(
|
||
title: const Text('Отправить всем'),
|
||
leading: Radio<bool>(
|
||
value: true,
|
||
groupValue: _broadcastToAll,
|
||
onChanged: (value) {
|
||
setState(() {
|
||
_broadcastToAll = value ?? true;
|
||
if (_broadcastToAll) {
|
||
_selectedUserIds.clear();
|
||
}
|
||
});
|
||
},
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(height: 12),
|
||
|
||
// "Selected users" toggle
|
||
Card(
|
||
child: ListTile(
|
||
title: const Text('Отправить выбранным'),
|
||
leading: Radio<bool>(
|
||
value: false,
|
||
groupValue: _broadcastToAll,
|
||
onChanged: (value) {
|
||
setState(() {
|
||
_broadcastToAll = value ?? false;
|
||
});
|
||
},
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(height: 16),
|
||
|
||
// User list for selection
|
||
if (!_broadcastToAll) ...[
|
||
if (_isLoadingUsers)
|
||
const Center(child: CircularProgressIndicator())
|
||
else if (_allUsers.isEmpty)
|
||
const Padding(
|
||
padding: EdgeInsets.all(16),
|
||
child: Text('Нет пользователей'),
|
||
)
|
||
else
|
||
Card(
|
||
child: Column(
|
||
children: [
|
||
Padding(
|
||
padding: const EdgeInsets.all(12),
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
Text(
|
||
'Выбрано: ${_selectedUserIds.length}',
|
||
style: Theme.of(context).textTheme.bodyMedium,
|
||
),
|
||
if (_selectedUserIds.isNotEmpty)
|
||
TextButton(
|
||
onPressed: () {
|
||
setState(() => _selectedUserIds.clear());
|
||
},
|
||
child: const Text('Очистить'),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
Divider(height: 0, color: colorScheme.outlineVariant),
|
||
SizedBox(
|
||
height: 250,
|
||
child: ListView.builder(
|
||
itemCount: _allUsers.length,
|
||
itemBuilder: (context, index) {
|
||
final user = _allUsers[index];
|
||
final userId = user['id'] as int;
|
||
final isSelected = _selectedUserIds.contains(userId);
|
||
|
||
return CheckboxListTile(
|
||
value: isSelected,
|
||
onChanged: (selected) {
|
||
setState(() {
|
||
if (selected == true) {
|
||
_selectedUserIds.add(userId);
|
||
} else {
|
||
_selectedUserIds.remove(userId);
|
||
}
|
||
});
|
||
},
|
||
title: Text(user['name'] ?? 'Неизвестный'),
|
||
subtitle: Text('@${user['username']}'),
|
||
);
|
||
},
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
const SizedBox(height: 24),
|
||
|
||
// Send button
|
||
SizedBox(
|
||
width: double.infinity,
|
||
child: FilledButton.icon(
|
||
onPressed: _isLoading ? null : _sendBroadcast,
|
||
icon: _isLoading
|
||
? const SizedBox(
|
||
width: 20,
|
||
height: 20,
|
||
child: CircularProgressIndicator(strokeWidth: 2),
|
||
)
|
||
: const Icon(Icons.send),
|
||
label: Text(
|
||
_isLoading ? 'Отправка...' : 'Отправить рассылку',
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|