feat(core): refonte architecture transverse (cache, network, websocket, DI)

- lib/app : app.dart, router mis à jour (routes nouveaux modules)
- lib/core/cache : cache_service + cached_datasource_decorator
- lib/core/network : api_client, offline_manager, retry_policy
- lib/core/websocket : websocket service (reconnexion exponentielle, heartbeat)
- lib/core/di : injection + register_module
- lib/core/storage : pending_operations_store (offline support)
- lib/core/navigation : main_navigation_layout (onglets par rôle)
- lib/core/config : environment, lcb_ft_constants
- lib/core/utils : error_formatter, validators
- pubspec.yaml/lock : dépendances mises à jour
This commit is contained in:
dahoud
2026-04-15 20:26:20 +00:00
parent 3a2c8a808f
commit 07b8488714
7 changed files with 145 additions and 19 deletions

View File

@@ -46,7 +46,13 @@ class UnionFlowApp extends StatelessWidget {
], ],
child: Consumer2<LocaleProvider, ThemeProvider>( child: Consumer2<LocaleProvider, ThemeProvider>(
builder: (context, locale, theme, child) { builder: (context, locale, theme, child) {
return MaterialApp( return BlocListener<AuthBloc, AuthState>(
listenWhen: (prev, curr) =>
curr is AuthAuthenticated && prev is! AuthAuthenticated,
listener: (context, _) {
context.read<OrgSwitcherBloc>().add(const OrgSwitcherLoadRequested());
},
child: MaterialApp(
title: 'UnionFlow', title: 'UnionFlow',
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
scaffoldMessengerKey: UnionFlowApp.scaffoldMessengerKey, scaffoldMessengerKey: UnionFlowApp.scaffoldMessengerKey,
@@ -79,7 +85,8 @@ class UnionFlowApp extends StatelessWidget {
child: child ?? const SizedBox(), child: child ?? const SizedBox(),
); );
}, },
); ),
);
}, },
), ),
); );

View File

@@ -20,7 +20,7 @@ import '../../features/adhesions/presentation/pages/adhesions_page_wrapper.dart'
import '../../features/settings/presentation/pages/system_settings_page.dart'; import '../../features/settings/presentation/pages/system_settings_page.dart';
import '../../features/dashboard/presentation/pages/advanced_dashboard_page.dart'; import '../../features/dashboard/presentation/pages/advanced_dashboard_page.dart';
import '../../features/admin/presentation/pages/user_management_page.dart'; import '../../features/admin/presentation/pages/user_management_page.dart';
import '../../features/communication/presentation/pages/conversations_page.dart'; import '../../features/communication/presentation/pages/conversations_page_wrapper.dart';
import '../../features/finance_workflow/presentation/pages/pending_approvals_page.dart'; import '../../features/finance_workflow/presentation/pages/pending_approvals_page.dart';
import '../../features/finance_workflow/presentation/pages/budgets_list_page.dart'; import '../../features/finance_workflow/presentation/pages/budgets_list_page.dart';
import '../../core/navigation/main_navigation_layout.dart'; import '../../core/navigation/main_navigation_layout.dart';
@@ -86,7 +86,7 @@ class AppRouter {
'/reports': (context) => const ReportsPageWrapper(), '/reports': (context) => const ReportsPageWrapper(),
'/finances': (context) => const CotisationsPageWrapper(), '/finances': (context) => const CotisationsPageWrapper(),
'/adhesions': (context) => const AdhesionsPageWrapper(), '/adhesions': (context) => const AdhesionsPageWrapper(),
'/messages': (context) => const ConversationsPage(), '/messages': (context) => const ConversationsPageWrapper(),
'/settings': (context) => const SystemSettingsPage(), '/settings': (context) => const SystemSettingsPage(),
'/analytics': (context) { '/analytics': (context) {
final authState = context.read<AuthBloc>().state; final authState = context.read<AuthBloc>().state;

View File

@@ -118,7 +118,7 @@ class _MainNavigationLayoutState extends State<MainNavigationLayout> {
statusBarIconBrightness: Brightness.dark, statusBarIconBrightness: Brightness.dark,
), ),
child: Scaffold( child: Scaffold(
backgroundColor: ColorTokens.background, backgroundColor: Theme.of(context).scaffoldBackgroundColor,
body: SafeArea( body: SafeArea(
top: true, top: true,
bottom: false, bottom: false,
@@ -199,12 +199,13 @@ class _PillNavigationBar extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: ColorTokens.surface, color: scheme.surface,
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: ColorTokens.shadow, color: scheme.shadow.withOpacity(0.12),
blurRadius: 12, blurRadius: 12,
offset: const Offset(0, -2), offset: const Offset(0, -2),
), ),

View File

@@ -1,6 +1,7 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../shared/design_system/tokens/app_colors.dart';
import 'package:injectable/injectable.dart'; import 'package:injectable/injectable.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import '../../app/app.dart'; import '../../app/app.dart';
@@ -35,17 +36,7 @@ class ApiClient {
), ),
); );
// Intercepteur de Log (Uniquement en Dev) // Intercepteur de Token & Refresh automatique (doit être AVANT le logger)
if (AppConfig.enableLogging) {
_dio.interceptors.add(LogInterceptor(
requestHeader: true,
requestBody: true,
responseBody: true,
logPrint: (obj) => print('🌐 [API] $obj'),
));
}
// Intercepteur de Token & Refresh automatique
_dio.interceptors.add( _dio.interceptors.add(
InterceptorsWrapper( InterceptorsWrapper(
onRequest: (options, handler) async { onRequest: (options, handler) async {
@@ -112,6 +103,16 @@ class ApiClient {
}, },
), ),
); );
// Intercepteur de Log (après le token pour voir le Bearer dans les logs)
if (AppConfig.enableLogging) {
_dio.interceptors.add(LogInterceptor(
requestHeader: true,
requestBody: true,
responseBody: true,
logPrint: (obj) => print('🌐 [API] $obj'),
));
}
} }
void _forceLogout() { void _forceLogout() {
@@ -132,7 +133,7 @@ class ApiClient {
), ),
], ],
), ),
backgroundColor: Colors.orange.shade700, backgroundColor: AppColors.warning,
duration: const Duration(seconds: 4), duration: const Duration(seconds: 4),
behavior: SnackBarBehavior.floating, behavior: SnackBarBehavior.floating,
), ),

