
# milsat_modules_flutter

[![pub package](https://img.shields.io/pub/v/milsat_modules_flutter.svg)](https://pub.dev/packages/milsat_modules_flutter)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

> A comprehensive Flutter package for geospatial data collection, user management, dynamic form rendering, data export/sync, and hierarchical location services. Built with feature-first Clean Architecture, each module is independent and can be used standalone or integrated together.

---

## 🚀 Features

- 🧩 **Common Module**: Shared entities, error/result models, network utilities, and configuration
- 👤 **User Module**: Authentication, user management, project compendium, white-label config, address verification, and wallet services
- 📝 **Form Module**: Dynamic form rendering, validation, conditional logic, script evaluation, and reusable UI widgets
- 📊 **Data Module**: Data management, export, sync, survey entities, and configuration import
- 📍 **Location Module**: Hierarchical location services, CSV-based data management, polling units, wards, LGAs, and more
- 🗺️ **Map Module**: Advanced Mapbox integration, geometry drawing, work area visualization, undo/redo, and dynamic icon generation

---

## 📦 Modules Overview

| Module   | Description                                                          |
| -------- | -------------------------------------------------------------------- |
| Common   | Core entities, error/result models, config, network utils            |
| User     | Auth, user management, compendium, white-label, address verification |
| Form     | Dynamic forms, validation, conditional logic, UI widgets             |
| Data     | Data management, export/sync, survey entities, config import         |
| Location | Hierarchical location, CSV data, polling units, LGAs, wards          |
| Map      | Mapbox integration, geometry, work areas, icons, undo/redo           |

---

## ⚡ Quick Start

Add `milsat_modules_flutter` to your `pubspec.yaml`:

```yaml
dependencies:
  milsat_modules_flutter: ^3.2.3
```

Import the package:

```dart
import 'package:milsat_modules_flutter/milsat_modules_flutter.dart';
```

---

## 🔧 Initialization

Initialize the modules in your app's dependency injection setup:

```dart
import 'package:milsat_modules_flutter/milsat_modules_flutter.dart';

Future<void> init() async {
  // Initialize milsat_modules_flutter
  await ModulesConfig.initialize(
    ppqBaseUrl: 'https://ppq-api.example.com',
    baseUrl: 'https://api.example.com',
    isDebugMode: kDebugMode,
    usePpqAuth: true, // Set to false for Enum auth
    mapboxAccessToken: 'your_mapbox_token',
  );

  // Initialize Mapbox with access token (if using Map module)
  if (MapConfig.isInitialized) {
    final mapboxToken = MapConfig.mapboxAccessToken;
    if (mapboxToken.isNotEmpty) {
      MapboxOptions.setAccessToken(mapboxToken);
    }
  }
}
```

---

## 📖 Module Usage Examples

### 🧩 Common Module

The Common module provides shared utilities, entities, and configuration across all modules.

```dart
import 'package:milsat_modules_flutter/milsat_modules_flutter.dart';

// Access API client for custom requests
final apiClient = ModulesConfig.getApiClient();

// Use Result pattern for error handling
Future<Result<UserEntity>> fetchUser() async {
  try {
    final user = await userService.getCurrentUser();
    return Result.success(user);
  } catch (e) {
    return Result.failure(AuthError(message: e.toString()));
  }
}

// Check result status
final result = await fetchUser();
if (result.isSuccess) {
  print('User: ${result.data}');
} else {
  print('Error: ${result.error?.message}');
}
```

---

### 👤 User Module

The User module handles authentication, user profiles, address verification, and wallet services.

#### Authentication

```dart
import 'package:milsat_modules_flutter/milsat_modules_flutter.dart';

// Access user service from config
final userService = UserConfig.userService;

// Login with email
class AuthRemoteDataSourceImpl implements AuthRemoteDataSource {
  final UserService _userService;

  AuthRemoteDataSourceImpl(this._userService);

  @override
  Future<CurrentUserEntity> loginWithEmail(String email, String password) async {
    final result = await _userService.loginWithEmail(email, password);
    if (result.isFailure) throw result.error!;
    return result.data!;
  }

  @override
  Future<void> logout() async {
    final result = await _userService.logout();
    if (result.isFailure) throw result.error!;
  }
}
```

#### User Profile

```dart
// Get current user
final currentUser = userService.currentUser;
print('User: ${currentUser.profile.firstName} ${currentUser.profile.lastName}');
print('Client ID: ${currentUser.client.id}');
print('Access Token: ${currentUser.accessToken}');

// Refresh user profile
final result = await userService.refreshUserProfile();
if (result.isSuccess) {
  print('Profile refreshed: ${result.data?.profile.email}');
}
```

#### Address Verification

```dart
import 'package:milsat_modules_flutter/milsat_modules_flutter.dart';

// Access address verification service
final addressVerificationService = UserConfig.addressVerificationService;

class AddressVerificationRemoteDataSourceImpl {
  final AddressVerificationService _service;

  AddressVerificationRemoteDataSourceImpl(this._service);

  // Get address verification projects
  Future<List<ProjectCompendiumEntity>> getProjects() async {
    final result = await _service.getProjectCompendiumByClientId();
    if (result.isFailure) throw result.error!;
    return result.data!;
  }

  // Get address verification requests (assigned and unassigned)
  Future<({
    List<VerificationAddressEntity> assigned,
    List<VerificationAddressEntity> unassigned,
    List<VerificationAddressEntity> all,
  })> getAddressRequests() async {
    final result = await _service.getAddressVerificationRequests(
      pageNumber: 1,
      pageSize: 10000,
      filterByUserStateAndLga: false,
    );
    if (result.isFailure) throw result.error!;
    return result.data!;
  }

  // Update address verification with automatic image sync
  Future<void> updateAddress(List<VerificationAddressEntity> addresses) async {
    // Images are automatically synced before API submission
    final result = await _service.updateAddressVerificationRequest(
      request: addresses,
    );
    if (result.isFailure) throw result.error!;
  }

  // Assign address to current user
  Future<void> assignAddress(VerificationAddressEntity address) async {
    final result = await _service.assignAddress(address: address);
    if (result.isFailure) throw result.error!;
  }

  // Initialize form with config file
  Future<({
    SurveyFormEntity surveyForm,
    Map<String, dynamic> uiFieldValues,
    List<FormFieldModel> fields,
  })> initializeForm(VerificationAddressEntity address, String configUrl) async {
    final result = await _service.fetchAndParseAddressVerificationConfig(
      address: address,
      configFileUrl: configUrl,
    );
    if (result.isFailure) throw result.error!;
    return result.data!;
  }
}
```

---

### 📊 Data Module

The Data module handles survey data, entries, export, and synchronization.

#### Data Service

```dart
import 'package:milsat_modules_flutter/milsat_modules_flutter.dart';

// Access data service from config
final dataService = DataConfig.dataService;

class ImportRemoteDataSourceImpl {
  final DataService _dataService;
  final UserService _userService;

  ImportRemoteDataSourceImpl({
    required DataService dataService,
    required UserService userService,
  }) : _dataService = dataService,
       _userService = userService;

  // Import config from cloud
  Future<SurveyEntity> importConfigFromCloud(ProjectCompendiumEntity project) async {
    final result = await _dataService.importConfig(
      url: project.configFileUrl!,
      accessToken: _userService.currentUser.accessToken,
      skipWorkFrameCheck: false,
    );
    if (result.isFailure) throw result.error!;
    return result.data!;
  }

  // Get all imported surveys
  Future<List<SurveyEntity>> getImportedProjects() async {
    final result = await _dataService.getAllSurveys();
    if (result.isFailure) throw result.error!;
    return result.data!;
  }

  // Get current survey
  SurveyEntity? get currentSurvey => _dataService.currentSurvey;

  // Set current survey
  Future<void> setCurrentSurvey(SurveyEntity survey) async {
    final result = await _dataService.setCurrentSurvey(survey);
    if (result.isFailure) throw result.error!;
  }

  // Delete imported project
  Future<void> deleteProject(SurveyEntity survey) async {
    final result = await _dataService.deleteSurvey(survey: survey);
    if (result.isFailure) throw result.error!;
  }
}
```

#### Entry Management

```dart
class EntriesRemoteDataSourceImpl {
  final DataService _dataService;
  final UserService _userService;

  EntriesRemoteDataSourceImpl({
    required DataService dataService,
    required UserService userService,
  }) : _dataService = dataService,
       _userService = userService;

  // Get all entries for a form
  Future<List<EntryEntity>> getAllEntries(String formName) async {
    final result = await _dataService.getAllEntries(formName: formName);
    if (result.isFailure) throw result.error!;
    return result.data!;
  }

  // Save new entry
  Future<int> saveEntry(String formName, EntryEntity entry) async {
    final result = await _dataService.insertEntry(
      formName: formName,
      entry: entry,
    );
    if (result.isFailure) throw result.error!;
    return result.data!;
  }

  // Update existing entry
  Future<void> updateEntry(String formName, EntryEntity entry) async {
    final result = await _dataService.updateEntry(
      formName: formName,
      entry: entry,
    );
    if (result.isFailure) throw result.error!;
  }

  // Delete entry
  Future<void> deleteEntry(String formName, EntryEntity entry) async {
    final result = await _dataService.deleteEntry(
      formName: formName,
      entry: entry,
    );
    if (result.isFailure) throw result.error!;
  }

  // Get entries count
  Future<int> getEntriesCount(String formName) async {
    final result = await _dataService.getEntriesCount(formName: formName);
    if (result.isFailure) throw result.error!;
    return result.data!;
  }
}
```

#### Export & Sync

```dart
class ExportRemoteDataSourceImpl {
  final DataService _dataService;
  final UserService _userService;

  ExportRemoteDataSourceImpl({
    required DataService dataService,
    required UserService userService,
  }) : _dataService = dataService,
       _userService = userService;

  // Export entries to cloud
  Future<int> exportEntries(SurveyEntity survey) async {
    final result = await _dataService.exportEntries(
      survey: survey,
      accessToken: _userService.currentUser.accessToken,
    );
    if (result.isFailure) throw result.error!;
    return result.data!;
  }

  // Export to CSV
  Future<String> exportToCSV(String formName) async {
    final result = await _dataService.loadEntriesToCsv(formName: formName);
    if (result.isFailure) throw result.error!;
    return result.data!;
  }

  // Sync images before export
  Future<int> syncImages(SurveyEntity survey) async {
    final result = await _dataService.syncImagesForSurvey(
      survey: survey,
      accessToken: _userService.currentUser.accessToken,
    );
    if (result.isFailure) throw result.error!;
    return result.data!;
  }
}
```

---

### 📝 Form Module

The Form module provides dynamic form rendering with conditional logic, validation, and various field types.

#### FormBuilder Widget

```dart
import 'package:milsat_modules_flutter/milsat_modules_flutter.dart';

class AddEditEntryScreen extends StatefulWidget {
  final SurveyFormEntity surveyForm;
  final EntryEntity? existingEntry;

  const AddEditEntryScreen({
    required this.surveyForm,
    this.existingEntry,
  });

  @override
  State<AddEditEntryScreen> createState() => _AddEditEntryScreenState();
}

class _AddEditEntryScreenState extends State<AddEditEntryScreen> {
  final _formKey = GlobalKey<FormState>();
  final _uiFieldValues = <String, dynamic>{};
  final _uiFieldMap = <String, UIFieldState>{};

  @override
  Widget build(BuildContext context) {
    return FormBuilder(
      surveyForm: widget.surveyForm,
      uiFieldValues: _uiFieldValues,
      uiFieldMap: _uiFieldMap,
      formKey: _formKey,

      // Callbacks for field value changes
      onUIFieldValueChange: (columnName, value) {
        _uiFieldValues[columnName] = value;
      },
      onUIFieldsChange: (field) {
        _uiFieldMap[field.field.columnName] = field;
      },

      // Navigation callbacks
      onNavigationStateChanged: (state) {
        // Handle page navigation state
      },
      onFormEnded: (isEnded) {
        // Handle when form reaches last page
      },

      // Data service for location fields
      dataService: DataConfig.dataService,

      // Optional: Custom field configurations
      defaultTextFieldConfig: TextFieldConfig(
        inputDecoration: InputDecoration(
          filled: true,
          fillColor: Colors.grey[100],
          border: OutlineInputBorder(
            borderRadius: BorderRadius.circular(8),
          ),
        ),
      ),
      defaultDropdownConfig: DropdownConfig(
        inputDecoration: InputDecoration(
          filled: true,
          fillColor: Colors.grey[100],
        ),
      ),
      defaultDatePickerConfig: DatePickerConfig(
        inputDecoration: InputDecoration(
          suffixIcon: Icon(Icons.calendar_today),
        ),
      ),
      defaultPictureConfig: PictureConfig(
        showCameraButton: true,
        showGalleryButton: true,
      ),
      defaultSignatureConfig: SignatureConfig(
        backgroundColor: Colors.white,
        penColor: Colors.black,
      ),

      // Layout options
      applyPadding: true,
      fillMaxSize: true,
    );
  }

  void _saveEntry() {
    if (_formKey.currentState?.validate() ?? false) {
      // Build entry from form values
      final entry = EntryModel.fromUIFieldMap(
        uiFieldMap: _uiFieldMap,
        uiFieldValues: _uiFieldValues,
        // ... other parameters
      );

      // Save entry using data service
      DataConfig.dataService.insertEntry(
        formName: widget.surveyForm.formName,
        entry: entry,
      );
    }
  }
}
```

#### Supported Field Types

The FormBuilder supports various field types:

- **Text Fields**: Single line, multi-line, numeric, email, phone
- **Selection Fields**: Dropdown, radio, checkbox, multi-select
- **Date/Time Fields**: Date picker, time picker, datetime picker
- **Media Fields**: Picture (camera/gallery), signature, file upload
- **Location Fields**: GPS coordinates, location picker with cascade (State → LGA → Ward)
- **Calculated Fields**: Auto-calculated based on other field values
- **Conditional Fields**: Show/hide based on conditions

---

### 🗺️ Map Module

The Map module provides Mapbox integration for geometry collection, entry visualization, and work area management.

#### Map Service

```dart
import 'package:milsat_modules_flutter/milsat_modules_flutter.dart';
import 'package:mapbox_maps_flutter/mapbox_maps_flutter.dart';

class MapRemoteDataSourceImpl {
  final DataService _dataService;
  final MapService _mapService;

  MapRemoteDataSourceImpl({
    required DataService dataService,
    required MapService mapService,
  }) : _dataService = dataService,
       _mapService = mapService;

  // Initialize map for data collection
  Future<MapService> initializeMapService({
    required MapboxMap mapboxMap,
    required SurveyEntity survey,
    required Function(DataFromMapModel)? onDataFromMap,
    required void Function(GeometryMetadata) onGeometryClicked,
    required void Function(String errorMessage) onMapClickError,
  }) async {
    final result = await _mapService.initialize(
      mapboxMap: mapboxMap,
      survey: survey,
      onDataFromMap: onDataFromMap,
      onGeometryClicked: onGeometryClicked,
      onError: (error) => onMapClickError(error.message),
    );
    if (result.isFailure) throw result.error!;
    return _mapService;
  }

  // Initialize map for address verification
  Future<MapService> initializeForAddressVerification({
    required MapboxMap mapboxMap,
    required VerificationAddressEntity address,
    required Function(DataFromMapModel)? onDataFromMap,
    required void Function(String errorMessage) onMapClickError,
  }) async {
    final result = await _mapService.initializeForAddressVerification(
      mapboxMap: mapboxMap,
      verificationAddress: address,
      onDataFromMap: onDataFromMap,
      onError: (error) => onMapClickError(error.message),
    );
    if (result.isFailure) throw result.error!;
    return _mapService;
  }

  // Show entries on map
  Future<void> showEntries(String formName) async {
    final entries = await _dataService.getAllEntries(formName: formName);
    if (entries.isSuccess) {
      await _mapService.showEntries(
        entries: entries.data!,
        formName: formName,
      );
    }
  }

  // Get current location
  Future<Position> getCurrentLocation({bool highAccuracy = true}) async {
    final result = await _mapService.getCurrentLocation(
      highAccuracy: highAccuracy,
    );
    if (result.isFailure) throw result.error!;
    return result.data!;
  }

  // Get geometry GeoJSON
  String? getFeatureGeoJson() {
    return _mapService.getFeatureGeoJsonFromGeometry();
  }

  // Get coordinates from drawn geometry
  Position? getCoordinates() {
    return _mapService.getLatLngFromGeometry();
  }
}
```

#### Map Widget Integration

```dart
class MapScreen extends StatefulWidget {
  @override
  State<MapScreen> createState() => _MapScreenState();
}

class _MapScreenState extends State<MapScreen> {
  MapService? _mapService;

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<MapBloc, MapState>(
      builder: (context, state) {
        return MapWidget(
          onMapCreated: (mapboxMap) {
            // Initialize map service when map is ready
            context.read<MapBloc>().add(InitializeMapEvent(
              mapboxMap: mapboxMap,
              survey: currentSurvey,
              onDataFromMap: (data) {
                // Handle data from map (coordinates, frame properties)
                print('Lat: ${data.userLat}, Long: ${data.userLong}');
                print('Frame: ${data.frameProperties}');
              },
              onGeometryClicked: (metadata) {
                // Handle geometry click
                print('Clicked entry: ${metadata.entryId}');
              },
            ));
          },
        );
      },
    );
  }
}
```

---

## 🔑 Configuration Classes

### ModulesConfig

```dart
// Initialize all modules
await ModulesConfig.initialize(
  ppqBaseUrl: 'https://ppq-api.example.com',
  baseUrl: 'https://api.example.com',
  isDebugMode: true,
  usePpqAuth: true,
  mapboxAccessToken: 'pk.xxx',
);

// Get API client
final apiClient = ModulesConfig.getApiClient();
```

### UserConfig

```dart
// Access user service
final userService = UserConfig.userService;

// Access address verification service
final addressVerificationService = UserConfig.addressVerificationService;
```

### DataConfig

```dart
// Access data service
final dataService = DataConfig.dataService;
```

### MapConfig

```dart
// Check if map is initialized
if (MapConfig.isInitialized) {
  // Access map service
  final mapService = MapConfig.mapService;

  // Get Mapbox token
  final token = MapConfig.mapboxAccessToken;
}
```

---

## 📖 Documentation & Resources

- [API Reference](https://github.com/HaywhyD/milsat_modules_flutter#readme)
- [Issue Tracker](https://github.com/HaywhyD/milsat_modules_flutter/issues)
- [Contributing Guide](https://github.com/HaywhyD/milsat_modules_flutter/blob/main/CONTRIBUTING.md)
- [Changelog](CHANGELOG.md)

---

## 🆕 Recent Changes

See [CHANGELOG.md](CHANGELOG.md) for the latest updates and release notes.

---

## 📄 License

This package is licensed under the MIT License. See [LICENSE](LICENSE) for details.
