nokken/lib/src/features/mood_tracker/providers/mood_state.dart
2025-04-20 11:17:03 -04:00

244 lines
7.3 KiB
Dart

// SPDX-FileCopyrightText: © 2025 Nøkken.io <nokken.io@proton.me>
// SPDX-License-Identifier: AGPL-3.0
//
// mood_state.dart
// State management for mood entries using Riverpod
//
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:nokken/src/features/mood_tracker/models/mood_entry.dart';
import 'package:nokken/src/core/services/database/database_service.dart';
import 'package:nokken/src/core/services/database/database_service_mood.dart';
import 'package:nokken/src/features/medication_tracker/providers/medication_state.dart';
/// State class to handle loading and error states for mood entry data
class MoodState {
final List<MoodEntry> entries;
final bool isLoading;
final String? error;
const MoodState({
this.entries = const [],
this.isLoading = false,
this.error,
});
/// Create a new state object with updated fields
MoodState copyWith({
List<MoodEntry>? entries,
bool? isLoading,
String? error,
}) {
return MoodState(
entries: entries ?? this.entries,
isLoading: isLoading ?? this.isLoading,
error: error, // Pass null to clear error
);
}
}
/// Notifier class to handle mood state changes
class MoodNotifier extends StateNotifier<MoodState> {
final DatabaseService _databaseService;
MoodNotifier({required DatabaseService databaseService})
: _databaseService = databaseService,
super(const MoodState()) {
// Load mood entries when initialized
loadMoodEntries();
}
/// Load mood entries from the database
Future<void> loadMoodEntries() async {
try {
state = state.copyWith(isLoading: true, error: null);
final entries = await _databaseService.getAllMoodEntries();
state = state.copyWith(
entries: entries,
isLoading: false,
);
} catch (e) {
state = state.copyWith(
isLoading: false,
error: 'Failed to load mood entries: $e',
);
}
}
/// Add a new mood entry to the database
Future<void> addMoodEntry(MoodEntry entry) async {
try {
state = state.copyWith(isLoading: true, error: null);
// Save to database
await _databaseService.insertMoodEntry(entry);
// Update state immediately with new entry
state = state.copyWith(
entries: [...state.entries, entry],
isLoading: false,
);
} catch (e) {
state = state.copyWith(
isLoading: false,
error: 'Failed to add mood entry: $e',
);
}
}
/// Update an existing mood entry in the database
Future<void> updateMoodEntry(MoodEntry entry) async {
try {
state = state.copyWith(isLoading: true, error: null);
// Update in database
await _databaseService.updateMoodEntry(entry);
// Update state immediately
state = state.copyWith(
entries:
state.entries.map((e) => e.id == entry.id ? entry : e).toList(),
isLoading: false,
);
} catch (e) {
state = state.copyWith(
isLoading: false,
error: 'Failed to update mood entry: $e',
);
}
}
/// Delete a mood entry
Future<void> deleteMoodEntry(String id) async {
try {
state = state.copyWith(isLoading: true, error: null);
// Delete from database
await _databaseService.deleteMoodEntry(id);
// Update state immediately by filtering out the deleted entry
state = state.copyWith(
entries: state.entries.where((e) => e.id != id).toList(),
isLoading: false,
);
} catch (e) {
state = state.copyWith(
isLoading: false,
error: 'Failed to delete mood entry: $e',
);
}
}
/// Get a mood entry for a specific date
Future<MoodEntry?> getMoodEntryForDate(DateTime date) async {
try {
// Check if we already have it in state
final normalizedDate = DateTime(date.year, date.month, date.day);
final existingEntry = state.entries.firstWhere(
(entry) => DateTime(entry.date.year, entry.date.month, entry.date.day)
.isAtSameMomentAs(normalizedDate),
orElse: () => throw Exception('Not found in state'),
);
return existingEntry;
} catch (_) {
try {
// Try to fetch from database
return await _databaseService.getMoodEntryForDate(date);
} catch (e) {
// Handle error but don't update state
return null;
}
}
}
}
//----------------------------------------------------------------------------
// PROVIDER DEFINITIONS
//----------------------------------------------------------------------------
/// Main state notifier provider for mood entries
final moodStateProvider = StateNotifierProvider<MoodNotifier, MoodState>((ref) {
final databaseService = ref.watch(databaseServiceProvider);
return MoodNotifier(databaseService: databaseService);
});
//----------------------------------------------------------------------------
// CONVENIENCE PROVIDERS
//----------------------------------------------------------------------------
/// Provider for accessing the list of mood entries
final moodEntriesProvider = Provider<List<MoodEntry>>((ref) {
return ref.watch(moodStateProvider).entries;
});
/// Provider for checking if mood entries are loading
final moodEntriesLoadingProvider = Provider<bool>((ref) {
return ref.watch(moodStateProvider).isLoading;
});
/// Provider for accessing mood entry loading errors
final moodEntriesErrorProvider = Provider<String?>((ref) {
return ref.watch(moodStateProvider).error;
});
/// Provider for getting a mood entry for a specific date
final moodEntryForDateProvider =
FutureProvider.family<MoodEntry?, DateTime>((ref, date) async {
final moodNotifier = ref.watch(moodStateProvider.notifier);
return await moodNotifier.getMoodEntryForDate(date);
});
/// Provider for getting mood entry dates
final moodEntryDatesProvider = Provider<Set<DateTime>>((ref) {
final entries = ref.watch(moodStateProvider).entries;
return entries.map((entry) {
final date = entry.date;
return DateTime(date.year, date.month, date.day);
}).toSet();
});
final filteredMoodEntriesProvider =
Provider.family<List<MoodEntry>, String>((ref, timeframe) {
final entries = ref.watch(moodEntriesProvider);
if (timeframe == 'All Time') {
return entries;
}
// Extract start date based on timeframe
final now = DateTime.now();
final DateTime startDate;
switch (timeframe) {
case 'Last 7 Days':
startDate = now.subtract(const Duration(days: 7));
break;
case 'Last 30 Days':
startDate = now.subtract(const Duration(days: 30));
break;
case 'Last 90 Days':
startDate = now.subtract(const Duration(days: 90));
break;
case 'Last 6 Months':
startDate = DateTime(now.year, now.month - 6, now.day);
break;
case 'Last Year':
startDate = DateTime(now.year - 1, now.month, now.day);
break;
default:
return entries;
}
return entries.where((entry) => entry.date.isAfter(startDate)).toList();
});
/// Provider for mood icon colors
final moodColorsProvider = Provider<Map<MoodRating, Color>>((ref) {
return {
MoodRating.great: Colors.green.shade400,
MoodRating.good: Colors.lightGreen.shade400,
MoodRating.okay: Colors.amber.shade400,
MoodRating.meh: Colors.orange.shade400,
MoodRating.bad: Colors.red.shade400,
};
});