fix(#112): afficher photos enfants en reprise + consentement cohérent

Charge existingPhotoUrl dans les cartes enfant (étapes 3 et 5) et pré-coche
le consentement photo lorsqu'une photo est déjà en base.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
MARTIN Julien 2026-06-16 17:00:15 +02:00
parent f300505225
commit 9b7231f1da
5 changed files with 36 additions and 7 deletions

View File

@ -30,6 +30,7 @@ class ParentData {
class ChildData { class ChildData {
static const Object _unsetImage = Object(); static const Object _unsetImage = Object();
static const Object _unsetImageBytes = Object(); static const Object _unsetImageBytes = Object();
static const Object _unsetExistingPhotoUrl = Object();
String firstName; String firstName;
String lastName; String lastName;
@ -75,7 +76,7 @@ class ChildData {
Object? imageBytes = _unsetImageBytes, Object? imageBytes = _unsetImageBytes,
CardColorVertical? cardColor, CardColorVertical? cardColor,
String? repriseChildId, String? repriseChildId,
String? existingPhotoUrl, Object? existingPhotoUrl = _unsetExistingPhotoUrl,
}) { }) {
return ChildData( return ChildData(
firstName: firstName ?? this.firstName, firstName: firstName ?? this.firstName,
@ -90,7 +91,9 @@ class ChildData {
identical(imageBytes, _unsetImageBytes) ? this.imageBytes : imageBytes as Uint8List?, identical(imageBytes, _unsetImageBytes) ? this.imageBytes : imageBytes as Uint8List?,
cardColor: cardColor ?? this.cardColor, cardColor: cardColor ?? this.cardColor,
repriseChildId: repriseChildId ?? this.repriseChildId, repriseChildId: repriseChildId ?? this.repriseChildId,
existingPhotoUrl: existingPhotoUrl ?? this.existingPhotoUrl, existingPhotoUrl: identical(existingPhotoUrl, _unsetExistingPhotoUrl)
? this.existingPhotoUrl
: existingPhotoUrl as String?,
); );
} }
} }

View File

