import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:latlong2/latlong.dart'; import 'package:rogapp/features/data/checkpoint.dart'; import 'package:rogapp/features/data/data_provider.dart'; import 'package:rogapp/features/data/user_location.dart'; import 'package:rogapp/features/state/game_state.dart'; import 'package:rogapp/features/utils/params.dart'; final gameViewModelProvider = StateNotifierProvider((ref) { final dataProvider = DataProvider(); return GameViewModel(dataProvider); }); class GameViewModel extends StateNotifier { final DataProvider _dataProvider; GameViewModel(this._dataProvider) : super(const GameState()) { loadInitialData(); } Future loadInitialData() async { List markers = await _dataProvider.getMarkers(); state = state.copyWith(markers: markers); } void setMarkers(List markers) { state = state.copyWith(markers: markers); } void startGame() { if (state.isWithinStartRadius) { state = state.copyWith(status: GameStatus.started); } } void updateLocation(UserLocation location) { Feature startMarker = state.markers.firstWhere( (marker) => marker.properties.cp == -1, orElse: () => Feature.empty()); List checkpoints = state.markers.where((marker) => marker.properties.cp != -1).toList(); bool isWithinStartRadius = _checkProximity(location, startMarker /* start marker location */ ); bool hasLeftArea = state.hasLeftStartArea || _checkHasLeftStartArea(location, startMarker); Feature? atCheckpoint = _checkAtAnyCheckpoint(location, checkpoints); // Check if at any checkpoint (except start point after game started) bool isAtCheckpoint = state.status == GameStatus.started && atCheckpoint != null; if (isAtCheckpoint) { _handleCheckpointArrival(location, atCheckpoint); } // Update game state based on the new location state = state.copyWith( currentLocation: location, isWithinStartRadius: isWithinStartRadius, hasLeftStartArea: hasLeftArea, status: isAtCheckpoint ? GameStatus.atCheckpoint : state.status, ); } void _handleCheckpointArrival(UserLocation location, Feature marker) { // Directly set the current checkpoint; no need to check if the photo was taken here state = state.copyWith(currentCheckpoint: marker); // If no checkpoint photo has been taken yet, set the pending action to take a checkpoint photo if (!state.checkpointPhotoTaken) { state = state.copyWith(pendingAction: PendingAction.checkInPhoto); } // Additional actions specific to buy points can be prepared here, but it's usually better // to handle them after the checkpoint photo is submitted, not at the moment of arrival. } void submitCheckInPhoto(String photoPath) { // Logic to save or upload the check-in photo... state = state.copyWith(checkpointPhotoTaken: true); // Determine the next action based on the checkpoint type if (state.currentCheckpoint != null && isBuyPoint(state.currentCheckpoint!)) { state = state.copyWith(pendingAction: PendingAction.receiptPhoto); } else { // If it's not a buy point, mark the checkpoint visit as complete _markCheckpointVisited(state.currentCheckpoint!); } } void submitReceiptPhoto(String photoPath) { // Logic to save or upload the receipt photo... state = state.copyWith(receiptPhotoTaken: true); // After submitting the receipt photo, mark the checkpoint visit as complete _markCheckpointVisited(state.currentCheckpoint!); } void _markCheckpointVisited(Feature checkpoint) { state = state.copyWith( visitedMarkers: Set.from(state.visitedMarkers)..add(checkpoint), hasVisitedCheckpoint: true, pendingAction: PendingAction.none, // Reset the pending action ); // Additional logic to determine if the game is completed... } bool isBuyPoint(Feature checkpoint) { if (checkpoint.properties.buyPoint != null) { return true; } return false; } bool _checkProximity(UserLocation location, Feature startMarker) { const Distance distance = Distance(); return distance( LatLng(location.latitude, location.longitude), LatLng(startMarker.geometry.latitude, startMarker.geometry.longitude)) <= startMinDistance; } bool _checkHasLeftStartArea(UserLocation location, Feature startMarker) { const Distance distance = Distance(); return distance( LatLng(location.latitude, location.longitude), LatLng(startMarker.geometry.latitude, startMarker.geometry.longitude)) >= minAwayfromStartDistance; } Feature? _checkAtAnyCheckpoint(UserLocation location, List markers) { const Distance distance = Distance(); for (Feature marker in markers) { final double distanceInMeters = distance( LatLng(location.latitude, location.longitude), LatLng(marker.geometry.latitude, marker.geometry.longitude), ); if (marker.properties.checkinRadious != null && marker.properties.checkinRadious! >= distanceInMeters) { return marker; } } return null; } Feature? _getCheckpointFromLocation(UserLocation location) { const thresholdDistance = 50.0; // meters, adjust based on your game design const Distance distance = Distance(); for (final marker in state.markers) { final double distanceInMeters = distance( LatLng(location.latitude, location.longitude), LatLng(marker.geometry.latitude, marker.geometry.longitude), ); if (distanceInMeters <= (marker.properties.checkinRadious ?? thresholdDistance)) { return marker; } } return null; // No checkpoint is close enough to the user's location } bool _isBackAtStart(UserLocation location) { const startThresholdDistance = 150.0; // Define proximity threshold const Distance distance = Distance(); // Assuming the start point is the first marker // Adjust based on your game's logic final startPoint = state.markers.first; final double distanceToStart = distance( LatLng(location.latitude, location.longitude), LatLng(startPoint.geometry.latitude, startPoint.geometry.longitude), ); // User is considered back at the start if within the threshold distance return distanceToStart <= startThresholdDistance; } void endGame() { if (state.status == GameStatus.gameCompleted) { // Perform any end game actions } } void loadGameState(GameState gameState) { state = gameState; } }