feat: surface read-only load errors

This commit is contained in:
Artem Kokos
2026-04-23 20:01:37 +07:00
parent 504f65fc5f
commit 5d2d0ac4a7
7 changed files with 491 additions and 76 deletions

View File

@@ -3,6 +3,8 @@ import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:geolocator/geolocator.dart';
import '../app/error_message.dart';
import '../app/load_state.dart';
import '../models/home_config.dart';
import '../services/api_client.dart';
import '../services/settings_service.dart';
@@ -694,50 +696,69 @@ class TasksNotifier extends Notifier<List<dynamic>> {
// ─── Статистика ──────────────────────────────────────────────
final statsProvider = NotifierProvider<StatsNotifier, Map<String, dynamic>>(
() => StatsNotifier(),
);
final statsProvider =
NotifierProvider<StatsNotifier, LoadState<Map<String, dynamic>>>(
() => StatsNotifier(),
);
class StatsNotifier extends Notifier<Map<String, dynamic>> {
class StatsNotifier extends Notifier<LoadState<Map<String, dynamic>>> {
@override
Map<String, dynamic> build() => {};
LoadState<Map<String, dynamic>> build() =>
const LoadState.idle(<String, dynamic>{});
Future<void> load({int days = 7}) async {
state = LoadState.loading(state.data);
try {
final api = ref.read(apiProvider);
final res = await api.getStatsSummary(days: days);
final data = res.data;
if (data is Map) {
state = Map<String, dynamic>.from(data);
if (data is! Map) {
throw FormatException('stats summary должен быть объектом');
}
final stats = Map<String, dynamic>.from(data);
final groups = stats['groups'];
final hasGroups = groups is List && groups.isNotEmpty;
state = hasGroups ? LoadState.data(stats) : LoadState.empty(stats);
} catch (e) {
debugPrint("Ошибка загрузки статистики: $e");
state = LoadState.error(state.data, describeLoadError(e));
}
}
}
// ─── Лог событий ─────────────────────────────────────────────
final eventLogProvider = NotifierProvider<EventLogNotifier, List<dynamic>>(
() => EventLogNotifier(),
);
final eventLogProvider =
NotifierProvider<EventLogNotifier, LoadState<List<dynamic>>>(
() => EventLogNotifier(),
);
class EventLogNotifier extends Notifier<List<dynamic>> {
class EventLogNotifier extends Notifier<LoadState<List<dynamic>>> {
@override
List<dynamic> build() => [];
LoadState<List<dynamic>> build() => const LoadState.idle(<dynamic>[]);
Future<void> load({int limit = 100}) async {
state = LoadState.loading(state.data);
try {
final api = ref.read(apiProvider);
final res = await api.getStatsLog(limit: limit);
final data = res.data;
late final List<dynamic> events;
if (data is List) {
state = data;
events = List<dynamic>.from(data);
} else if (data is Map) {
state = data['data'] ?? data['events'] ?? data.values.toList();
final value = data['data'] ?? data['events'] ?? data.values.toList();
if (value is! List) {
throw FormatException('stats log должен быть списком событий');
}
events = List<dynamic>.from(value);
} else {
throw FormatException('stats log должен быть списком событий');
}
state = events.isEmpty ? LoadState.empty(events) : LoadState.data(events);
} catch (e) {
debugPrint("Ошибка загрузки логов: $e");
state = LoadState.error(state.data, describeLoadError(e));
}
}
}