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>
157 lines
4.5 KiB
Dart
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,
|
|
},
|
|
};
|
|
}
|