Files
dahoud 70cbd1c873 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
2026-04-07 20:56:03 +00:00

145 lines
4.9 KiB
Dart

/// BLoC pour le sélecteur d'organisation multi-org.
///
/// Charge GET /api/membres/mes-organisations et maintient
/// l'organisation active via [OrgContextService].
library org_switcher_bloc;
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:injectable/injectable.dart';
import 'package:dio/dio.dart';
import '../../../core/network/api_client.dart';
import '../../../core/network/org_context_service.dart';
import '../../../core/utils/logger.dart';
import '../data/models/org_switcher_entry.dart';
// ─── ÉVÉNEMENTS ─────────────────────────────────────────────────────────────
abstract class OrgSwitcherEvent extends Equatable {
const OrgSwitcherEvent();
@override
List<Object?> get props => [];
}
/// Charge la liste des organisations du membre connecté.
class OrgSwitcherLoadRequested extends OrgSwitcherEvent {
const OrgSwitcherLoadRequested();
}
/// Sélectionne une organisation comme active.
class OrgSwitcherSelectRequested extends OrgSwitcherEvent {
final OrgSwitcherEntry organisation;
const OrgSwitcherSelectRequested(this.organisation);
@override
List<Object?> get props => [organisation];
}
// ─── ÉTATS ──────────────────────────────────────────────────────────────────
abstract class OrgSwitcherState extends Equatable {
const OrgSwitcherState();
@override
List<Object?> get props => [];
}
class OrgSwitcherInitial extends OrgSwitcherState {}
class OrgSwitcherLoading extends OrgSwitcherState {}
class OrgSwitcherLoaded extends OrgSwitcherState {
final List<OrgSwitcherEntry> organisations;
final OrgSwitcherEntry? active;
const OrgSwitcherLoaded({required this.organisations, this.active});
OrgSwitcherLoaded copyWith({
List<OrgSwitcherEntry>? organisations,
OrgSwitcherEntry? active,
}) {
return OrgSwitcherLoaded(
organisations: organisations ?? this.organisations,
active: active ?? this.active,
);
}
@override
List<Object?> get props => [organisations, active];
}
class OrgSwitcherError extends OrgSwitcherState {
final String message;
const OrgSwitcherError(this.message);
@override
List<Object?> get props => [message];
}
// ─── BLOC ────────────────────────────────────────────────────────────────────
@injectable
class OrgSwitcherBloc extends Bloc<OrgSwitcherEvent, OrgSwitcherState> {
final ApiClient _apiClient;
final OrgContextService _orgContextService;
static const String _endpoint = '/api/membres/mes-organisations';
OrgSwitcherBloc(this._apiClient, this._orgContextService)
: super(OrgSwitcherInitial()) {
on<OrgSwitcherLoadRequested>(_onLoad);
on<OrgSwitcherSelectRequested>(_onSelect);
}
Future<void> _onLoad(
OrgSwitcherLoadRequested event,
Emitter<OrgSwitcherState> emit,
) async {
emit(OrgSwitcherLoading());
try {
final response = await _apiClient.get<List<dynamic>>(_endpoint);
final rawList = response.data as List<dynamic>? ?? [];
final orgs = rawList
.map((e) => OrgSwitcherEntry.fromJson(e as Map<String, dynamic>))
.toList();
// Auto-select si une seule organisation ou si une org est déjà active
OrgSwitcherEntry? active;
if (_orgContextService.hasContext) {
active = orgs.where((o) => o.organisationId == _orgContextService.activeOrganisationId).firstOrNull;
}
active ??= orgs.isNotEmpty ? orgs.first : null;
if (active != null && !_orgContextService.hasContext) {
_applyActiveOrg(active);
}
emit(OrgSwitcherLoaded(organisations: orgs, active: active));
} on DioException catch (e) {
AppLogger.warning('OrgSwitcherBloc: erreur réseau: ${e.message}');
emit(OrgSwitcherError('Impossible de charger vos organisations: ${e.message}'));
} catch (e) {
AppLogger.error('OrgSwitcherBloc: erreur inattendue: $e');
emit(OrgSwitcherError('Erreur inattendue: $e'));
}
}
Future<void> _onSelect(
OrgSwitcherSelectRequested event,
Emitter<OrgSwitcherState> emit,
) async {
final current = state;
if (current is! OrgSwitcherLoaded) return;
_applyActiveOrg(event.organisation);
emit(current.copyWith(active: event.organisation));
AppLogger.info('OrgSwitcherBloc: sélection → ${event.organisation.nom}');
}
void _applyActiveOrg(OrgSwitcherEntry org) {
_orgContextService.setActiveOrganisation(
organisationId: org.organisationId,
nom: org.nom,
type: org.typeOrganisation,
modulesActifsCsv: org.modulesActifs,
);
}
}