Waaaay big enchancements

This commit is contained in:
Artem Kokos
2026-04-02 23:51:28 +07:00
parent 6221fbcc71
commit 5e09f41747
14 changed files with 1308 additions and 111 deletions

View File

@@ -0,0 +1,208 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../providers/providers.dart';
/// Экран просмотра статистики.
/// Показывает сводку по группам за выбранный период.
class StatsScreen extends ConsumerStatefulWidget {
const StatsScreen({super.key});
@override
ConsumerState<StatsScreen> createState() => _StatsScreenState();
}
class _StatsScreenState extends ConsumerState<StatsScreen> {
bool _loading = true;
int _days = 7;
@override
void initState() {
super.initState();
_load();
}
Future<void> _load() async {
setState(() => _loading = true);
await ref.read(statsProvider.notifier).load(days: _days);
if (mounted) setState(() => _loading = false);
}
@override
Widget build(BuildContext context) {
final stats = ref.watch(statsProvider);
final groups = (stats['groups'] as List<dynamic>?) ?? [];
return Scaffold(
appBar: AppBar(
title: const Text('СТАТИСТИКА'),
),
body: Column(
children: [
// ─── Переключатель периода ───
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Row(
children: [
const Text('Период:', style: TextStyle(color: Colors.white54)),
const SizedBox(width: 12),
...[1, 7, 14, 30].map(
(d) => Padding(
padding: const EdgeInsets.only(right: 8),
child: ChoiceChip(
label: Text('$d д.'),
selected: _days == d,
selectedColor: Colors.deepOrange,
onSelected: (_) {
setState(() => _days = d);
_load();
},
),
),
),
],
),
),
// ─── Содержимое ───
Expanded(
child: _loading
? const Center(
child: CircularProgressIndicator(color: Colors.deepOrange),
)
: groups.isEmpty
? const Center(
child: Text(
'Нет данных',
style: TextStyle(color: Colors.white54),
),
)
: RefreshIndicator(
color: Colors.deepOrange,
onRefresh: _load,
child: ListView.builder(
padding: const EdgeInsets.all(12),
itemCount: groups.length,
itemBuilder: (context, index) {
final g = groups[index];
return _StatsCard(data: g is Map
? Map<String, dynamic>.from(g)
: {});
},
),
),
),
],
),
);
}
}
/// Карточка статистики одной группы
class _StatsCard extends StatelessWidget {
final Map<String, dynamic> data;
const _StatsCard({required this.data});
@override
Widget build(BuildContext context) {
final targetId = (data['target_id'] ?? data['group_id'] ?? data['id'] ?? '').toString();
final name = (data['name'] ?? targetId).toString();
final totalCommands = data['total_commands'] ?? 0;
final togglesOn = data['toggles_on'] ?? 0;
final togglesOff = data['toggles_off'] ?? 0;
final estimatedHours = data['estimated_hours'];
return Card(
margin: const EdgeInsets.only(bottom: 8),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
name,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
_StatRow(
icon: Icons.touch_app,
label: 'Всего команд',
value: totalCommands.toString(),
),
const SizedBox(height: 4),
_StatRow(
icon: Icons.power_settings_new,
label: 'Включений',
value: togglesOn.toString(),
color: Colors.green,
),
const SizedBox(height: 4),
_StatRow(
icon: Icons.power_off,
label: 'Выключений',
value: togglesOff.toString(),
color: Colors.redAccent,
),
if (estimatedHours != null) ...[
const SizedBox(height: 4),
_StatRow(
icon: Icons.access_time,
label: 'Примерное время работы',
value: _formatHours(estimatedHours),
color: Colors.amber,
),
],
],
),
),
);
}
String _formatHours(dynamic hours) {
final h = (hours is num) ? hours.toDouble() : 0.0;
if (h < 1) return '${(h * 60).round()} мин';
return '${h.toStringAsFixed(1)} ч';
}
}
/// Строка с иконкой, меткой и значением
class _StatRow extends StatelessWidget {
final IconData icon;
final String label;
final String value;
final Color? color;
const _StatRow({
required this.icon,
required this.label,
required this.value,
this.color,
});
@override
Widget build(BuildContext context) {
return Row(
children: [
Icon(icon, size: 16, color: color ?? Colors.white38),
const SizedBox(width: 8),
Expanded(
child: Text(
label,
style: const TextStyle(fontSize: 13, color: Colors.white54),
),
),
Text(
value,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: color ?? Colors.white70,
),
),
],
);
}
}