elta AI App - Implementing Emergency Contact Selection During Trip Check-in

Plan for Implementing Emergency Contact Selection During Trip Check-in

This plan outlines the necessary modifications to your Flutter application using AWS Amplify to allow users to select an emergency contact when checking into a trip.

Phase 1: Update Data Models

  1. Modify EmergencyContact Model (if necessary for querying by user):
    • File: Your GraphQL schema (e.g., amplify/backend/api/<YOUR_API_NAME>/schema.graphql) for the EmergencyContact model.
    • Purpose: Ensure you can efficiently query emergency contacts for the logged-in user. If you're not already storing a userID or relying on the implicit owner field from @auth rules in a way that's easily queryable by your application logic, consider adding an explicit userID.
    • Action: If you modify the schema, run amplify codegen models in your terminal to update the Dart model files in lib/models/.
  2. Modify CheckInLog Model:
    • File: Your GraphQL schema for the CheckInLog model.
    • Purpose: Add a field to store the ID of the selected emergency contact for that specific check-in.
    • File (Dart Model - after codegen): lib/models/CheckInLog.dart will be updated.
    • Action: Run amplify codegen models after updating the schema.

Change (Conceptual in Dart - this will be auto-generated):

// lib/models/CheckInLog.dart (Illustrative - generated by Amplify)
// ... other imports
// import 'package:amplify_flutter/amplify_flutter.dart';

// part 'CheckInLog.g.dart'; // Make sure this line exists

// @JsonSerializable(explicitToJson: true) // For older Amplify versions, or if you customize
// @immutable // For older Amplify versions
// class CheckInLog extends Model {
//   // ... existing fields ...
//   final String? selectedEmergencyContactID; // New field

//   const CheckInLog({
//     // ... existing parameters ...
//     this.selectedEmergencyContactID,
//   });
//   // ... fromJson, toJson, copyWith, etc. will be updated ...
// }

Example Schema Change:

type CheckInLog @model @auth(rules: [
  {allow: owner, operations: [create, read, update, delete]}
]) {
  id: ID!
  tripID: ID! # Assuming this links to a Trip model
  # trip: Trip @connection(fields: ["tripID"]) # Optional connection
  checkInTime: AWSDateTime!
  expectedCheckOutTime: AWSDateTime!
  actualCheckOutTime: AWSDateTime
  notes: String
  # userID: ID! @index(name: "byUser", queryField: "checkInLogsByUserID") # Or rely on owner
  selectedEmergencyContactID: ID # New field to store the ID of the EmergencyContact
  # selectedEmergencyContact: EmergencyContact @connection(fields: ["selectedEmergencyContactID"]) # Optional: if you want a direct GraphQL link
}

Example Schema Change (add userID if not present and relying on it for queries):

type EmergencyContact @model @auth(rules: [
  {allow: owner, operations: [create, read, update, delete]}
]) {
  id: ID!
  name: String!
  phone: String!
  relationship: String
  userID: ID @index(name: "byUser", queryField: "emergencyContactsByUserID") # Add this if you want to query by userID
  # If you rely solely on 'owner' field, ensure your repository logic handles that.
}

Phase 2: Update Emergency Contact Feature (Data Access)

  1. Modify EmergencyContactRepository:
    • File: lib/features/emergency_contact/data/emergency_contact_repository.dart
    • Purpose: Ensure there's a method to fetch all emergency contacts for the current user.
  2. Update/Create Emergency Contact Provider:
    • File: lib/features/emergency_contact/providers/emergency_contact_providers.dart
    • Purpose: Provide the list of user's emergency contacts to the UI.

Example (using Riverpod):

// lib/features/emergency_contact/providers/emergency_contact_providers.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:YOUR_APP_NAME/models/ModelProvider.dart'; // Update YOUR_APP_NAME
import 'package:YOUR_APP_NAME/features/emergency_contact/data/emergency_contact_repository.dart';
import 'package:YOUR_APP_NAME/common/services/auth_service.dart'; // Assuming you have this

