244 lines
7.3 KiB
Dart
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,
|
|
};
|
|
});
|