Files
Avtex/lib/screens/alarms_screen.dart
T
Alexyz f1e0f6b5ef feat: Add Nanaxe server with Docker, GitHub Actions CI/CD, and auto-deployment
- Add complete server infrastructure with Gitea, API, PostgreSQL
- Implement Obsidian-compatible markdown mindmap converter
- Add Flutter service for server synchronization
- Configure GitHub Actions for automated build and deploy
- Add production docker-compose with health checks
- Include VPS setup and deployment scripts
- Add comprehensive deployment documentation
2026-04-19 22:18:25 +05:00

263 lines
8.0 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../models/alarm_model.dart';
import '../core/notification_service.dart';
import 'package:uuid/uuid.dart';
class AlarmsScreen extends ConsumerStatefulWidget {
const AlarmsScreen({super.key});
@override
ConsumerState<AlarmsScreen> createState() => _AlarmsScreenState();
}
class _AlarmsScreenState extends ConsumerState<AlarmsScreen> {
List<AlarmModel> _alarms = [];
bool _isLoading = true;
@override
void initState() {
super.initState();
_loadAlarms();
}
Future<void> _loadAlarms() async {
setState(() => _isLoading = true);
// TODO: Загрузить из Firestore/Hive
setState(() {
_alarms = [];
_isLoading = false;
});
}
Future<void> _toggleAlarm(AlarmModel alarm) async {
final updated = alarm.copyWith(isEnabled: !alarm.isEnabled);
setState(() {
final index = _alarms.indexWhere((a) => a.id == alarm.id);
if (index != -1) {
_alarms[index] = updated;
}
});
if (updated.isEnabled) {
await notificationService.scheduleNotification(
id: updated.hashCode,
title: updated.title,
body: 'Напоминание: ${updated.title}',
scheduledDate: updated.dateTime,
);
} else {
await notificationService.cancelNotification(updated.hashCode);
}
}
Future<void> _deleteAlarm(String id) async {
final alarm = _alarms.firstWhere((a) => a.id == id);
await notificationService.cancelNotification(alarm.hashCode);
setState(() {
_alarms.removeWhere((a) => a.id == id);
});
}
Future<void> _showAddAlarmDialog() async {
final titleController = TextEditingController();
DateTime selectedDate = DateTime.now();
TimeOfDay selectedTime = TimeOfDay.now();
await showDialog(
context: context,
builder: (context) => StatefulBuilder(
builder: (context, setDialogState) => AlertDialog(
title: const Text('Новый будильник'),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: titleController,
decoration: const InputDecoration(
labelText: 'Название',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 16),
ListTile(
leading: const Icon(Icons.calendar_today),
title: const Text('Дата'),
subtitle: Text(
'${selectedDate.day}.${selectedDate.month}.${selectedDate.year}',
),
trailing: const Icon(Icons.arrow_forward_ios, size: 16),
onTap: () async {
final picked = await showDatePicker(
context: context,
initialDate: selectedDate,
firstDate: DateTime.now(),
lastDate: DateTime.now().add(const Duration(days: 365)),
);
if (picked != null) {
setDialogState(() => selectedDate = picked);
}
},
),
ListTile(
leading: const Icon(Icons.access_time),
title: const Text('Время'),
subtitle: Text(selectedTime.format(context)),
trailing: const Icon(Icons.arrow_forward_ios, size: 16),
onTap: () async {
final picked = await showTimePicker(
context: context,
initialTime: selectedTime,
);
if (picked != null) {
setDialogState(() => selectedTime = picked);
}
},
),
],
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Отмена'),
),
ElevatedButton(
onPressed: () {
if (titleController.text.isEmpty) return;
final dateTime = DateTime(
selectedDate.year,
selectedDate.month,
selectedDate.day,
selectedTime.hour,
selectedTime.minute,
);
final alarm = AlarmModel(
id: const Uuid().v4(),
title: titleController.text,
dateTime: dateTime,
isEnabled: true,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
);
setState(() {
_alarms.add(alarm);
});
notificationService.scheduleNotification(
id: alarm.hashCode,
title: alarm.title,
body: 'Будильник: ${alarm.title}',
scheduledDate: alarm.dateTime,
);
Navigator.pop(context);
},
child: const Text('Создать'),
),
],
),
),
);
}
@override
Widget build(BuildContext context) {
if (_isLoading) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
return Scaffold(
appBar: AppBar(
title: const Text('Будильники'),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: _loadAlarms,
tooltip: 'Обновить',
),
],
),
body: _alarms.isEmpty
? _buildEmptyState()
: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: _alarms.length,
itemBuilder: (context, index) {
final alarm = _alarms[index];
return _buildAlarmCard(alarm);
},
),
floatingActionButton: FloatingActionButton(
onPressed: _showAddAlarmDialog,
child: const Icon(Icons.add),
),
);
}
Widget _buildEmptyState() {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.alarm, size: 100, color: Colors.grey[400]),
const SizedBox(height: 24),
Text(
'Нет будильников',
style: TextStyle(fontSize: 20, color: Colors.grey[600]),
),
const SizedBox(height: 8),
Text(
'Создайте первый будильник',
style: TextStyle(color: Colors.grey[500]),
),
],
),
);
}
Widget _buildAlarmCard(AlarmModel alarm) {
return Card(
margin: const EdgeInsets.only(bottom: 12),
child: ListTile(
leading: Icon(
alarm.isEnabled ? Icons.alarm : Icons.alarm_off,
color: alarm.isEnabled ? Colors.blue : Colors.grey,
),
title: Text(
alarm.title,
style: TextStyle(
fontWeight: FontWeight.bold,
color: alarm.isEnabled ? null : Colors.grey,
),
),
subtitle: Text(
'${alarm.dateTime.day}.${alarm.dateTime.month}.${alarm.dateTime.year} '
'${alarm.dateTime.hour.toString().padLeft(2, '0')}:'
'${alarm.dateTime.minute.toString().padLeft(2, '0')}',
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
Switch(
value: alarm.isEnabled,
onChanged: (_) => _toggleAlarm(alarm),
),
IconButton(
icon: const Icon(Icons.delete, color: Colors.red),
onPressed: () => _deleteAlarm(alarm.id),
),
],
),
),
);
}
}