ionic-planb-logistic-app-fl.../lib/providers/providers.dart
Jean-Philippe Brule 96c9e59cf0 Implement system theme support and dark mode infrastructure
Add comprehensive theme management system with iOS system integration:

- System theme detection: App follows iOS dark/light mode preferences via ThemeMode.system
- Theme provider: Centralized theme state management with Riverpod (defaults to dark mode)
- Settings toggle: Segmented button UI for Light/Dark/Auto theme selection
- iOS system UI: Status bar and navigation bar adapt to current theme brightness

Dark mode map styling (Android-ready):
- DarkModeMapComponent: Reactive theme change detection with didChangeDependencies
- Map style application: Custom dark JSON style for navigation maps
- Theme-aware styling: Automatically applies/resets map style on theme changes
- Note: Map styling currently Android-only due to iOS SDK limitations

Updates:
- main.dart: System UI overlay styling for iOS, theme provider integration
- settings_page.dart: SegmentedButton theme toggle with icons
- providers.dart: themeModeProvider for app-wide theme state
- dark_mode_map.dart: Theme reactivity and style application logic
- navigation_page.dart: Theme detection infrastructure (prepared for future use)

Design philosophy:
- Follow system preferences by default for native iOS experience
- Manual override available for user preference
- Clean separation between Flutter UI theming and native map styling

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-16 00:52:14 -05:00

157 lines
4.5 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../api/types.dart';
import '../api/client.dart';
import '../api/openapi_config.dart';
import '../services/auth_service.dart';
import '../models/user_profile.dart';
import '../models/delivery_route.dart';
import '../models/delivery.dart';
import '../models/delivery_order.dart';
import '../models/delivery_address.dart';
import '../models/delivery_contact.dart';
final authServiceProvider = Provider<AuthService>((ref) {
return AuthService();
});
final apiClientProvider = Provider<CqrsApiClient>((ref) {
return CqrsApiClient(config: ApiClientConfig.production);
});
final isAuthenticatedProvider = FutureProvider<bool>((ref) async {
final authService = ref.watch(authServiceProvider);
return await authService.isAuthenticated();
});
final userProfileProvider = FutureProvider<UserProfile?>((ref) async {
final authService = ref.watch(authServiceProvider);
final token = await authService.getToken();
if (token == null) return null;
return authService.decodeToken(token);
});
final authTokenProvider = FutureProvider<String?>((ref) async {
final authService = ref.watch(authServiceProvider);
return await authService.getToken();
});
final deliveryRoutesProvider = FutureProvider<List<DeliveryRoute>>((ref) async {
final token = ref.watch(authTokenProvider).valueOrNull;
if (token == null) {
throw Exception('User not authenticated');
}
// Create a new client with auth token
final authClient = CqrsApiClient(
config: ApiClientConfig(
baseUrl: ApiClientConfig.production.baseUrl,
defaultHeaders: {'Authorization': 'Bearer $token'},
),
);
final result = await authClient.executeQuery<List<DeliveryRoute>>(
endpoint: 'simpleDeliveryRouteQueryItems',
query: _EmptyQuery(),
fromJson: (json) {
// API returns data wrapped in object with "data" field
if (json is Map<String, dynamic>) {
final data = json['data'];
if (data is List) {
return (data as List<dynamic>).map((r) => DeliveryRoute.fromJson(r as Map<String, dynamic>)).toList();
}
}
return [];
},
);
return result.whenSuccess((routes) => routes) ?? [];
});
final deliveriesProvider = FutureProvider.family<List<Delivery>, int>((ref, routeFragmentId) async {
final token = ref.watch(authTokenProvider).valueOrNull;
if (token == null) {
throw Exception('User not authenticated');
}
final authClient = CqrsApiClient(
config: ApiClientConfig(
baseUrl: ApiClientConfig.production.baseUrl,
defaultHeaders: {'Authorization': 'Bearer $token'},
),
);
final result = await authClient.executeQuery<List<Delivery>>(
endpoint: 'simpleDeliveriesQueryItems',
query: _DeliveriesQuery(routeFragmentId: routeFragmentId),
fromJson: (json) {
// API returns data wrapped in object with "data" field
if (json is Map<String, dynamic>) {
final data = json['data'];
if (data is List) {
return (data as List<dynamic>).map((d) => Delivery.fromJson(d as Map<String, dynamic>)).toList();
}
}
return [];
},
);
return result.whenSuccess((deliveries) => deliveries) ?? [];
});
/// Provider to get all deliveries from all routes
final allDeliveriesProvider = FutureProvider<List<Delivery>>((ref) async {
final routes = ref.watch(deliveryRoutesProvider).valueOrNull ?? [];
if (routes.isEmpty) {
return [];
}
// Fetch deliveries for all routes
final deliveriesFutures = routes.map((route) {
return ref.watch(deliveriesProvider(route.id)).when(
data: (deliveries) => deliveries,
loading: () => <Delivery>[],
error: (_, __) => <Delivery>[],
);
});
// Combine all deliveries
final allDeliveries = <Delivery>[];
for (final deliveries in deliveriesFutures) {
allDeliveries.addAll(deliveries);
}
return allDeliveries;
});
final languageProvider = StateProvider<String>((ref) {
return 'fr';
});
/// Theme mode provider for manual theme switching
/// Default is ThemeMode.dark for testing
final themeModeProvider = StateProvider<ThemeMode>((ref) {
return ThemeMode.dark;
});
class _EmptyQuery implements Serializable {
@override
Map<String, Object?> toJson() => {};
}
class _DeliveriesQuery implements Serializable {
final int routeFragmentId;
_DeliveriesQuery({required this.routeFragmentId});
@override
Map<String, Object?> toJson() => {
'params': {
'routeFragmentId': routeFragmentId,
},
};
}