// SPDX-FileCopyrightText: © 2025 Nøkken.io // 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 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? 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 { 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 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 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 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 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 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((ref) { final databaseService = ref.watch(databaseServiceProvider); return MoodNotifier(databaseService: databaseService); }); //---------------------------------------------------------------------------- // CONVENIENCE PROVIDERS //---------------------------------------------------------------------------- /// Provider for accessing the list of mood entries final moodEntriesProvider = Provider>((ref) { return ref.watch(moodStateProvider).entries; }); /// Provider for checking if mood entries are loading final moodEntriesLoadingProvider = Provider((ref) { return ref.watch(moodStateProvider).isLoading; }); /// Provider for accessing mood entry loading errors final moodEntriesErrorProvider = Provider((ref) { return ref.watch(moodStateProvider).error; }); /// Provider for getting a mood entry for a specific date final moodEntryForDateProvider = FutureProvider.family((ref, date) async { final moodNotifier = ref.watch(moodStateProvider.notifier); return await moodNotifier.getMoodEntryForDate(date); }); /// Provider for getting mood entry dates final moodEntryDatesProvider = Provider>((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, 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>((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, }; });