// Provider for the repository itself
final emergencyContactRepositoryProvider = Provider<EmergencyContactRepository>((ref) {
  return EmergencyContactRepository(); // Instantiate with dependencies if any
});

// Provider to fetch the list of emergency contacts for the current user
final userEmergencyContactsProvider = FutureProvider<List<EmergencyContact>>((ref) async {
  final authService = ref.watch(authServiceProvider); // Assuming authServiceProvider exists
  final cognitoUser = await authService.getCurrentUser(); // Or however you get user details

  if (cognitoUser == null) { // Or check if user is signed in
     safePrint('User not authenticated, cannot fetch emergency contacts.');
    return []; // Or throw an error appropriate for your app's UX
  }
  // Use cognitoUser.userId or cognitoUser.username depending on what you use as 'owner' or 'userID'
  final String userId = cognitoUser.userId; 

  final repository = ref.watch(emergencyContactRepositoryProvider);
  return repository.getEmergencyContactsForUser(userId);
});

Example Method:

// lib/features/emergency_contact/data/emergency_contact_repository.dart
import 'package:amplify_flutter/amplify_flutter.dart';
import 'package:YOUR_APP_NAME/models/ModelProvider.dart'; // Update YOUR_APP_NAME

class EmergencyContactRepository {
  // Assuming you inject DataStore or use Amplify.DataStore directly
  // Example: final _dataStore = Amplify.DataStore;

  Future<List<EmergencyContact>> getEmergencyContactsForUser(String userId) async {
    try {
      // If you added a userID field and indexed it:
      final contacts = await Amplify.DataStore.query(
        EmergencyContact.classType,
        where: EmergencyContact.USERID.eq(userId), // Or EmergencyContact.OWNER.eq(userId) if using owner field
      );
      // If contacts are not directly queryable by userID but are linked via another model (e.g. Profile),
      // you'd first fetch that model, then access its contacts.
      // For simplicity, this example assumes direct queryability.
      return contacts;
    } catch (e) {
      safePrint('Error fetching emergency contacts for user $userId: $e');
      throw AmplifyException('Failed to fetch emergency contacts', underlyingException: e.toString());
    }
  }

  // ... other existing methods (save, delete, etc.)
}

Phase 3: Implement UI for Selecting Emergency Contact

  1. Identify/Create Check-in UI Screen/Widget:
    • This will likely be a new screen or a bottom sheet presented when the user initiates a check-in. For example, lib/features/checkin/ui/check_in_screen.dart or lib/features/checkin/ui/widgets/check_in_form.dart.
    • This UI will take other check-in details (like expected checkout time) and now also the emergency contact.
  2. Add Emergency Contact Selector to Check-in Form:
    • File: The check-in UI file identified above.
    • Purpose: Allow the user to pick from their list of emergency contacts.

Example Widget (using DropdownButtonFormField and Riverpod):

// In your CheckInScreen or CheckInForm widget (StatefulWidget or ConsumerStatefulWidget for Riverpod)
// ... imports ...
// import 'package:flutter_riverpod/flutter_riverpod.dart';
// import 'package:YOUR_APP_NAME/models/ModelProvider.dart';
// import 'package:YOUR_APP_NAME/features/emergency_contact/providers/emergency_contact_providers.dart';
// import 'package:YOUR_APP_NAME/features/emergency_contact/ui/screens/add_edit_emergency_contact_screen.dart'; // For navigation

class CheckInForm extends ConsumerStatefulWidget { // Or StatefulWidget
  final String tripId; // Assuming tripId is passed to this form
  const CheckInForm({super.key, required this.tripId});

  @override
  ConsumerState<CheckInForm> createState() => _CheckInFormState();
}

class _CheckInFormState extends ConsumerState<CheckInForm> {
  final _formKey = GlobalKey<FormState>();
  DateTime? _expectedCheckOutTime;
  EmergencyContact? _selectedEmergencyContact;
  String _notes = '';

  // Method to show date picker, etc. for _expectedCheckOutTime

