fix(mobile): URL changement mdp corrigée + v3.0 — multi-org, AppAuth, sécurité prod

Auth:
- profile_repository.dart: /api/auth/change-password → /api/membres/auth/change-password

Multi-org (Phase 3):
- OrgSelectorPage, OrgSwitcherBloc, OrgSwitcherEntry
- org_context_service.dart: headers X-Active-Organisation-Id + X-Active-Role

Navigation:
- MorePage: navigation conditionnelle par typeOrganisation
- Suppression adaptive_navigation (remplacé par main_navigation_layout)

Auth AppAuth:
- keycloak_webview_auth_service: fixes AppAuth Android
- AuthBloc: gestion REAUTH_REQUIS + premierLoginComplet

Onboarding:
- Nouveaux états: payment_method_page, onboarding_shared_widgets
- SouscriptionStatusModel mis à jour StatutValidationSouscription

Android:
- build.gradle: ProGuard/R8, network_security_config
- Gradle wrapper mis à jour
This commit is contained in:
dahoud
2026-04-07 20:56:03 +00:00
parent 22f9c7e9a1
commit 70cbd1c873
63 changed files with 9316 additions and 6122 deletions

View File

@@ -15,7 +15,7 @@ import '../../bloc/membres_bloc.dart';
import '../../bloc/membres_event.dart';
import '../../bloc/membres_state.dart';
import '../../data/models/membre_complete_model.dart';
import '../widgets/add_member_dialog.dart';
import '../widgets/add_member_dialog.dart' show showAddMemberSheet, showCredentialsDialog;
import 'members_page_connected.dart';
final _getIt = GetIt.instance;
@@ -55,55 +55,14 @@ class MembersPageConnected extends StatelessWidget {
Widget build(BuildContext context) {
return BlocListener<MembresBloc, MembresState>(
listener: (context, state) {
// Après création : afficher le mot de passe temporaire si disponible, puis recharger
// Après création : recharger la liste (la dialog mot de passe est gérée dans AddMemberDialog)
if (state is MembreCreated) {
final motDePasse = state.membre.motDePasseTemporaire;
if (motDePasse != null && motDePasse.isNotEmpty) {
showDialog<void>(
context: context,
barrierDismissible: false,
builder: (_) => AlertDialog(
title: const Text('Compte créé avec succès'),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Le membre ${state.membre.nomComplet} a été créé.'),
const SizedBox(height: 12),
const Text(
'Mot de passe temporaire :',
style: TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
SelectableText(
motDePasse,
style: const TextStyle(
fontSize: 18,
fontFamily: 'monospace',
letterSpacing: 2,
),
),
const SizedBox(height: 12),
const Text(
'Communiquez ce mot de passe au membre. Il devra le changer à sa première connexion.',
style: TextStyle(fontSize: 12, color: Colors.grey),
),
],
),
actions: [
ElevatedButton(
onPressed: () => Navigator.of(_).pop(),
child: const Text('OK'),
),
],
),
);
}
context.read<MembresBloc>().add(LoadMembres(refresh: true, organisationId: organisationId));
}
// Gestion des erreurs avec SnackBar
if (state is MembresError) {
final bloc = context.read<MembresBloc>();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(state.message),
@@ -113,12 +72,67 @@ class MembersPageConnected extends StatelessWidget {
label: 'Réessayer',
textColor: Colors.white,
onPressed: () {
context.read<MembresBloc>().add(LoadMembres(organisationId: organisationId));
bloc.add(LoadMembres(organisationId: organisationId));
},
),
),
);
}
// Après activation : succès + rechargement
if (state is MembreActivated) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Membre activé avec succès'),
backgroundColor: Colors.green,
duration: Duration(seconds: 3),
),
);
context.read<MembresBloc>().add(LoadMembres(refresh: true, organisationId: organisationId));
}
// Après reset mot de passe : afficher le dialog credentials
if (state is MotDePasseReinitialise) {
showCredentialsDialog(context, state.membre);
}
// Après affectation à une organisation : succès + rechargement
if (state is MembreAffecte) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Membre affecté à l\'organisation avec succès'),
backgroundColor: Colors.green,
duration: Duration(seconds: 3),
),
);
context.read<MembresBloc>().add(LoadMembres(refresh: true, organisationId: organisationId));
}
// Lifecycle adhésion : succès + rechargement
if (state is MembreInvite) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Invitation envoyée'), backgroundColor: Colors.blue, duration: Duration(seconds: 3)),
);
context.read<MembresBloc>().add(LoadMembres(refresh: true, organisationId: organisationId));
}
if (state is AdhesionActivee) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Adhésion activée'), backgroundColor: Colors.green, duration: Duration(seconds: 3)),
);
context.read<MembresBloc>().add(LoadMembres(refresh: true, organisationId: organisationId));
}
if (state is AdhesionSuspendue) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Adhésion suspendue'), backgroundColor: Colors.orange, duration: Duration(seconds: 3)),
);
context.read<MembresBloc>().add(LoadMembres(refresh: true, organisationId: organisationId));
}
if (state is MembreRadie) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Membre radié de l\'organisation'), backgroundColor: Colors.red, duration: Duration(seconds: 3)),
);
context.read<MembresBloc>().add(LoadMembres(refresh: true, organisationId: organisationId));
}
},
child: BlocBuilder<MembresBloc, MembresState>(
builder: (context, state) {
@@ -178,6 +192,7 @@ class MembersPageConnected extends StatelessWidget {
totalCount: state.totalElements,
currentPage: state.currentPage,
totalPages: state.totalPages,
organisationId: organisationId,
onPageChanged: (newPage, recherche) {
AppLogger.userAction('Load page', data: {'page': newPage});
context.read<MembresBloc>().add(LoadMembres(page: newPage, recherche: recherche, organisationId: organisationId));
@@ -189,16 +204,37 @@ class MembersPageConnected extends StatelessWidget {
onSearch: (query) {
context.read<MembresBloc>().add(LoadMembres(page: 0, recherche: query, organisationId: organisationId));
},
onAddMember: () async {
final bloc = context.read<MembresBloc>();
await showDialog<void>(
context: context,
builder: (_) => BlocProvider.value(
value: bloc,
child: const AddMemberDialog(),
),
);
onAddMember: () => showAddMemberSheet(context),
onActivateMember: (memberId) {
context.read<MembresBloc>().add(ActivateMembre(memberId));
},
onResetPassword: (memberId) {
context.read<MembresBloc>().add(ResetMotDePasse(memberId));
},
onAffecterOrganisation: organisationId == null
? (memberId, orgId) {
context.read<MembresBloc>().add(AffecterOrganisation(memberId, orgId));
}
: null,
onLifecycleAction: organisationId != null
? (memberId, action, motif) {
final bloc = context.read<MembresBloc>();
switch (action) {
case 'inviter':
bloc.add(InviterMembre(membreId: memberId, organisationId: organisationId!));
break;
case 'activer':
bloc.add(ActiverAdhesion(membreId: memberId, organisationId: organisationId!, motif: motif));
break;
case 'suspendre':
bloc.add(SuspendrAdhesion(membreId: memberId, organisationId: organisationId!, motif: motif));
break;
case 'radier':
bloc.add(RadierAdhesion(membreId: memberId, organisationId: organisationId!, motif: motif));
break;
}
}
: null,
);
}