import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'domain/models/server_config.dart'; import 'domain/models/sensor.dart'; import 'domain/models/floor.dart'; import 'domain/models/tag.dart'; import 'domain/models/particle.dart'; import 'domain/models/floor_plan_mode.dart'; import 'data/sources/local/credential_store.dart'; import 'data/sources/localiser/onboarding_client.dart'; import 'data/sources/localiser/session_client.dart'; import 'data/sources/localiser/floor_client.dart'; import 'data/sources/localiser/sensor_client.dart'; import 'data/sources/localiser/tag_client.dart'; import 'data/sources/localiser/realtime_data_client.dart'; import 'data/repositories/onboarding_repository.dart'; import 'data/repositories/sensor_repository.dart'; import 'data/repositories/tag_repository.dart'; import 'data/repositories/floor_plan_repository.dart'; import 'data/repositories/phoenix_onboarding_repository.dart'; import 'data/repositories/phoenix_sensor_repository.dart'; import 'data/repositories/phoenix_tag_repository.dart'; import 'data/repositories/phoenix_floor_plan_repository.dart'; // --------------------------------------------------------------------------- // Connection / auth state — set imperatively after login // --------------------------------------------------------------------------- /// The server the user has selected. Null until a server is chosen. final serverConfigProvider = StateProvider((ref) => null); /// JWT returned by /api/session or /api/setup. Null until authenticated. final authTokenProvider = StateProvider((ref) => null); /// Live WebSocket connection. Null until [RealtimeDataClient.connect] succeeds. final realtimeDataClientProvider = StateProvider((ref) => null); // --------------------------------------------------------------------------- // Convenience // --------------------------------------------------------------------------- final credentialStoreProvider = Provider((ref) { return CredentialStore(); }); /// MQTT broker settings stored locally on the device. /// Null if not yet configured. Set via [CredentialStore.saveMqttBroker]. final mqttBrokerProvider = FutureProvider<({String host, int port})?>( (ref) => ref.watch(credentialStoreProvider).loadMqttBroker(), ); // --------------------------------------------------------------------------- // Feature clients — throw if required state is missing // --------------------------------------------------------------------------- ServerConfig _requireConfig(Ref ref) { final config = ref.watch(serverConfigProvider); if (config == null) throw StateError('no server selected'); return config; } String _requireToken(Ref ref) { final token = ref.watch(authTokenProvider); if (token == null) throw StateError('not authenticated'); return token; } RealtimeDataClient _requireRealtime(Ref ref) { final rt = ref.watch(realtimeDataClientProvider); if (rt == null) throw StateError('realtime not connected'); return rt; } final onboardingClientProvider = Provider((ref) { return OnboardingClient(config: _requireConfig(ref)); }); final sessionClientProvider = Provider((ref) { return SessionClient(config: _requireConfig(ref)); }); final floorClientProvider = Provider((ref) { return FloorClient(config: _requireConfig(ref), token: _requireToken(ref)); }); final sensorClientProvider = Provider((ref) { return SensorClient(config: _requireConfig(ref), token: _requireToken(ref)); }); final tagClientProvider = Provider((ref) { return TagClient(config: _requireConfig(ref), token: _requireToken(ref)); }); // --------------------------------------------------------------------------- // Repositories // --------------------------------------------------------------------------- final onboardingRepositoryProvider = Provider((ref) { return PhoenixOnboardingRepository( client: ref.watch(onboardingClientProvider)); }); final sensorRepositoryProvider = Provider((ref) { return PhoenixSensorRepository(client: ref.watch(sensorClientProvider)); }); final tagRepositoryProvider = Provider((ref) { return PhoenixTagRepository( tagClient: ref.watch(tagClientProvider), realtime: _requireRealtime(ref), ); }); final floorRepositoryProvider = Provider((ref) { return PhoenixFloorPlanRepository(client: ref.watch(floorClientProvider)); }); // --------------------------------------------------------------------------- // Floor plan data // --------------------------------------------------------------------------- final floorProvider = FutureProvider.autoDispose((ref) => ref.watch(floorRepositoryProvider).getFirstFloor()); final roomsProvider = FutureProvider.autoDispose>((ref) async { final floor = await ref.watch(floorProvider.future); if (floor == null) return []; return ref.watch(floorRepositoryProvider).getRooms(floor.id); }); // --------------------------------------------------------------------------- // Sensor data // --------------------------------------------------------------------------- final sensorsProvider = FutureProvider.autoDispose>((ref) { return ref.watch(sensorRepositoryProvider).getSensors(); }); final sensorProvider = FutureProvider.autoDispose.family((ref, id) { return ref.watch(sensorRepositoryProvider).getSensor(id); }); // --------------------------------------------------------------------------- // Cross-tab UI state // --------------------------------------------------------------------------- final selectedSensorIdProvider = StateProvider((ref) => null); final floorPlanModeProvider = StateProvider((ref) => FloorPlanMode.view); // --------------------------------------------------------------------------- // Live data streams // --------------------------------------------------------------------------- final tagPositionsProvider = StreamProvider>((ref) { final repo = ref.watch(tagRepositoryProvider); return repo.watchPositions(); }); final particleCloudProvider = StreamProvider>((ref) { final repo = ref.watch(tagRepositoryProvider); return repo.watchParticleCloud(); });