  @override
  Widget build(BuildContext context) {
    final emergencyContactsAsyncValue = ref.watch(userEmergencyContactsProvider);

    return Form(
      key: _formKey,
      child: ListView( // Use ListView for scrollability if form is long
        padding: EdgeInsets.all(16.0),
        children: <Widget>[
          // ... other form fields (expected checkout time, notes) ...

          Text('Select Emergency Contact:', style: Theme.of(context).textTheme.titleMedium),
          SizedBox(height: 8),

          emergencyContactsAsyncValue.when(
            data: (contacts) {
              if (contacts.isEmpty) {
                return Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text('No emergency contacts found.'),
                    SizedBox(height: 8),
                    ElevatedButton(
                      onPressed: () {
                        // Navigate to add emergency contact screen
                        // Example: Navigator.push(context, MaterialPageRoute(builder: (_) => AddEditEmergencyContactScreen()));
                        // Or using go_router: context.push(AppRoute.addEmergencyContact.path);
                        // Make sure to refresh/refetch contacts after adding one.
                         ScaffoldMessenger.of(context).showSnackBar(
                          SnackBar(content: Text('Navigation to add contact screen - TBD')),
                        );
                      },
                      child: Text('Add Emergency Contact'),
                    ),
                  ],
                );
              }
              return DropdownButtonFormField<EmergencyContact>(
                decoration: InputDecoration(
                  labelText: 'Emergency Contact',
                  border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
                  filled: true,
                ),
                value: _selectedEmergencyContact,
                hint: Text('Choose an emergency contact'),
                isExpanded: true,
                items: contacts.map((EmergencyContact contact) {
                  return DropdownMenuItem<EmergencyContact>(
                    value: contact,
                    child: Text('${contact.name} (${contact.phone})'),
                  );
                }).toList(),
                onChanged: (EmergencyContact? newValue) {
                  setState(() {
                    _selectedEmergencyContact = newValue;
                  });
                },
                validator: (value) {
                  if (value == null) {
                    return 'Please select an emergency contact.';
                  }
                  return null;
                },
              );
            },
            loading: () => Center(child: CircularProgressIndicator()),
            error: (err, stack) {
               safePrint('Error loading emergency contacts in dropdown: $err');
               return Text('Error: Could not load contacts. $err');
            }
          ),
          SizedBox(height: 20),
          ElevatedButton(
            onPressed: _submitCheckIn,
            child: Text('Start Check-in'),
            style: ElevatedButton.styleFrom(
              padding: EdgeInsets.symmetric(vertical: 16),
              textStyle: TextStyle(fontSize: 16),
            ),
          ),
        ],
      ),
    );
  }

  void _submitCheckIn() {
    if (_formKey.currentState!.validate()) {
      _formKey.currentState!.save();
      if (_expectedCheckOutTime == null) {
         ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('Please select an expected checkout time.')),
        );
        return;
      }
      if (_selectedEmergencyContact == null) {
         ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('Please select an emergency contact.')),
        );
        return;
      }

      // Call your CheckInController or CheckInRepository here
      // Example:
      // final checkInController = ref.read(checkInControllerProvider.notifier);
      // checkInController.performCheckIn(
      //   tripId: widget.tripId,
      //   expectedCheckOutTime: _expectedCheckOutTime!,
      //   selectedEmergencyContactId: _selectedEmergencyContact!.id,
      //   notes: _notes,
      // );
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Check-in submitted with contact: ${_selectedEmergencyContact!.name}')),
      );
    }
  }
}

