feat(am): inscription photo (picker, bytes web, photo_filename) (#120)
- ProfessionalInfoFormScreen : ImagePicker par défaut, MemoryImage, consentement si photo réelle - AmRegistrationData : photoBytes + photoFilename ; récap étape 4 - registerAM : base64 + filename, erreurs réseau/JSON comme parents - docs: 23_LISTE-TICKETS — issue #120 Made-with: Cursor
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'dart:math' as math;
|
||||
import 'dart:io';
|
||||
@@ -19,6 +21,8 @@ import 'custom_navigation_button.dart';
|
||||
class ProfessionalInfoData {
|
||||
final String? photoPath;
|
||||
final File? photoFile;
|
||||
final Uint8List? photoBytes;
|
||||
final String? photoFilename;
|
||||
final bool photoConsent;
|
||||
final DateTime? dateOfBirth;
|
||||
final String birthCity;
|
||||
@@ -30,6 +34,8 @@ class ProfessionalInfoData {
|
||||
ProfessionalInfoData({
|
||||
this.photoPath,
|
||||
this.photoFile,
|
||||
this.photoBytes,
|
||||
this.photoFilename,
|
||||
this.photoConsent = false,
|
||||
this.dateOfBirth,
|
||||
this.birthCity = '',
|
||||
@@ -86,8 +92,17 @@ class _ProfessionalInfoFormScreenState extends State<ProfessionalInfoFormScreen>
|
||||
DateTime? _selectedDate;
|
||||
String? _photoPathFramework;
|
||||
File? _photoFile;
|
||||
Uint8List? _photoBytes;
|
||||
String? _photoFilename;
|
||||
bool _photoConsent = false;
|
||||
|
||||
bool get _hasRealPhoto =>
|
||||
(_photoBytes != null && _photoBytes!.isNotEmpty) ||
|
||||
_photoFile != null ||
|
||||
(_photoPathFramework != null &&
|
||||
_photoPathFramework!.isNotEmpty &&
|
||||
!_photoPathFramework!.startsWith('assets/'));
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -106,6 +121,8 @@ class _ProfessionalInfoFormScreenState extends State<ProfessionalInfoFormScreen>
|
||||
_capacityController.text = data.capacity?.toString() ?? '';
|
||||
_photoPathFramework = data.photoPath;
|
||||
_photoFile = data.photoFile;
|
||||
_photoBytes = data.photoBytes;
|
||||
_photoFilename = data.photoFilename;
|
||||
_photoConsent = data.photoConsent;
|
||||
}
|
||||
}
|
||||
@@ -140,18 +157,42 @@ class _ProfessionalInfoFormScreenState extends State<ProfessionalInfoFormScreen>
|
||||
Future<void> _pickPhoto() async {
|
||||
if (widget.onPickPhoto != null) {
|
||||
await widget.onPickPhoto!();
|
||||
} else {
|
||||
// Comportement par défaut : utiliser un asset de test
|
||||
setState(() {
|
||||
_photoPathFramework = 'assets/images/icon_assmat.png';
|
||||
_photoFile = null;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
final picker = ImagePicker();
|
||||
try {
|
||||
final XFile? picked = await picker.pickImage(
|
||||
source: ImageSource.gallery,
|
||||
imageQuality: 75,
|
||||
maxWidth: 1200,
|
||||
maxHeight: 1200,
|
||||
);
|
||||
if (picked == null) return;
|
||||
final bytes = await picked.readAsBytes();
|
||||
if (bytes.isEmpty) return;
|
||||
|
||||
File? file;
|
||||
if (!kIsWeb) {
|
||||
try {
|
||||
final f = File(picked.path);
|
||||
if (await f.exists()) file = f;
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
final name = picked.name.trim();
|
||||
setState(() {
|
||||
_photoBytes = bytes;
|
||||
_photoFile = file;
|
||||
_photoPathFramework = null;
|
||||
_photoFilename = name.isNotEmpty ? name : 'photo_am.jpg';
|
||||
});
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
void _submitForm() {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
if (_photoPathFramework != null && !_photoConsent) {
|
||||
if (_hasRealPhoto && !_photoConsent) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Veuillez accepter le consentement photo pour continuer.')),
|
||||
);
|
||||
@@ -159,8 +200,10 @@ class _ProfessionalInfoFormScreenState extends State<ProfessionalInfoFormScreen>
|
||||
}
|
||||
|
||||
final data = ProfessionalInfoData(
|
||||
photoPath: _photoPathFramework,
|
||||
photoPath: _photoBytes != null ? null : _photoPathFramework,
|
||||
photoFile: _photoFile,
|
||||
photoBytes: _photoBytes,
|
||||
photoFilename: _photoFilename,
|
||||
photoConsent: _photoConsent,
|
||||
dateOfBirth: _selectedDate,
|
||||
birthCity: _birthCityController.text,
|
||||
@@ -169,7 +212,7 @@ class _ProfessionalInfoFormScreenState extends State<ProfessionalInfoFormScreen>
|
||||
agrementNumber: _agrementController.text,
|
||||
capacity: int.tryParse(_capacityController.text),
|
||||
);
|
||||
|
||||
|
||||
widget.onSubmit(data);
|
||||
}
|
||||
}
|
||||
@@ -461,11 +504,14 @@ class _ProfessionalInfoFormScreenState extends State<ProfessionalInfoFormScreen>
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
child: _photoFile != null
|
||||
? Image.file(_photoFile!, fit: BoxFit.cover)
|
||||
: (_photoPathFramework != null && _photoPathFramework!.startsWith('assets/')
|
||||
? Image.asset(_photoPathFramework!, fit: BoxFit.contain)
|
||||
: Image.asset('assets/images/photo.png', fit: BoxFit.contain)),
|
||||
child: _photoBytes != null && _photoBytes!.isNotEmpty
|
||||
? Image.memory(_photoBytes!, fit: BoxFit.cover)
|
||||
: _photoFile != null
|
||||
? Image.file(_photoFile!, fit: BoxFit.cover)
|
||||
: (_photoPathFramework != null &&
|
||||
_photoPathFramework!.startsWith('assets/')
|
||||
? Image.asset(_photoPathFramework!, fit: BoxFit.contain)
|
||||
: Image.asset('assets/images/photo.png', fit: BoxFit.contain)),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -741,7 +787,9 @@ class _ProfessionalInfoFormScreenState extends State<ProfessionalInfoFormScreen>
|
||||
final Color hoverPhotoShadow = baseCardColorForShadow.withAlpha(130);
|
||||
|
||||
ImageProvider? currentImageProvider;
|
||||
if (_photoFile != null) {
|
||||
if (_photoBytes != null && _photoBytes!.isNotEmpty) {
|
||||
currentImageProvider = MemoryImage(_photoBytes!);
|
||||
} else if (_photoFile != null) {
|
||||
currentImageProvider = FileImage(_photoFile!);
|
||||
} else if (_photoPathFramework != null && _photoPathFramework!.startsWith('assets/')) {
|
||||
currentImageProvider = AssetImage(_photoPathFramework!);
|
||||
@@ -777,7 +825,7 @@ class _ProfessionalInfoFormScreenState extends State<ProfessionalInfoFormScreen>
|
||||
AppCustomCheckbox(
|
||||
label: 'J\'accepte l\'utilisation\nde ma photo.',
|
||||
value: _photoConsent,
|
||||
onChanged: (val) => setState(() => _photoConsent = val ?? false),
|
||||
onChanged: (val) => setState(() => _photoConsent = val == true),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user