View File

@@ -77,6 +77,17 @@ abstract class WebSocketEvent {
organizationId: json['organizationId'] as String?, organizationId: json['organizationId'] as String?,
); );
case 'NOUVEAU_MESSAGE':
case 'MESSAGE_SUPPRIME':
case 'CONVERSATION_LUE':
return ChatMessageEvent(
eventType: eventType,
timestamp: timestamp,
data: data,
conversationId: json['conversationId'] as String?,
organizationId: json['organizationId'] as String?,
);
default: default:
return GenericEvent( return GenericEvent(
eventType: eventType, eventType: eventType,
@@ -144,6 +155,19 @@ class ContributionEvent extends WebSocketEvent {
}); });
} }
class ChatMessageEvent extends WebSocketEvent {
final String? conversationId;
final String? organizationId;
ChatMessageEvent({
required super.eventType,
required super.timestamp,
required super.data,
this.conversationId,
this.organizationId,
});
}
class GenericEvent extends WebSocketEvent { class GenericEvent extends WebSocketEvent {
GenericEvent({ GenericEvent({
required super.eventType, required super.eventType,

View File

@@ -17,6 +17,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.4.1" version: "8.4.1"
ansicolor:
dependency: transitive
description:
name: ansicolor
sha256: "50e982d500bc863e1d703448afdbf9e5a72eb48840a4f766fa361ffd6877055f"
url: "https://pub.dev"
source: hosted
version: "2.0.3"
archive: archive:
dependency: transitive dependency: transitive
description: description:
@@ -177,6 +185,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.2.0" version: "0.2.0"
cli_util:
dependency: transitive
description:
name: cli_util
sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
url: "https://pub.dev"
source: hosted
version: "0.4.2"
clock: clock:
dependency: transitive dependency: transitive
description: description:
@@ -249,6 +265,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.7" version: "3.0.7"
csslib:
dependency: transitive
description:
name: csslib
sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e"
url: "https://pub.dev"
source: hosted
version: "1.0.2"
csv: csv:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -451,6 +475,14 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_launcher_icons:
dependency: "direct dev"
description:
name: flutter_launcher_icons
sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea"
url: "https://pub.dev"
source: hosted
version: "0.13.1"
flutter_lints: flutter_lints:
dependency: "direct dev" dependency: "direct dev"
description: description:
@@ -488,6 +520,14 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_native_splash:
dependency: "direct dev"
description:
name: flutter_native_splash
sha256: "7062602e0dbd29141fb8eb19220b5871ca650be5197ab9c1f193a28b17537bc7"
url: "https://pub.dev"
source: hosted
version: "2.4.4"
flutter_plugin_android_lifecycle: flutter_plugin_android_lifecycle:
dependency: transitive dependency: transitive
description: description:
@@ -623,6 +663,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.2" version: "2.3.2"
html:
dependency: transitive
description:
name: html
sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602"
url: "https://pub.dev"
source: hosted
version: "0.15.6"
http: http:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -1489,6 +1537,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.0" version: "1.4.0"
universal_io:
dependency: transitive
description:
name: universal_io
sha256: f63cbc48103236abf48e345e07a03ce5757ea86285ed313a6a032596ed9301e2
url: "https://pub.dev"
source: hosted
version: "2.3.1"
url_launcher: url_launcher:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@@ -83,12 +83,49 @@ dev_dependencies:
bloc_test: ^9.1.7 bloc_test: ^9.1.7
integration_test: integration_test:
sdk: flutter sdk: flutter
flutter_launcher_icons: ^0.13.1
flutter_native_splash: ^2.4.1
# ─── Génération des icônes d'application (launcher) ──────────────────────────
# Source unique : assets/images/unionflow-logo.png
# Régénérer : dart run flutter_launcher_icons
flutter_launcher_icons:
android: "ic_launcher"
ios: true
remove_alpha_ios: true
image_path: "assets/images/unionflow-logo.png"
background_color_ios: "#FFFFFF"
min_sdk_android: 21
adaptive_icon_background: "#FFFFFF"
adaptive_icon_foreground: "assets/images/unionflow-logo.png"
web:
generate: false
windows:
generate: false
macos:
generate: false
# ─── Génération du splash screen natif (Android + iOS) ───────────────────────
# Régénérer : dart run flutter_native_splash:create
flutter_native_splash:
color: "#FFFFFF"
color_dark: "#0A0D1A"
image: "assets/images/unionflow-logo.png"
android_12:
image: "assets/images/unionflow-logo.png"
color: "#FFFFFF"
color_dark: "#0A0D1A"
android: true
ios: true
web: false
fullscreen: false
flutter: flutter:
uses-material-design: true uses-material-design: true
generate: true generate: true
assets: assets:
- assets/images/ - assets/images/
- assets/images/branding/
- assets/images/payment_methods/wave/ - assets/images/payment_methods/wave/
- assets/images/payment_methods/orange_money/ - assets/images/payment_methods/orange_money/
- assets/images/payment_methods/free_money/ - assets/images/payment_methods/free_money/