@ -166,7 +166,11 @@ class _ParentRegisterStep3ScreenState extends State<ParentRegisterStep3Screen> {
if (await f.exists()) file = f; if (await f.exists()) file = f;
} catch (_) {} } catch (_) {}
} }
final updatedChild = oldChild.copyWith(imageBytes: bytes, imageFile: file); final updatedChild = oldChild.copyWith(
imageBytes: bytes,
imageFile: file,
existingPhotoUrl: null,
);
registrationData.updateChild(childIndex, updatedChild); registrationData.updateChild(childIndex, updatedChild);
} }
} }
@ -306,7 +310,7 @@ class _ParentRegisterStep3ScreenState extends State<ParentRegisterStep3Screen> {
onClearImage: () => setState(() { onClearImage: () => setState(() {
final c = registrationData.children[index]; final c = registrationData.children[index];
registrationData.updateChild( registrationData.updateChild(
index, c.copyWith(imageFile: null, imageBytes: null)); index, c.copyWith(imageFile: null, imageBytes: null, existingPhotoUrl: null));
}), }),
onDateSelect: () => _selectDate(context, index, registrationData), onDateSelect: () => _selectDate(context, index, registrationData),
onFirstNameChanged: (value) => setState(() { onFirstNameChanged: (value) => setState(() {
@ -414,7 +418,7 @@ class _ParentRegisterStep3ScreenState extends State<ParentRegisterStep3Screen> {
onClearImage: () => setState(() { onClearImage: () => setState(() {
final c = registrationData.children[index]; final c = registrationData.children[index];
registrationData.updateChild( registrationData.updateChild(
index, c.copyWith(imageFile: null, imageBytes: null)); index, c.copyWith(imageFile: null, imageBytes: null, existingPhotoUrl: null));
}), }),
onDateSelect: () => _selectDate(context, index, registrationData), onDateSelect: () => _selectDate(context, index, registrationData),
onFirstNameChanged: (value) => setState(() { onFirstNameChanged: (value) => setState(() {

View File

@ -37,12 +37,15 @@ class RepriseMapper {
? isoToDdMmYyyy(e.dueDate) ? isoToDdMmYyyy(e.dueDate)
: isoToDdMmYyyy(e.birthDate); : isoToDdMmYyyy(e.birthDate);
final photo = e.photoUrl?.trim(); final photo = e.photoUrl?.trim();
final hasPhoto = photo != null && photo.isNotEmpty;
return ChildData( return ChildData(
firstName: e.firstName ?? '', firstName: e.firstName ?? '',
lastName: e.lastName ?? '', lastName: e.lastName ?? '',
dob: dob, dob: dob,
genre: e.gender ?? '', genre: e.gender ?? '',
photoConsent: e.consentPhoto, // Inscription initiale exigeait la coche pour envoyer la photo ; le back
// ne persistait pas toujours consent_photo on pré-coche si photo en base.
photoConsent: e.consentPhoto || hasPhoto,
multipleBirth: e.estMultiple, multipleBirth: e.estMultiple,
isUnbornChild: isUnborn, isUnbornChild: isUnborn,
cardColor: _childCardColors[index % _childCardColors.length], cardColor: _childCardColors[index % _childCardColors.length],

View File

@ -1,4 +1,5 @@
import 'dart:math' as math; import 'dart:math' as math;
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
@ -20,7 +21,7 @@ bool _hasChildPhoto(ChildData c) {
return registrationPhotoSlotHasImage( return registrationPhotoSlotHasImage(
imageBytes: c.imageBytes, imageBytes: c.imageBytes,
imageFile: c.imageFile, imageFile: c.imageFile,
imagePathOrAsset: null, imagePathOrAsset: c.existingPhotoUrl,
); );
} }
@ -33,6 +34,20 @@ Widget _buildChildPhotoImage(ChildData c, {required BoxFit fit}) {
if (f != null) { if (f != null) {
return kIsWeb ? Image.network(f.path, fit: fit) : Image.file(f, fit: fit); return kIsWeb ? Image.network(f.path, fit: fit) : Image.file(f, fit: fit);
} }
final url = c.existingPhotoUrl?.trim();
if (url != null && url.isNotEmpty) {
if (url.startsWith('http://') || url.startsWith('https://')) {
return Image.network(url, fit: fit);
}
if (!kIsWeb) {
try {
final file = File(url);
if (file.existsSync()) {
return Image.file(file, fit: fit);
}
} catch (_) {}
}
}
return Image.asset('assets/images/photo.png', fit: BoxFit.contain); return Image.asset('assets/images/photo.png', fit: BoxFit.contain);
} }
@ -221,6 +236,7 @@ class _ChildCardWidgetState extends State<ChildCardWidget> {
scaleFactor: scaleFactor, scaleFactor: scaleFactor,
imageBytes: widget.childData.imageBytes, imageBytes: widget.childData.imageBytes,
imageFile: widget.childData.imageFile, imageFile: widget.childData.imageFile,
imagePathOrAsset: widget.childData.existingPhotoUrl,
onTapPick: !config.isReadonly ? widget.onPickImage : null, onTapPick: !config.isReadonly ? widget.onPickImage : null,
onClear: !config.isReadonly ? widget.onClearImage : null, onClear: !config.isReadonly ? widget.onClearImage : null,
baseShadowColor: baseCardColorForShadow, baseShadowColor: baseCardColorForShadow,

View File

@ -71,6 +71,9 @@ class RegistrationPhotoSlot extends StatelessWidget {
if (p.startsWith('assets/')) { if (p.startsWith('assets/')) {
return Image.asset(p, fit: fit); return Image.asset(p, fit: fit);
} }
if (p.startsWith('http://') || p.startsWith('https://')) {
return Image.network(p, fit: fit);
}
if (!kIsWeb) { if (!kIsWeb) {
try { try {
final file = File(p); final file = File(p);