|
| 1 | +import 'package:flutter_riverpod/flutter_riverpod.dart'; |
| 2 | +import 'package:readmigo/features/analytics/data/analytics_service.dart'; |
| 3 | + |
| 4 | +class AnalyticsState { |
| 5 | + final int totalReadingMinutes; |
| 6 | + final int booksCompleted; |
| 7 | + final int currentStreak; |
| 8 | + final int wordsLearned; |
| 9 | + final int totalSessions; |
| 10 | + final double avgSessionMinutes; |
| 11 | + final List<DailyReading> dailyStats; |
| 12 | + final bool isLoading; |
| 13 | + final String? error; |
| 14 | + |
| 15 | + const AnalyticsState({ |
| 16 | + this.totalReadingMinutes = 0, |
| 17 | + this.booksCompleted = 0, |
| 18 | + this.currentStreak = 0, |
| 19 | + this.wordsLearned = 0, |
| 20 | + this.totalSessions = 0, |
| 21 | + this.avgSessionMinutes = 0, |
| 22 | + this.dailyStats = const [], |
| 23 | + this.isLoading = false, |
| 24 | + this.error, |
| 25 | + }); |
| 26 | + |
| 27 | + String get totalReadingFormatted { |
| 28 | + final hours = totalReadingMinutes ~/ 60; |
| 29 | + final mins = totalReadingMinutes % 60; |
| 30 | + if (hours > 0) return '${hours}h ${mins}m'; |
| 31 | + return '${mins}m'; |
| 32 | + } |
| 33 | + |
| 34 | + AnalyticsState copyWith({ |
| 35 | + int? totalReadingMinutes, |
| 36 | + int? booksCompleted, |
| 37 | + int? currentStreak, |
| 38 | + int? wordsLearned, |
| 39 | + int? totalSessions, |
| 40 | + double? avgSessionMinutes, |
| 41 | + List<DailyReading>? dailyStats, |
| 42 | + bool? isLoading, |
| 43 | + String? Function()? error, |
| 44 | + }) { |
| 45 | + return AnalyticsState( |
| 46 | + totalReadingMinutes: totalReadingMinutes ?? this.totalReadingMinutes, |
| 47 | + booksCompleted: booksCompleted ?? this.booksCompleted, |
| 48 | + currentStreak: currentStreak ?? this.currentStreak, |
| 49 | + wordsLearned: wordsLearned ?? this.wordsLearned, |
| 50 | + totalSessions: totalSessions ?? this.totalSessions, |
| 51 | + avgSessionMinutes: avgSessionMinutes ?? this.avgSessionMinutes, |
| 52 | + dailyStats: dailyStats ?? this.dailyStats, |
| 53 | + isLoading: isLoading ?? this.isLoading, |
| 54 | + error: error != null ? error() : this.error, |
| 55 | + ); |
| 56 | + } |
| 57 | +} |
| 58 | + |
| 59 | +class DailyReading { |
| 60 | + final DateTime date; |
| 61 | + final int minutes; |
| 62 | + final int wordsLearned; |
| 63 | + |
| 64 | + const DailyReading({ |
| 65 | + required this.date, |
| 66 | + this.minutes = 0, |
| 67 | + this.wordsLearned = 0, |
| 68 | + }); |
| 69 | + |
| 70 | + factory DailyReading.fromJson(Map<String, dynamic> json) { |
| 71 | + return DailyReading( |
| 72 | + date: DateTime.parse(json['date'] as String), |
| 73 | + minutes: json['minutes'] as int? ?? json['readingMinutes'] as int? ?? 0, |
| 74 | + wordsLearned: json['wordsLearned'] as int? ?? 0, |
| 75 | + ); |
| 76 | + } |
| 77 | +} |
| 78 | + |
| 79 | +class AnalyticsNotifier extends Notifier<AnalyticsState> { |
| 80 | + @override |
| 81 | + AnalyticsState build() => const AnalyticsState(); |
| 82 | + |
| 83 | + AnalyticsService get _service => ref.read(analyticsServiceProvider); |
| 84 | + |
| 85 | + Future<void> loadAnalytics() async { |
| 86 | + state = state.copyWith(isLoading: true, error: () => null); |
| 87 | + try { |
| 88 | + final results = await Future.wait([ |
| 89 | + _service.getOverview(), |
| 90 | + _service.getDailyStats(), |
| 91 | + ]); |
| 92 | + final overview = results[0] as Map<String, dynamic>; |
| 93 | + final daily = results[1] as List<Map<String, dynamic>>; |
| 94 | + |
| 95 | + state = state.copyWith( |
| 96 | + totalReadingMinutes: overview['totalReadingMinutes'] as int? ?? 0, |
| 97 | + booksCompleted: overview['booksCompleted'] as int? ?? 0, |
| 98 | + currentStreak: |
| 99 | + overview['currentStreak'] as int? ?? |
| 100 | + overview['streakDays'] as int? ?? |
| 101 | + 0, |
| 102 | + wordsLearned: |
| 103 | + overview['wordsLearned'] as int? ?? |
| 104 | + overview['totalWordsLearned'] as int? ?? |
| 105 | + 0, |
| 106 | + totalSessions: overview['totalSessions'] as int? ?? 0, |
| 107 | + avgSessionMinutes: |
| 108 | + (overview['avgSessionMinutes'] as num?)?.toDouble() ?? 0, |
| 109 | + dailyStats: daily.map((e) => DailyReading.fromJson(e)).toList(), |
| 110 | + isLoading: false, |
| 111 | + ); |
| 112 | + } catch (e) { |
| 113 | + state = state.copyWith(isLoading: false, error: () => e.toString()); |
| 114 | + } |
| 115 | + } |
| 116 | +} |
| 117 | + |
| 118 | +final analyticsProvider = |
| 119 | + NotifierProvider<AnalyticsNotifier, AnalyticsState>(AnalyticsNotifier.new); |
0 commit comments