mirror of
https://github.com/Dvorinka/1356.git
synced 2026-06-04 12:02:56 +00:00
37ffb93923
Version: 1.1.0 Major changes: - Implemented complete Flutter app structure with all core features - Added comprehensive UI screens for auth, countdown, goals, profile, settings, and social features - Integrated Supabase backend with authentication and data repositories - Added offline support with Hive caching and local storage - Implemented comprehensive routing with go_router - Added location services with Google Maps integration - Implemented notifications and home widget support - Added voice recording capabilities and AI chat features - Created comprehensive test suite and documentation - Added Android and iOS platform configurations - Implemented achievements system and social features - Added calendar integration and bucket list functionality This represents a complete Phase 1 milestone with 3,775 additions across 31 files.
171 lines
5.6 KiB
Dart
171 lines
5.6 KiB
Dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import '../../../data/models/goal_model.dart';
|
|
import '../../../data/repositories/goals_repository.dart';
|
|
import '../../../bootstrap/supabase_client.dart';
|
|
import '../../../core/errors/failure.dart';
|
|
import '../../../core/services/analytics_service.dart';
|
|
import '../../auth/application/auth_controller.dart';
|
|
|
|
class GoalsController extends StateNotifier<GoalsState> {
|
|
final GoalsRepository _repository;
|
|
final String _userId;
|
|
final AnalyticsService _analytics = AnalyticsService();
|
|
|
|
GoalsController(this._repository, this._userId) : super(const GoalsState.initial()) {
|
|
loadGoals();
|
|
}
|
|
|
|
Future<void> loadGoals() async {
|
|
state = const GoalsState.loading();
|
|
try {
|
|
final goals = await _repository.getGoals(_userId);
|
|
state = GoalsState.loaded(goals);
|
|
} catch (e) {
|
|
state = GoalsState.error(e.toString());
|
|
_analytics.logError(error: e.toString(), context: 'loadGoals');
|
|
}
|
|
}
|
|
|
|
Future<void> createGoal(Goal goal) async {
|
|
try {
|
|
final currentGoalsCount = await _repository.getGoalsCount(_userId);
|
|
if (currentGoalsCount >= GoalsRepository.maxGoals) {
|
|
throw const ValidationFailure('You can only have up to ${GoalsRepository.maxGoals} goals in your bucket list');
|
|
}
|
|
|
|
await _repository.createGoal(goal);
|
|
_analytics.logGoalCreated(
|
|
goalId: goal.id,
|
|
hasLocation: goal.hasLocation.toString(),
|
|
hasImage: goal.hasImage.toString(),
|
|
);
|
|
await loadGoals();
|
|
} on Failure catch (failure) {
|
|
state = GoalsState.error(failure.message);
|
|
_analytics.logError(error: failure.message, context: 'createGoal');
|
|
} catch (e) {
|
|
state = GoalsState.error(e.toString());
|
|
_analytics.logError(error: e.toString(), context: 'createGoal');
|
|
}
|
|
}
|
|
|
|
Future<void> updateGoal(Goal goal) async {
|
|
try {
|
|
await _repository.updateGoal(goal);
|
|
_analytics.logGoalUpdated(goalId: goal.id);
|
|
await loadGoals();
|
|
} on Failure catch (failure) {
|
|
state = GoalsState.error(failure.message);
|
|
_analytics.logError(error: failure.message, context: 'updateGoal');
|
|
} catch (e) {
|
|
state = GoalsState.error(e.toString());
|
|
_analytics.logError(error: e.toString(), context: 'updateGoal');
|
|
}
|
|
}
|
|
|
|
Future<void> deleteGoal(String goalId) async {
|
|
try {
|
|
final canModify = await _repository.canModifyGoals(_userId);
|
|
if (!canModify) {
|
|
throw const ValidationFailure('Cannot delete goals after countdown has started');
|
|
}
|
|
|
|
await _repository.deleteGoal(goalId);
|
|
_analytics.logGoalDeleted(goalId: goalId);
|
|
await loadGoals();
|
|
} on Failure catch (failure) {
|
|
state = GoalsState.error(failure.message);
|
|
_analytics.logError(error: failure.message, context: 'deleteGoal');
|
|
} catch (e) {
|
|
state = GoalsState.error(e.toString());
|
|
_analytics.logError(error: e.toString(), context: 'deleteGoal');
|
|
}
|
|
}
|
|
|
|
Future<void> updateGoalProgress(String goalId, int progress) async {
|
|
try {
|
|
final goals = state.goals;
|
|
final goal = goals.firstWhere((g) => g.id == goalId);
|
|
final updatedGoal = goal.copyWith(progress: progress);
|
|
await _repository.updateGoal(updatedGoal);
|
|
await loadGoals();
|
|
} on Failure catch (failure) {
|
|
state = GoalsState.error(failure.message);
|
|
_analytics.logError(error: failure.message, context: 'updateGoalProgress');
|
|
} catch (e) {
|
|
state = GoalsState.error(e.toString());
|
|
_analytics.logError(error: e.toString(), context: 'updateGoalProgress');
|
|
}
|
|
}
|
|
|
|
Future<void> markGoalAsCompleted(String goalId) async {
|
|
try {
|
|
final goals = state.goals;
|
|
final goal = goals.firstWhere((g) => g.id == goalId);
|
|
final updatedGoal = goal.copyWith(
|
|
progress: 100,
|
|
completed: true,
|
|
);
|
|
await _repository.updateGoal(updatedGoal);
|
|
|
|
final daysInChallenge = goal.createdAt.difference(DateTime.now()).inDays.abs();
|
|
_analytics.logGoalCompleted(
|
|
goalId: goalId,
|
|
daysInChallenge: daysInChallenge,
|
|
);
|
|
|
|
await loadGoals();
|
|
} on Failure catch (failure) {
|
|
state = GoalsState.error(failure.message);
|
|
_analytics.logError(error: failure.message, context: 'markGoalAsCompleted');
|
|
} catch (e) {
|
|
state = GoalsState.error(e.toString());
|
|
_analytics.logError(error: e.toString(), context: 'markGoalAsCompleted');
|
|
}
|
|
}
|
|
|
|
bool get canAddMoreGoals {
|
|
return state.goals.length < GoalsRepository.maxGoals;
|
|
}
|
|
|
|
int get remainingGoalsSlots {
|
|
return GoalsRepository.maxGoals - state.goals.length;
|
|
}
|
|
}
|
|
|
|
class GoalsState {
|
|
final bool isLoading;
|
|
final List<Goal> goals;
|
|
final String? error;
|
|
|
|
const GoalsState({
|
|
this.isLoading = false,
|
|
this.goals = const [],
|
|
this.error,
|
|
});
|
|
|
|
const GoalsState.initial() : isLoading = false, goals = const [], error = null;
|
|
|
|
const GoalsState.loading() : isLoading = true, goals = const [], error = null;
|
|
|
|
const GoalsState.loaded(this.goals) : isLoading = false, error = null;
|
|
|
|
const GoalsState.error(this.error) : isLoading = false, goals = const [];
|
|
}
|
|
|
|
final goalsRepositoryProvider = Provider<GoalsRepository>((ref) {
|
|
return GoalsRepository(supabaseClient);
|
|
});
|
|
|
|
final goalsControllerProvider = StateNotifierProvider<GoalsController, GoalsState>((ref) {
|
|
final repository = ref.watch(goalsRepositoryProvider);
|
|
final authController = ref.read(authControllerProvider.notifier);
|
|
final userId = authController.currentUserId ?? '';
|
|
|
|
if (userId.isEmpty) {
|
|
return GoalsController(repository, 'placeholder_user_id');
|
|
}
|
|
|
|
return GoalsController(repository, userId);
|
|
});
|