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

@@ -2,9 +2,12 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../bloc/onboarding_bloc.dart';
import '../../../../core/di/injection.dart';
import '../../../../features/authentication/presentation/bloc/auth_bloc.dart';
import '../../../../shared/design_system/tokens/unionflow_colors.dart';
import 'plan_selection_page.dart';
import 'period_selection_page.dart';
import 'subscription_summary_page.dart';
import 'payment_method_page.dart';
import 'wave_payment_page.dart';
import 'awaiting_validation_page.dart';
@@ -14,11 +17,13 @@ class OnboardingFlowPage extends StatelessWidget {
final String onboardingState;
final String? souscriptionId;
final String organisationId;
final String? typeOrganisation;
const OnboardingFlowPage({
super.key,
required this.onboardingState,
required this.organisationId,
this.typeOrganisation,
this.souscriptionId,
});
@@ -29,6 +34,8 @@ class OnboardingFlowPage extends StatelessWidget {
..add(OnboardingStarted(
initialState: onboardingState,
existingSouscriptionId: souscriptionId,
typeOrganisation: typeOrganisation,
organisationId: organisationId.isNotEmpty ? organisationId : null,
)),
child: const _OnboardingFlowView(),
);
@@ -40,30 +47,76 @@ class _OnboardingFlowView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<OnboardingBloc, OnboardingState>(
return BlocConsumer<OnboardingBloc, OnboardingState>(
listener: (context, state) {
// Paiement confirmé → re-check du statut (auto-activation backend)
if (state is OnboardingPaiementConfirme) {
context.read<AuthBloc>().add(const AuthStatusChecked());
}
},
builder: (context, state) {
if (state is OnboardingLoading || state is OnboardingInitial) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
if (state is OnboardingLoading || state is OnboardingInitial || state is OnboardingPaiementConfirme) {
return Scaffold(
backgroundColor: UnionFlowColors.background,
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const CircularProgressIndicator(
color: UnionFlowColors.unionGreen,
),
const SizedBox(height: 16),
Text(
state is OnboardingPaiementConfirme
? 'Activation de votre compte…'
: 'Chargement…',
style: const TextStyle(
color: UnionFlowColors.textSecondary,
fontSize: 15,
),
),
],
),
),
);
}
if (state is OnboardingError) {
return Scaffold(
backgroundColor: UnionFlowColors.background,
body: Center(
child: Padding(
padding: const EdgeInsets.all(24),
padding: const EdgeInsets.all(32),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.error_outline, size: 64, color: Colors.red),
const SizedBox(height: 16),
Text(state.message, textAlign: TextAlign.center),
Container(
width: 80,
height: 80,
decoration: BoxDecoration(
color: UnionFlowColors.errorPale,
shape: BoxShape.circle,
),
child: const Icon(Icons.error_outline,
size: 40, color: UnionFlowColors.error),
),
const SizedBox(height: 20),
Text(
state.message,
textAlign: TextAlign.center,
style: const TextStyle(
color: UnionFlowColors.textPrimary, fontSize: 15),
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () => context.read<OnboardingBloc>().add(
OnboardingStarted(initialState: 'NO_SUBSCRIPTION'),
const OnboardingStarted(
initialState: 'NO_SUBSCRIPTION'),
),
style: ElevatedButton.styleFrom(
backgroundColor: UnionFlowColors.unionGreen,
foregroundColor: Colors.white,
),
child: const Text('Réessayer'),
),
],
@@ -89,6 +142,10 @@ class _OnboardingFlowView extends StatelessWidget {
return SubscriptionSummaryPage(souscription: state.souscription);
}
if (state is OnboardingStepChoixPaiement) {
return PaymentMethodPage(souscription: state.souscription);
}
if (state is OnboardingStepPaiement) {
return WavePaymentPage(
souscription: state.souscription,
@@ -104,7 +161,9 @@ class _OnboardingFlowView extends StatelessWidget {
return _RejectedPage(commentaire: state.commentaire);
}
return const Scaffold(body: Center(child: CircularProgressIndicator()));
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
},
);
}
@@ -117,28 +176,93 @@ class _RejectedPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
backgroundColor: UnionFlowColors.background,
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(32),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.cancel_outlined, size: 72, color: Colors.red),
const SizedBox(height: 24),
const Text(
'Souscription rejetée',
style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: UnionFlowColors.errorPale,
shape: BoxShape.circle,
),
child: const Icon(Icons.cancel_outlined,
size: 52, color: UnionFlowColors.error),
),
if (commentaire != null) ...[
const SizedBox(height: 12),
Text(commentaire!, textAlign: TextAlign.center),
const SizedBox(height: 28),
const Text(
'Demande rejetée',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: UnionFlowColors.textPrimary,
),
),
const SizedBox(height: 12),
const Text(
'Votre demande de souscription a été refusée.',
textAlign: TextAlign.center,
style: TextStyle(color: UnionFlowColors.textSecondary),
),
if (commentaire != null && commentaire!.isNotEmpty) ...[
const SizedBox(height: 20),
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: UnionFlowColors.errorPale,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: UnionFlowColors.error.withOpacity(0.3)),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Icon(Icons.comment_outlined,
color: UnionFlowColors.error, size: 18),
const SizedBox(width: 10),
Expanded(
child: Text(
commentaire!,
style: const TextStyle(
color: UnionFlowColors.textPrimary,
fontSize: 14,
height: 1.5),
),
),
],
),
),
],
const SizedBox(height: 32),
ElevatedButton(
onPressed: () => context.read<OnboardingBloc>().add(
OnboardingStarted(initialState: 'NO_SUBSCRIPTION'),
),
child: const Text('Nouvelle souscription'),
const SizedBox(height: 36),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () => context.read<OnboardingBloc>().add(
const OnboardingStarted(
initialState: 'NO_SUBSCRIPTION'),
),
style: ElevatedButton.styleFrom(
backgroundColor: UnionFlowColors.unionGreen,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
),
child: const Text('Soumettre une nouvelle demande',
style: TextStyle(fontSize: 15)),
),
),
const SizedBox(height: 12),
TextButton(
onPressed: () =>
context.read<AuthBloc>().add(const AuthLogoutRequested()),
child: const Text('Se déconnecter',
style:
TextStyle(color: UnionFlowColors.textSecondary)),
),
],
),