Files
ignis_app/lib/screens/stats_screen.dart
2026-04-23 21:05:37 +07:00

223 lines
6.4 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 'package:flutter_riverpod/flutter_riverpod.dart';
import '../app/load_state.dart';
import '../models/stats_summary.dart';
import '../providers/providers.dart';
import '../widgets/load_error_view.dart';
/// Экран просмотра статистики.
/// Показывает сводку по группам за выбранный период.
class StatsScreen extends ConsumerStatefulWidget {
const StatsScreen({super.key});
@override
ConsumerState<StatsScreen> createState() => _StatsScreenState();
}
class _StatsScreenState extends ConsumerState<StatsScreen> {
int _days = 7;
@override
void initState() {
super.initState();
_load();
}
Future<void> _load() async {
await ref.read(statsProvider.notifier).load(days: _days);
}
@override
Widget build(BuildContext context) {
final statsState = ref.watch(statsProvider);
final stats = statsState.data;
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: _buildContent(statsState, stats.groups)),
],
),
);
}
Widget _buildContent(
LoadState<StatsSummary> statsState,
List<GroupStats> groups,
) {
if ((statsState.isIdle || statsState.isLoading) && groups.isEmpty) {
return const Center(
child: CircularProgressIndicator(color: Colors.deepOrange),
);
}
if (statsState.hasError && groups.isEmpty) {
return LoadErrorView(
title: 'Не удалось загрузить статистику',
message: statsState.errorMessage,
icon: Icons.bar_chart,
onRetry: _load,
);
}
if (groups.isEmpty) {
return const Center(
child: Text('Нет данных', style: TextStyle(color: Colors.white54)),
);
}
final hasStatusHeader = statsState.isLoading || statsState.hasError;
final statusHeaderCount = hasStatusHeader ? 1 : 0;
return RefreshIndicator(
color: Colors.deepOrange,
onRefresh: _load,
child: ListView.builder(
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.all(12),
itemCount: groups.length + statusHeaderCount,
itemBuilder: (context, index) {
if (hasStatusHeader && index == 0) {
if (statsState.isLoading) {
return const Padding(
padding: EdgeInsets.only(bottom: 12),
child: LinearProgressIndicator(color: Colors.deepOrange),
);
}
return LoadErrorBanner(
title: 'Не удалось обновить статистику',
message: statsState.errorMessage,
onRetry: _load,
);
}
final groupIndex = index - statusHeaderCount;
return _StatsCard(data: groups[groupIndex]);
},
),
);
}
}
/// Карточка статистики одной группы
class _StatsCard extends StatelessWidget {
final GroupStats data;
const _StatsCard({required this.data});
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.only(bottom: 8),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
data.name,
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
_StatRow(
icon: Icons.touch_app,
label: 'Всего команд',
value: data.totalCommands.toString(),
),
const SizedBox(height: 4),
_StatRow(
icon: Icons.power_settings_new,
label: 'Включений',
value: data.togglesOn.toString(),
color: Colors.green,
),
const SizedBox(height: 4),
_StatRow(
icon: Icons.power_off,
label: 'Выключений',
value: data.togglesOff.toString(),
color: Colors.redAccent,
),
if (data.estimatedHours != null) ...[
const SizedBox(height: 4),
_StatRow(
icon: Icons.access_time,
label: 'Примерное время работы',
value: data.formattedEstimatedHours,
color: Colors.amber,
),
],
],
),
),
);
}
}
/// Строка с иконкой, меткой и значением
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,
),
),
],
);
}
}