278 lines
7.4 KiB
Dart
278 lines
7.4 KiB
Dart
import 'package:hive_flutter/hive_flutter.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import '../models/focus_session.dart';
|
|
import '../models/user_progress.dart';
|
|
|
|
/// Service to manage local storage using Hive
|
|
class StorageService {
|
|
static const String _focusSessionBox = 'focus_sessions';
|
|
static const String _userProgressBox = 'user_progress';
|
|
static const String _progressKey = 'user_progress_key';
|
|
|
|
// Cache for today's sessions to improve performance
|
|
List<FocusSession>? _todaySessionsCache;
|
|
DateTime? _cacheDate;
|
|
|
|
// Cache for user progress
|
|
UserProgress? _userProgressCache;
|
|
|
|
/// Initialize Hive storage service
|
|
///
|
|
/// This method initializes Hive, registers adapters, and opens the focus sessions box.
|
|
/// It should be called once during app initialization.
|
|
Future<void> init() async {
|
|
try {
|
|
await Hive.initFlutter();
|
|
|
|
// Register adapters
|
|
Hive.registerAdapter(FocusSessionAdapter());
|
|
Hive.registerAdapter(UserProgressAdapter());
|
|
|
|
// Open boxes
|
|
await Hive.openBox<FocusSession>(_focusSessionBox);
|
|
await Hive.openBox<UserProgress>(_userProgressBox);
|
|
|
|
if (kDebugMode) {
|
|
print('StorageService initialized successfully');
|
|
}
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print('Failed to initialize StorageService: $e');
|
|
}
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// Get the focus sessions box
|
|
Box<FocusSession> get _sessionsBox => Hive.box<FocusSession>(_focusSessionBox);
|
|
|
|
/// Get the user progress box
|
|
Box<UserProgress> get _progressBox => Hive.box<UserProgress>(_userProgressBox);
|
|
|
|
/// Invalidate the cache when data changes
|
|
void _invalidateCache() {
|
|
_todaySessionsCache = null;
|
|
_cacheDate = null;
|
|
}
|
|
|
|
/// Invalidate user progress cache
|
|
void _invalidateProgressCache() {
|
|
_userProgressCache = null;
|
|
}
|
|
|
|
// ==================== User Progress Methods ====================
|
|
|
|
/// Get user progress (creates new one if doesn't exist)
|
|
UserProgress getUserProgress() {
|
|
try {
|
|
// Return cached progress if available
|
|
if (_userProgressCache != null) {
|
|
return _userProgressCache!;
|
|
}
|
|
|
|
// Try to get from box
|
|
var progress = _progressBox.get(_progressKey);
|
|
|
|
// Create new progress if doesn't exist
|
|
if (progress == null) {
|
|
progress = UserProgress();
|
|
_progressBox.put(_progressKey, progress);
|
|
}
|
|
|
|
// Cache and return
|
|
_userProgressCache = progress;
|
|
return progress;
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print('Failed to get user progress: $e');
|
|
}
|
|
// Return new progress as fallback
|
|
return UserProgress();
|
|
}
|
|
}
|
|
|
|
/// Save user progress
|
|
Future<void> saveUserProgress(UserProgress progress) async {
|
|
try {
|
|
await _progressBox.put(_progressKey, progress);
|
|
_userProgressCache = progress; // Update cache
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print('Failed to save user progress: $e');
|
|
}
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// Update user progress with a function
|
|
Future<void> updateUserProgress(Function(UserProgress) updateFn) async {
|
|
try {
|
|
final progress = getUserProgress();
|
|
updateFn(progress);
|
|
await saveUserProgress(progress);
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print('Failed to update user progress: $e');
|
|
}
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// Clear user progress (for testing/reset)
|
|
Future<void> clearUserProgress() async {
|
|
try {
|
|
await _progressBox.delete(_progressKey);
|
|
_invalidateProgressCache();
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print('Failed to clear user progress: $e');
|
|
}
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// Save a focus session to local storage
|
|
///
|
|
/// [session] - The focus session to save
|
|
/// Returns a Future that completes when the session is saved
|
|
Future<void> saveFocusSession(FocusSession session) async {
|
|
try {
|
|
await _sessionsBox.add(session);
|
|
_invalidateCache(); // Invalidate cache when data changes
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print('Failed to save focus session: $e');
|
|
}
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// Get all focus sessions from local storage
|
|
///
|
|
/// Returns a list of all focus sessions stored locally
|
|
List<FocusSession> getAllSessions() {
|
|
try {
|
|
return _sessionsBox.values.toList();
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print('Failed to get all sessions: $e');
|
|
}
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/// Get today's focus sessions with caching
|
|
///
|
|
/// Returns a list of focus sessions that occurred today
|
|
/// Uses caching to improve performance for frequent calls
|
|
List<FocusSession> getTodaySessions() {
|
|
try {
|
|
final now = DateTime.now();
|
|
final today = DateTime(now.year, now.month, now.day);
|
|
|
|
// Check if cache is valid
|
|
if (_todaySessionsCache != null && _cacheDate == today) {
|
|
return _todaySessionsCache!;
|
|
}
|
|
|
|
// Query and cache results
|
|
final sessions = _sessionsBox.values.where((session) {
|
|
final sessionDate = DateTime(
|
|
session.startTime.year,
|
|
session.startTime.month,
|
|
session.startTime.day,
|
|
);
|
|
return sessionDate == today;
|
|
}).toList();
|
|
|
|
// Update cache
|
|
_todaySessionsCache = sessions;
|
|
_cacheDate = today;
|
|
|
|
return sessions;
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print('Failed to get today\'s sessions: $e');
|
|
}
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/// Get total focus minutes for today
|
|
///
|
|
/// Returns the sum of actual minutes focused today
|
|
int getTodayTotalMinutes() {
|
|
return getTodaySessions()
|
|
.fold<int>(0, (sum, session) => sum + session.actualMinutes);
|
|
}
|
|
|
|
/// Get total distractions for today
|
|
///
|
|
/// Returns the total number of distractions recorded today
|
|
int getTodayDistractionCount() {
|
|
return getTodaySessions()
|
|
.fold<int>(0, (sum, session) => sum + session.distractionCount);
|
|
}
|
|
|
|
/// Get total completed sessions for today
|
|
///
|
|
/// Returns the number of focus sessions completed today
|
|
int getTodayCompletedCount() {
|
|
return getTodaySessions()
|
|
.where((session) => session.completed)
|
|
.length;
|
|
}
|
|
|
|
/// Get total sessions count for today (including stopped early)
|
|
///
|
|
/// Returns the total number of focus sessions started today
|
|
int getTodaySessionsCount() {
|
|
return getTodaySessions().length;
|
|
}
|
|
|
|
/// Delete a focus session from local storage
|
|
///
|
|
/// [session] - The focus session to delete
|
|
/// Returns a Future that completes when the session is deleted
|
|
Future<void> deleteSession(FocusSession session) async {
|
|
try {
|
|
await session.delete();
|
|
_invalidateCache(); // Invalidate cache when data changes
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print('Failed to delete focus session: $e');
|
|
}
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// Clear all sessions from local storage (for testing/debugging)
|
|
///
|
|
/// Returns a Future that completes when all sessions are cleared
|
|
Future<void> clearAllSessions() async {
|
|
try {
|
|
await _sessionsBox.clear();
|
|
_invalidateCache(); // Invalidate cache when data changes
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print('Failed to clear all sessions: $e');
|
|
}
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// Close all Hive boxes
|
|
///
|
|
/// Should be called when the app is closing to properly clean up resources
|
|
static Future<void> close() async {
|
|
try {
|
|
await Hive.close();
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print('Failed to close Hive boxes: $e');
|
|
}
|
|
rethrow;
|
|
}
|
|
}
|
|
}
|