Files
wortis_tpe/lib/main.dart

1135 lines
34 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:wtpe/pages/role_navigator.dart';
import 'package:wtpe/pages/session_manager.dart';
import 'pages/splash_screen.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
import '../widgets/responsive_helper.dart';
import 'services/connectivity_service.dart';
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
// =================== MODÈLE SERVICE (INCHANGÉ) ===================
class WortisService {
final int? rang;
final String id;
final String name;
final String description;
final String secteurActivite;
final String typeService;
final String icon;
final String? banner;
final bool status;
final String? linkView;
WortisService({
required this.id,
this.rang,
required this.name,
required this.description,
required this.secteurActivite,
required this.typeService,
required this.icon,
this.banner,
required this.status,
this.linkView,
});
factory WortisService.fromJson(Map<String, dynamic> json) {
return WortisService(
id: json['_id'] ?? '',
name: json['name'] ?? '',
description: json['description'] ?? '',
secteurActivite: json['SecteurActivite'] ?? '',
typeService: json['Type_Service'] ?? '',
icon: json['icon'] ?? 'apps',
banner: json['banner'],
status: json['status'] ?? false,
linkView: json['link_view'],
);
}
IconData get flutterIcon {
switch (icon.toLowerCase()) {
case 'movie':
return Icons.movie;
case 'phone':
return Icons.phone;
case 'wifi':
return Icons.wifi;
case 'flash_on':
return Icons.flash_on;
case 'water_drop':
return Icons.water_drop;
case 'account_balance_wallet':
return Icons.account_balance_wallet;
case 'tv':
return Icons.tv;
case 'security':
return Icons.security;
case 'send':
return Icons.send;
default:
return Icons.apps;
}
}
Color get sectorColor {
switch (secteurActivite.toLowerCase()) {
case 'billetterie':
return Color(0xFF9C27B0);
case 'mobile money':
return Color(0xFFFF9800);
case 'télécommunications':
case 'telecommunication':
return Color(0xFF2196F3);
case 'électricité & eau':
case 'electricite':
return Color(0xFF4CAF50);
case 'services financiers':
return Color(0xFFFF6600);
case 'transport':
return Color(0xFF607D8B);
default:
return Color(0xFF006699);
}
}
get logo => null;
}
// =================== MODÈLE UTILISATEUR ÉTENDU ===================
class User {
final String id;
final String nom;
final String agentId;
final String role;
final DateTime dateCreation;
final String type; // 'agent' ou 'enterprise_member'
final Enterprise? enterprise; // Nullable pour les agents individuels
User({
required this.id,
required this.nom,
required this.agentId,
required this.role,
required this.dateCreation,
this.type = 'agent',
this.enterprise,
});
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'] ?? '',
nom: json['nom'] ?? '',
agentId: json['agent_id'] ?? '',
role: json['role'] ?? 'agent',
dateCreation:
json['date_creation'] != null
? DateTime.parse(json['date_creation'])
: DateTime.now(),
type: json['type'] ?? 'agent',
enterprise:
json['enterprise'] != null
? Enterprise.fromJson(json['enterprise'])
: null,
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'nom': nom,
'agent_id': agentId,
'role': role,
'date_creation': dateCreation.toIso8601String(),
'type': type,
'enterprise': enterprise?.toJson(),
};
}
bool get isEnterpriseMember => type == 'enterprise_member';
bool get isIndividualAgent => type == 'agent';
}
// ===== Dans main.dart - Mettre à jour la classe Enterprise =====
// ===== CORRIGER LA CLASSE Enterprise =====
class Enterprise {
final String id;
final String nomEntreprise;
final String? numeroRegistreCommerce;
final String? adresse;
final String? telephone;
final String? email;
final double soldeEntreprise;
final List<String> membresIds;
final DateTime dateCreation;
final int nombreMembres;
final String? domaineActivite;
// AJOUTER ces propriétés manquantes
final String enterpriseId; // Pour compatibilité
final String status;
Enterprise({
required this.id,
required this.nomEntreprise,
this.numeroRegistreCommerce,
this.adresse,
this.telephone,
this.email,
required this.soldeEntreprise,
required this.membresIds,
required this.dateCreation,
required this.nombreMembres,
this.domaineActivite,
String? enterpriseId, // NOUVEAU
String? status, // NOUVEAU
}) : enterpriseId = enterpriseId ?? id,
status = status ?? 'active';
factory Enterprise.fromJson(Map<String, dynamic> json) {
return Enterprise(
id: json['_id'] ?? json['id'] ?? '',
nomEntreprise: json['nom_entreprise'] ?? json['nomEntreprise'] ?? '',
numeroRegistreCommerce:
json['numero_registre_commerce'] ?? json['numeroRegistreCommerce'],
adresse: json['adresse'],
telephone: json['telephone'],
email: json['email'],
soldeEntreprise:
(json['solde_entreprise'] ?? json['soldeEntreprise'] ?? 0.0)
.toDouble(),
membresIds: List<String>.from(
json['membres_ids'] ?? json['membresIds'] ?? [],
),
dateCreation:
json['date_creation'] != null
? DateTime.parse(json['date_creation'])
: json['dateCreation'] != null
? DateTime.parse(json['dateCreation'])
: DateTime.now(),
nombreMembres:
json['nombre_membres'] ??
json['nombreMembres'] ??
(json['membres_ids'] as List?)?.length ??
(json['membresIds'] as List?)?.length ??
1,
domaineActivite:
json['domaine_activite'] ??
json['domaineActivite'] ??
json['secteur_activite'] ??
json['secteurActivite'],
enterpriseId: json['enterprise_id'] ?? json['enterpriseId'], // NOUVEAU
status: json['status'] ?? 'active', // NOUVEAU
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'enterprise_id': enterpriseId,
'nom_entreprise': nomEntreprise,
'numero_registre_commerce': numeroRegistreCommerce,
'adresse': adresse,
'telephone': telephone,
'email': email,
'solde_entreprise': soldeEntreprise,
'membres_ids': membresIds,
'date_creation': dateCreation.toIso8601String(),
'nombre_membres': nombreMembres,
'domaine_activite': domaineActivite,
'status': status,
};
}
Enterprise copyWith({
String? id,
String? nomEntreprise,
String? numeroRegistreCommerce,
String? adresse,
String? telephone,
String? email,
double? soldeEntreprise,
List<String>? membresIds,
DateTime? dateCreation,
int? nombreMembres,
String? domaineActivite,
String? enterpriseId,
String? status,
}) {
return Enterprise(
id: id ?? this.id,
nomEntreprise: nomEntreprise ?? this.nomEntreprise,
numeroRegistreCommerce:
numeroRegistreCommerce ?? this.numeroRegistreCommerce,
adresse: adresse ?? this.adresse,
telephone: telephone ?? this.telephone,
email: email ?? this.email,
soldeEntreprise: soldeEntreprise ?? this.soldeEntreprise,
membresIds: membresIds ?? this.membresIds,
dateCreation: dateCreation ?? this.dateCreation,
nombreMembres: nombreMembres ?? this.nombreMembres,
domaineActivite: domaineActivite ?? this.domaineActivite,
enterpriseId: enterpriseId ?? this.enterpriseId,
status: status ?? this.status,
);
}
}
// =================== SERVICE API (INCHANGÉ) ===================
class WortisApiService {
static const String baseUrl = 'https://api.live.wortis.cg/tpe';
static final WortisApiService _instance = WortisApiService._internal();
factory WortisApiService() => _instance;
WortisApiService._internal();
Future<List<WortisService>> getServices() async {
try {
final response = await http
.get(
Uri.parse('$baseUrl/get_services_tpe'),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
)
.timeout(Duration(seconds: 30));
if (response.statusCode == 200) {
final Map<String, dynamic> jsonData = json.decode(response.body);
final List<dynamic> servicesJson = jsonData['all_services'] ?? [];
return servicesJson
.map((serviceJson) => WortisService.fromJson(serviceJson))
.toList();
} else {
throw Exception('Erreur API: ${response.statusCode}');
}
} catch (e) {
print('Erreur lors de la récupération des services: $e');
throw Exception('Impossible de récupérer les services: $e');
}
}
Map<String, List<WortisService>> groupServicesBySector(
List<WortisService> services,
) {
Map<String, List<WortisService>> grouped = {};
for (var service in services) {
String sector = service.secteurActivite;
if (grouped[sector] == null) {
grouped[sector] = [];
}
grouped[sector]!.add(service);
}
return grouped;
}
}
// =================== CONTROLLER SERVICES (INCHANGÉ) ===================
class ServicesController extends ChangeNotifier {
final WortisApiService _apiService = WortisApiService();
List<WortisService> _allServices = [];
Map<String, List<WortisService>> _servicesBySector = {};
bool _isLoading = false;
String? _error;
String _searchQuery = '';
String _selectedSector = 'Tous';
List<WortisService> get allServices => _allServices;
Map<String, List<WortisService>> get servicesBySector => _servicesBySector;
bool get isLoading => _isLoading;
String? get error => _error;
String get searchQuery => _searchQuery;
String get selectedSector => _selectedSector;
List<String> get sectors => ['Tous', ..._servicesBySector.keys];
List<WortisService> get filteredServices {
List<WortisService> services;
if (_selectedSector == 'Tous') {
services = _allServices;
} else {
services = _servicesBySector[_selectedSector] ?? [];
}
if (_searchQuery.isEmpty) {
return services;
}
return services.where((service) {
final name = service.name.toLowerCase();
final description = service.description.toLowerCase();
final sector = service.secteurActivite.toLowerCase();
final query = _searchQuery.toLowerCase();
return name.contains(query) ||
description.contains(query) ||
sector.contains(query);
}).toList();
}
Future<void> loadServices() async {
_isLoading = true;
_error = null;
notifyListeners();
try {
_allServices = await _apiService.getServices();
_servicesBySector = _apiService.groupServicesBySector(_allServices);
_error = null;
} catch (e) {
_error = e.toString();
_allServices = [];
_servicesBySector = {};
} finally {
_isLoading = false;
notifyListeners();
}
}
Future<void> refreshServices() async {
await loadServices();
}
void updateSearchQuery(String query) {
_searchQuery = query;
notifyListeners();
}
void selectSector(String sector) {
_selectedSector = sector;
notifyListeners();
}
void clearSearch() {
_searchQuery = '';
notifyListeners();
}
List<WortisService> get activeServices {
return _allServices.where((service) => service.status).toList();
}
Map<String, int> get servicesCountBySector {
Map<String, int> count = {};
for (var entry in _servicesBySector.entries) {
count[entry.key] = entry.value.length;
}
return count;
}
}
// =================== AUTH CONTROLLER AVEC SUPPORT ENTREPRISE ===================
class AuthController extends ChangeNotifier {
bool _isLoading = false;
bool _isLoggedIn = false;
String? _agentId;
String? _agentName;
String? _pinkey;
String? _role;
String? _errorMessage;
String? _token;
double _balance = 0.0;
Map<String, dynamic>? _currentUser;
DateTime? _lastLoginDate;
String? _lastWorkingEndpoint;
// ===== NOUVEAUX ATTRIBUTS POUR ENTREPRISE =====
String? _accountType; // 'agent' ou 'enterprise_member'
Enterprise? _enterprise;
double? _enterpriseBalance;
String? _balanceSource; // 'personal' ou 'enterprise'
static const String baseUrl = 'https://api.live.wortis.cg/tpe';
static const Map<String, String> apiHeaders = {
'Content-Type': 'application/json',
'Accept': 'application/json',
};
// Getters existants
bool get isLoading => _isLoading;
bool get isLoggedIn => _isLoggedIn;
String? get agentId => _agentId;
String? get pinkey => _pinkey;
String? get agentName => _agentName;
String? get role => _role;
String? get errorMessage => _errorMessage;
double get balance => _balance;
Map<String, dynamic>? get currentUser => _currentUser;
DateTime? get lastLoginDate => _lastLoginDate;
// ===== NOUVEAUX GETTERS POUR ENTREPRISE =====
String get accountType => _accountType ?? 'agent';
Enterprise? get enterprise => _enterprise;
double? get enterpriseBalance => _enterpriseBalance;
String get balanceSource => _balanceSource ?? 'personal';
bool get isEnterpriseMember => _accountType == 'enterprise_member';
bool get isIndividualAgent => _accountType == 'agent';
// Retourne le solde à utiliser selon le type de compte
double get activeBalance {
if (isEnterpriseMember && _enterpriseBalance != null) {
return _enterpriseBalance!;
}
return _balance;
}
void _setLoading(bool loading) {
_isLoading = loading;
notifyListeners();
}
void _setError(String? error) {
_errorMessage = error;
notifyListeners();
}
Future<bool> testApiConnection() async {
try {
final response = await http
.get(Uri.parse('$baseUrl/get_services_tpe'), headers: apiHeaders)
.timeout(Duration(seconds: 5));
return response.statusCode == 200;
} catch (e) {
print('Erreur test connexion API: $e');
return false;
}
}
Future<Map<String, dynamic>> _loginWithApi(
String agentId,
String pin,
bool isSessionLogin,
) async {
List<String> possibleEndpoints = [
'$baseUrl/login',
'$baseUrl/auth/login',
'$baseUrl/auth_agent',
'$baseUrl/authenticate',
'$baseUrl/agent/login',
'$baseUrl/tpe/auth',
];
if (_lastWorkingEndpoint != null) {
possibleEndpoints.remove(_lastWorkingEndpoint);
possibleEndpoints.insert(0, _lastWorkingEndpoint!);
}
Map<String, dynamic> body;
if (isSessionLogin) {
print('Connexion avec token de session');
String? savedPin = await SessionManager().getPIN();
body = {'agent_id': agentId, 'pin': savedPin ?? pin};
} else {
print('Connexion avec PIN');
body = {'agent_id': agentId, 'pin': pin};
}
for (String endpoint in possibleEndpoints) {
try {
print('Tentative avec endpoint: $endpoint');
final response = await http
.post(
Uri.parse(endpoint),
headers: apiHeaders,
body: jsonEncode(body),
)
.timeout(Duration(seconds: 8));
print('Réponse API ($endpoint): ${response.statusCode}');
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
_lastWorkingEndpoint = endpoint;
print('✅ Connexion réussie avec $endpoint');
return data;
} else if (response.statusCode == 404) {
print('Endpoint $endpoint non trouvé');
continue;
} else if (response.statusCode == 401 || response.statusCode == 403) {
print('Authentification échouée sur $endpoint');
return {'success': false, 'message': 'Identifiants incorrects'};
} else {
print('Erreur $endpoint: ${response.statusCode}');
continue;
}
} catch (e) {
print('Erreur réseau $endpoint: $e');
continue;
}
}
return {
'success': false,
'message': 'Service d\'authentification indisponible',
};
}
Future<bool> login(
String agentId,
String pin, [
bool isSessionLogin = false,
]) async {
_setLoading(true);
_setError(null);
try {
print('🔐 Tentative de connexion pour: $agentId');
bool apiConnected = await testApiConnection();
if (apiConnected) {
print('✅ API disponible, connexion en ligne');
final response = await _loginWithApi(agentId, pin, isSessionLogin);
if (response['success'] == true ||
response.containsKey('user') ||
response.containsKey('agent_id') ||
response.containsKey('token')) {
await _setUserData(response, agentId);
final session = SessionManager();
await session.saveSession(
_agentId!,
token: _token,
role: _role,
pinkey: pin,
);
// Log selon le type de compte
if (isEnterpriseMember) {
print('✅ Connexion membre entreprise réussie');
print(' - Entreprise: ${_enterprise?.nomEntreprise}');
print(' - Solde entreprise: ${_enterpriseBalance} FCFA');
print(' - Solde personnel: $_balance FCFA');
} else {
print('✅ Connexion agent individuel réussie');
print(' - Solde: $_balance FCFA');
}
_setLoading(false);
return true;
} else {
_setError(response['message'] ?? 'Identifiants incorrects');
print('❌ Erreur API: ${response['message']}');
}
} else {
print('⚠️ API non disponible');
_setError('Service temporairement indisponible');
}
} catch (e) {
_setError('Erreur de connexion: ${e.toString()}');
print('❌ Exception login: $e');
}
_setLoading(false);
return false;
}
/// ===== MÉTHODE AMÉLIORÉE POUR GÉRER LES DONNÉES ENTREPRISE =====
Future<void> _setUserData(
Map<String, dynamic> response,
String agentId,
) async {
_isLoggedIn = true;
_lastLoginDate = DateTime.now();
try {
if (response.containsKey('user') && response['user'] != null) {
_currentUser = response['user'];
_agentId = _currentUser?['agent_id']?.toString() ?? agentId;
_agentName = _currentUser?['nom']?.toString() ?? 'Agent $_agentId';
_pinkey = _currentUser?['pin']?.toString();
_role = _currentUser?['role']?.toString() ?? 'agent';
_balance = _parseDouble(_currentUser?['solde']) ?? 0.0;
_token =
_currentUser?['token']?.toString() ?? response['token']?.toString();
// Gestion du type de compte
_accountType = _currentUser?['type']?.toString() ?? 'agent';
_balanceSource =
_accountType == 'enterprise_member' ? 'enterprise' : 'personal';
// Extraction des données entreprise
if (_currentUser?['enterprise'] != null) {
try {
_enterprise = Enterprise.fromJson(_currentUser!['enterprise']);
_enterpriseBalance = _enterprise?.soldeEntreprise;
print('📊 Données entreprise chargées:');
print(' - ID: ${_enterprise?.enterpriseId}');
print(' - Nom: ${_enterprise?.nomEntreprise}');
print(' - Solde: ${_enterpriseBalance} FCFA');
print(' - Membres: ${_enterprise?.nombreMembres}');
print(
' - Secteur: ${_enterprise?.domaineActivite ?? "Non spécifié"}',
);
} catch (e) {
print('⚠️ Erreur parsing entreprise: $e');
_enterprise = null;
_enterpriseBalance = null;
}
}
} else {
_agentId = response['agent_id']?.toString() ?? agentId;
_agentName = response['nom']?.toString() ?? 'Agent $_agentId';
_pinkey = response['pin']?.toString();
_role = response['role']?.toString() ?? 'agent';
_balance = _parseDouble(response['solde']) ?? 0.0;
_token = response['token']?.toString();
_accountType = response['type']?.toString() ?? 'agent';
// Gestion entreprise au niveau racine
if (response['enterprise'] != null) {
try {
_enterprise = Enterprise.fromJson(response['enterprise']);
_enterpriseBalance = _enterprise?.soldeEntreprise;
_balanceSource = 'enterprise';
} catch (e) {
print('⚠️ Erreur parsing entreprise (racine): $e');
}
}
_currentUser = {
'agent_id': _agentId,
'nom': _agentName,
'pin': _pinkey,
'role': _role,
'solde': _balance,
'token': _token,
'type': _accountType,
'enterprise': _enterprise?.toJson(),
};
}
// Valeurs par défaut si non définies
_agentId ??= agentId;
_agentName ??= 'Agent $_agentId';
_role ??= 'agent';
_accountType ??= 'agent';
_token ??= 'temp_token_${DateTime.now().millisecondsSinceEpoch}';
} catch (e) {
print('❌ Erreur configuration données utilisateur: $e');
print('Stack trace: ${StackTrace.current}');
// Fallback sécurisé
_agentId = agentId;
_agentName = 'Agent $agentId';
_role = 'agent';
_balance = 0.0;
_accountType = 'agent';
_token = 'fallback_token_${DateTime.now().millisecondsSinceEpoch}';
_enterprise = null;
_enterpriseBalance = null;
_currentUser = {
'agent_id': _agentId,
'nom': _agentName,
'role': _role,
'solde': _balance,
'token': _token,
'type': _accountType,
};
}
}
double? _parseDouble(dynamic value) {
if (value == null) return null;
if (value is double) return value;
if (value is int) return value.toDouble();
if (value is String) {
try {
return double.parse(value);
} catch (e) {
return null;
}
}
return null;
}
Future<bool> checkLoginStatus(BuildContext context) async {
_setLoading(true);
try {
final session = SessionManager();
bool hasSession = await session.isLoggedIn();
if (hasSession) {
String? savedUserId = await session.getUserId();
String? savedToken = await session.getToken();
String? savedRole = await session.getRole();
String? savedPin = await session.getPIN();
if (savedUserId != null && savedPin != null && savedRole != null) {
print(
'Session trouvée pour $savedUserId, tentative de reconnexion...',
);
bool reconnected = await login(savedUserId, savedPin, true);
if (reconnected) {
print('✅ Reconnexion automatique réussie');
WidgetsBinding.instance.addPostFrameCallback((_) {
RoleNavigator.navigateByRole(context, _role!);
});
_setLoading(false);
return true;
} else {
print('❌ Échec de la reconnexion, suppression de la session');
await session.clearSession();
}
}
}
} catch (e) {
print('❌ Erreur vérification session: $e');
}
_setLoading(false);
return false;
}
/// ===== NOUVELLE MÉTHODE: RAFRAÎCHIR TOUS LES SOLDES =====
Future<void> refreshBalance() async {
if (_agentId != null) {
try {
final response = await http.get(
Uri.parse('$baseUrl/agent/$_agentId/balance'),
headers: apiHeaders,
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
if (data['success']) {
// Solde personnel
_balance = _parseDouble(data['solde']) ?? 0.0;
// Solde entreprise si applicable
if (data['enterprise'] != null) {
_enterpriseBalance = _parseDouble(
data['enterprise']['solde_entreprise'],
);
}
notifyListeners();
print(
'✅ Soldes mis à jour - Personnel: $_balance, Entreprise: $_enterpriseBalance',
);
}
}
} catch (e) {
print('❌ Erreur actualisation solde: $e');
}
}
}
Future<void> logout() async {
print('Déconnexion de $_agentName');
_isLoggedIn = false;
_agentId = null;
_agentName = null;
_pinkey = null;
_role = null;
_balance = 0.0;
_errorMessage = null;
_currentUser = null;
_lastLoginDate = null;
_token = null;
// Réinitialiser les données entreprise
_accountType = null;
_enterprise = null;
_enterpriseBalance = null;
_balanceSource = null;
await SessionManager().clearSession();
notifyListeners();
}
/// ===== MÉTHODES AMÉLIORÉES POUR GÉRER LES SOLDES =====
void updateBalance(double newBalance) {
if (isEnterpriseMember) {
// Mise à jour du solde entreprise
if (newBalance != _enterpriseBalance) {
_enterpriseBalance = newBalance;
if (_enterprise != null) {
_enterprise = _enterprise!.copyWith(soldeEntreprise: newBalance);
}
notifyListeners();
}
} else {
// Mise à jour du solde personnel
if (newBalance != _balance) {
_balance = newBalance;
if (_currentUser != null) {
_currentUser!['solde'] = newBalance;
}
notifyListeners();
}
}
}
void debitBalance(double amount) {
if (amount <= 0) return;
if (isEnterpriseMember && _enterpriseBalance != null) {
// Débiter le solde entreprise
if (amount <= _enterpriseBalance!) {
_enterpriseBalance = _enterpriseBalance! - amount;
notifyListeners();
}
} else {
// Débiter le solde personnel
if (amount <= _balance) {
_balance -= amount;
if (_currentUser != null) {
_currentUser!['solde'] = _balance;
}
notifyListeners();
}
}
}
void creditBalance(double amount) {
if (amount <= 0) return;
if (isEnterpriseMember && _enterpriseBalance != null) {
// Créditer le solde entreprise
_enterpriseBalance = _enterpriseBalance! + amount;
notifyListeners();
} else {
// Créditer le solde personnel
_balance += amount;
if (_currentUser != null) {
_currentUser!['solde'] = _balance;
}
notifyListeners();
}
}
bool hasSufficientBalance(double requiredAmount) {
return activeBalance >= requiredAmount;
}
void clearError() {
_setError(null);
}
/// ===== NOUVELLE MÉTHODE: OBTENIR LES INFOS DE BALANCE =====
Map<String, dynamic> getBalanceInfo() {
return {
'account_type': accountType,
'is_enterprise_member': isEnterpriseMember,
'personal_balance': _balance,
'enterprise_balance': _enterpriseBalance,
'active_balance': activeBalance,
'balance_source': balanceSource,
'enterprise_name': _enterprise?.nomEntreprise,
'enterprise_id': _enterprise?.enterpriseId,
};
}
}
// =================== MAIN APP ===================
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
ResponsiveHelper.enableAllOrientations();
SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.light,
systemNavigationBarColor: Color(0xFF006699),
systemNavigationBarIconBrightness: Brightness.light,
),
);
ConnectivityService.setNavigatorKey(navigatorKey);
runApp(WortisApp());
}
class WortisApp extends StatefulWidget {
static const Color primaryColor = Color(0xFF006699);
static const Color secondaryColor = Color(0xFF0088CC);
static const Color accentColor = Color(0xFFFF6B35);
static const Color backgroundColor = Color(0xFFF8FAFC);
static const Color surfaceColor = Colors.white;
static const Color errorColor = Color(0xFFE53E3E);
const WortisApp({super.key});
@override
State<WortisApp> createState() => _WortisAppState();
}
class _WortisAppState extends State<WortisApp> with WidgetsBindingObserver {
final ConnectivityService _connectivityService = ConnectivityService();
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
print('✅ WortisApp initialisé avec support entreprise');
WidgetsBinding.instance.addPostFrameCallback((_) {
print('🌐 Démarrage de la surveillance de connectivité');
_connectivityService.startMonitoring();
});
}
@override
void dispose() {
_connectivityService.dispose();
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
if (state == AppLifecycleState.resumed) {
print('📱 App au premier plan, vérification de connectivité');
_connectivityService.checkConnectivity();
}
}
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => AuthController()),
ChangeNotifierProvider(create: (_) => ServicesController()),
ChangeNotifierProvider<ConnectivityService>.value(
value: _connectivityService,
),
],
child: MaterialApp(
title: 'Wortis Agent',
debugShowCheckedModeBanner: false,
navigatorKey: navigatorKey,
theme: _buildAppTheme(),
home: SplashScreen(),
builder: (context, child) {
return MediaQuery(
data: MediaQuery.of(
context,
).copyWith(textScaler: TextScaler.linear(1.0)),
child: child!,
);
},
),
);
}
ThemeData _buildAppTheme() {
return ThemeData(
primarySwatch: _createMaterialColor(WortisApp.primaryColor),
primaryColor: WortisApp.primaryColor,
scaffoldBackgroundColor: WortisApp.backgroundColor,
appBarTheme: AppBarTheme(
backgroundColor: WortisApp.primaryColor,
foregroundColor: Colors.white,
elevation: 0,
centerTitle: true,
titleTextStyle: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Colors.white,
),
systemOverlayStyle: SystemUiOverlayStyle.light,
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: WortisApp.primaryColor,
foregroundColor: Colors.white,
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
elevation: 2,
textStyle: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
),
),
inputDecorationTheme: InputDecorationTheme(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: Colors.grey.shade300),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: WortisApp.primaryColor, width: 2),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: WortisApp.errorColor),
),
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
filled: true,
fillColor: WortisApp.surfaceColor,
),
snackBarTheme: SnackBarThemeData(
backgroundColor: Colors.grey.shade800,
contentTextStyle: TextStyle(color: Colors.white),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
behavior: SnackBarBehavior.floating,
),
// cardTheme: CardTheme(
// elevation: 2,
// shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
// clipBehavior: Clip.antiAlias,
// ),
visualDensity: VisualDensity.adaptivePlatformDensity,
useMaterial3: true,
);
}
MaterialColor _createMaterialColor(Color color) {
List strengths = <double>[.05];
Map<int, Color> swatch = <int, Color>{};
final int r = color.red, g = color.green, b = color.blue;
for (int i = 1; i < 10; i++) {
strengths.add(0.1 * i);
}
for (var strength in strengths) {
final double ds = 0.5 - strength;
swatch[(strength * 1000).round()] = Color.fromRGBO(
r + ((ds < 0 ? r : (255 - r)) * ds).round(),
g + ((ds < 0 ? g : (255 - g)) * ds).round(),
b + ((ds < 0 ? b : (255 - b)) * ds).round(),
1,
);
}
return MaterialColor(color.value, swatch);
}
}