Phase 4: Update Check-in Controller and Repository

  1. Modify CheckInController (if you have one):
    • File: lib/features/checkin/controller/check_in_controller.dart (create if it doesn't exist for check-in specific logic, or integrate into TripController).
    • Purpose: Handle the logic of creating a CheckInLog object with the selected emergency contact ID.
  2. Create/Modify CheckInRepository:
    • File: lib/features/checkin/data/check_in_repository.dart
    • Purpose: Save the CheckInLog to Amplify DataStore.

Example:

// lib/features/checkin/data/check_in_repository.dart
import 'package:amplify_flutter/amplify_flutter.dart';
import 'package:YOUR_APP_NAME/models/ModelProvider.dart';

class CheckInRepository {
  Future<CheckInLog> saveCheckIn(CheckInLog checkInLog) async {
    try {
      await Amplify.DataStore.save(checkInLog);
      safePrint('CheckInLog saved successfully: ${checkInLog.id}');
      return checkInLog;
    } catch (e) {
      safePrint('Error saving CheckInLog: $e');
      throw AmplifyException('Failed to save check-in log', underlyingException: e.toString());
    }
  }

  // You might also need methods to fetch active check-ins, update them, etc.
}

Example (Riverpod StateNotifier):

// lib/features/checkin/controller/check_in_controller.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:YOUR_APP_NAME/models/ModelProvider.dart';
import 'package:YOUR_APP_NAME/features/checkin/data/check_in_repository.dart'; // Create this
import 'package:YOUR_APP_NAME/common/services/auth_service.dart';
import 'package:amplify_flutter/amplify_flutter.dart';


// State for the controller
abstract class CheckInState {}
class CheckInInitial extends CheckInState {}
class CheckInLoading extends CheckInState {}
class CheckInSuccess extends CheckInState {
  final CheckInLog checkInLog;
  CheckInSuccess(this.checkInLog);
}
class CheckInFailure extends CheckInState {
  final String error;
  CheckInFailure(this.error);
}

// Provider for the CheckInRepository
final checkInRepositoryProvider = Provider<CheckInRepository>((ref) {
  return CheckInRepository(); // Instantiate with Amplify.DataStore or other dependencies
});

// Provider for the CheckInController
final checkInControllerProvider = StateNotifierProvider<CheckInController, CheckInState>((ref) {
  return CheckInController(ref);
});

class CheckInController extends StateNotifier<CheckInState> {
  final Ref _ref;
  CheckInController(this._ref) : super(CheckInInitial());

  Future<void> performCheckIn({
    required String tripId,
    required DateTime expectedCheckOutTime,
    required String selectedEmergencyContactId,
    String? notes,
  }) async {
    state = CheckInLoading();
    try {
      final authService = _ref.read(authServiceProvider);
      final currentUser = await authService.getCurrentUser();
      if (currentUser == null) {
        throw Exception('User not authenticated.');
      }
      // final String userId = currentUser.userId; // Or however you get the owner/userID

      final newCheckInLog = CheckInLog(
        tripID: tripId,
        checkInTime: TemporalDateTime.now(), // Use TemporalDateTime for Amplify
        expectedCheckOutTime: TemporalDateTime(expectedCheckOutTime),
        selectedEmergencyContactID: selectedEmergencyContactId,
        notes: notes,
        // userID: userId, // If your CheckInLog model has userID and it's not auto-set by owner policy
      );

      final checkInRepository = _ref.read(checkInRepositoryProvider);
      final savedLog = await checkInRepository.saveCheckIn(newCheckInLog);
      state = CheckInSuccess(savedLog);
    } catch (e) {
      safePrint('Error performing check-in: $e');
      state = CheckInFailure(e.toString());
    }
  }
}

Phase 5: Integration and Testing

  1. Trigger Check-in UI: From your trip details page (lib/features/trip/ui/trip_page/trip_page.dart or similar), add a button "Start Check-in" that navigates to your new CheckInScreen or shows the CheckInForm in a dialog/bottom sheet.
  2. Pass tripId: Ensure the tripId is passed to the check-in form/screen.
  3. Handle State: Use the CheckInController's state to show loading indicators, success messages, or error messages in the UI after attempting to check in.
  4. Test Thoroughly:
    • Check-in with an emergency contact selected.
    • Verify data is saved correctly in Amplify DataStore (you can check via Amplify Console).
    • Test the flow if no emergency contacts are available.
    • Test error handling (e.g., network issues during save).
    • Test UI responsiveness and form validation.

This detailed plan should guide you through implementing the emergency contact selection feature. Remember to replace YOUR_APP_NAME with your actual application name in import paths.