first commit
This commit is contained in:
39
lib/services/encouragement_service.dart
Normal file
39
lib/services/encouragement_service.dart
Normal file
@@ -0,0 +1,39 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
/// Service to manage encouragement messages
|
||||
class EncouragementService {
|
||||
List<String> _messages = [];
|
||||
final Random _random = Random();
|
||||
|
||||
/// Load encouragement messages from assets
|
||||
Future<void> loadMessages() async {
|
||||
try {
|
||||
final String jsonString =
|
||||
await rootBundle.loadString('assets/encouragements.json');
|
||||
final List<dynamic> jsonList = json.decode(jsonString);
|
||||
_messages = jsonList.cast<String>();
|
||||
} catch (e) {
|
||||
// Fallback messages if file can't be loaded
|
||||
_messages = [
|
||||
"Showing up is half the battle.",
|
||||
"Every minute counts.",
|
||||
"You're learning, not failing.",
|
||||
"Gentleness is strength.",
|
||||
"Progress over perfection.",
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a random encouragement message
|
||||
String getRandomMessage() {
|
||||
if (_messages.isEmpty) {
|
||||
return "You're doing great!";
|
||||
}
|
||||
return _messages[_random.nextInt(_messages.length)];
|
||||
}
|
||||
|
||||
/// Get all messages (for testing)
|
||||
List<String> getAllMessages() => List.from(_messages);
|
||||
}
|
||||
194
lib/services/notification_service.dart
Normal file
194
lib/services/notification_service.dart
Normal file
@@ -0,0 +1,194 @@
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
/// Notification Service - Handles local notifications
|
||||
class NotificationService {
|
||||
static final NotificationService _instance = NotificationService._internal();
|
||||
factory NotificationService() => _instance;
|
||||
NotificationService._internal();
|
||||
|
||||
final FlutterLocalNotificationsPlugin _notifications =
|
||||
FlutterLocalNotificationsPlugin();
|
||||
|
||||
bool _initialized = false;
|
||||
|
||||
/// Initialize notification service
|
||||
Future<void> initialize() async {
|
||||
if (_initialized) return;
|
||||
|
||||
// Skip initialization on web platform
|
||||
if (kIsWeb) {
|
||||
if (kDebugMode) {
|
||||
print('Notifications not supported on web platform');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Android initialization settings
|
||||
const androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher');
|
||||
|
||||
// iOS initialization settings
|
||||
const iosSettings = DarwinInitializationSettings(
|
||||
requestAlertPermission: true,
|
||||
requestBadgePermission: true,
|
||||
requestSoundPermission: true,
|
||||
);
|
||||
|
||||
const initSettings = InitializationSettings(
|
||||
android: androidSettings,
|
||||
iOS: iosSettings,
|
||||
);
|
||||
|
||||
await _notifications.initialize(
|
||||
initSettings,
|
||||
onDidReceiveNotificationResponse: _onNotificationTapped,
|
||||
);
|
||||
|
||||
_initialized = true;
|
||||
if (kDebugMode) {
|
||||
print('Notification service initialized successfully');
|
||||
}
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('Failed to initialize notifications: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle notification tap
|
||||
void _onNotificationTapped(NotificationResponse response) {
|
||||
if (kDebugMode) {
|
||||
print('Notification tapped: ${response.payload}');
|
||||
}
|
||||
// TODO: Navigate to appropriate screen if needed
|
||||
}
|
||||
|
||||
/// Request notification permissions (iOS only, Android auto-grants)
|
||||
Future<bool> requestPermissions() async {
|
||||
if (kIsWeb) return false;
|
||||
|
||||
try {
|
||||
final result = await _notifications
|
||||
.resolvePlatformSpecificImplementation<
|
||||
IOSFlutterLocalNotificationsPlugin>()
|
||||
?.requestPermissions(
|
||||
alert: true,
|
||||
badge: true,
|
||||
sound: true,
|
||||
);
|
||||
|
||||
return result ?? true; // Android always returns true
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('Failed to request permissions: $e');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Show focus session completed notification
|
||||
Future<void> showFocusCompletedNotification({
|
||||
required int minutes,
|
||||
required int distractionCount,
|
||||
}) async {
|
||||
if (kIsWeb || !_initialized) return;
|
||||
|
||||
try {
|
||||
const androidDetails = AndroidNotificationDetails(
|
||||
'focus_completed',
|
||||
'Focus Session Completed',
|
||||
channelDescription: 'Notifications for completed focus sessions',
|
||||
importance: Importance.high,
|
||||
priority: Priority.high,
|
||||
enableVibration: true,
|
||||
playSound: true,
|
||||
);
|
||||
|
||||
const iosDetails = DarwinNotificationDetails(
|
||||
presentAlert: true,
|
||||
presentBadge: true,
|
||||
presentSound: true,
|
||||
);
|
||||
|
||||
const notificationDetails = NotificationDetails(
|
||||
android: androidDetails,
|
||||
iOS: iosDetails,
|
||||
);
|
||||
|
||||
// Create notification message
|
||||
final title = '🎉 Focus session complete!';
|
||||
final body = distractionCount == 0
|
||||
? 'You focused for $minutes ${minutes == 1 ? 'minute' : 'minutes'} without distractions!'
|
||||
: 'You focused for $minutes ${minutes == 1 ? 'minute' : 'minutes'}. Great effort!';
|
||||
|
||||
await _notifications.show(
|
||||
0, // Notification ID
|
||||
title,
|
||||
body,
|
||||
notificationDetails,
|
||||
payload: 'focus_completed',
|
||||
);
|
||||
|
||||
if (kDebugMode) {
|
||||
print('Notification shown: $title - $body');
|
||||
}
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('Failed to show notification: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Show reminder notification (optional feature for future)
|
||||
Future<void> showReminderNotification({
|
||||
required String message,
|
||||
}) async {
|
||||
if (kIsWeb || !_initialized) return;
|
||||
|
||||
try {
|
||||
const androidDetails = AndroidNotificationDetails(
|
||||
'reminders',
|
||||
'Focus Reminders',
|
||||
channelDescription: 'Gentle reminders to focus',
|
||||
importance: Importance.defaultImportance,
|
||||
priority: Priority.defaultPriority,
|
||||
);
|
||||
|
||||
const iosDetails = DarwinNotificationDetails();
|
||||
|
||||
const notificationDetails = NotificationDetails(
|
||||
android: androidDetails,
|
||||
iOS: iosDetails,
|
||||
);
|
||||
|
||||
await _notifications.show(
|
||||
1, // Different ID from completion notifications
|
||||
'💚 FocusBuddy',
|
||||
message,
|
||||
notificationDetails,
|
||||
payload: 'reminder',
|
||||
);
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('Failed to show reminder: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Cancel all notifications
|
||||
Future<void> cancelAll() async {
|
||||
if (kIsWeb || !_initialized) return;
|
||||
|
||||
try {
|
||||
await _notifications.cancelAll();
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('Failed to cancel notifications: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if notifications are supported on this platform
|
||||
bool get isSupported => !kIsWeb;
|
||||
}
|
||||
80
lib/services/storage_service.dart
Normal file
80
lib/services/storage_service.dart
Normal file
@@ -0,0 +1,80 @@
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import '../models/focus_session.dart';
|
||||
|
||||
/// Service to manage local storage using Hive
|
||||
class StorageService {
|
||||
static const String _focusSessionBox = 'focus_sessions';
|
||||
|
||||
/// Initialize Hive
|
||||
static Future<void> init() async {
|
||||
await Hive.initFlutter();
|
||||
|
||||
// Register adapters
|
||||
Hive.registerAdapter(FocusSessionAdapter());
|
||||
|
||||
// Open boxes
|
||||
await Hive.openBox<FocusSession>(_focusSessionBox);
|
||||
}
|
||||
|
||||
/// Get the focus sessions box
|
||||
Box<FocusSession> get _sessionsBox => Hive.box<FocusSession>(_focusSessionBox);
|
||||
|
||||
/// Save a focus session
|
||||
Future<void> saveFocusSession(FocusSession session) async {
|
||||
await _sessionsBox.add(session);
|
||||
}
|
||||
|
||||
/// Get all focus sessions
|
||||
List<FocusSession> getAllSessions() {
|
||||
return _sessionsBox.values.toList();
|
||||
}
|
||||
|
||||
/// Get today's focus sessions
|
||||
List<FocusSession> getTodaySessions() {
|
||||
final now = DateTime.now();
|
||||
final today = DateTime(now.year, now.month, now.day);
|
||||
|
||||
return _sessionsBox.values.where((session) {
|
||||
final sessionDate = DateTime(
|
||||
session.startTime.year,
|
||||
session.startTime.month,
|
||||
session.startTime.day,
|
||||
);
|
||||
return sessionDate == today;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
/// Get total focus minutes for today
|
||||
int getTodayTotalMinutes() {
|
||||
return getTodaySessions()
|
||||
.fold<int>(0, (sum, session) => sum + session.actualMinutes);
|
||||
}
|
||||
|
||||
/// Get total distractions for today
|
||||
int getTodayDistractionCount() {
|
||||
return getTodaySessions()
|
||||
.fold<int>(0, (sum, session) => sum + session.distractionCount);
|
||||
}
|
||||
|
||||
/// Get total completed sessions for today
|
||||
int getTodayCompletedCount() {
|
||||
return getTodaySessions()
|
||||
.where((session) => session.completed)
|
||||
.length;
|
||||
}
|
||||
|
||||
/// Delete a focus session
|
||||
Future<void> deleteSession(FocusSession session) async {
|
||||
await session.delete();
|
||||
}
|
||||
|
||||
/// Clear all sessions (for testing/debugging)
|
||||
Future<void> clearAllSessions() async {
|
||||
await _sessionsBox.clear();
|
||||
}
|
||||
|
||||
/// Close all boxes
|
||||
static Future<void> close() async {
|
||||
await Hive.close();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user