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:
parent
d69768806c
commit
62b2466cce
@ -101,6 +101,7 @@
|
|||||||
| 116 | Rattachement parent – frontend | Ouvert |
|
| 116 | Rattachement parent – frontend | Ouvert |
|
||||||
| 117 | Évolution du cahier des charges | Ouvert |
|
| 117 | Évolution du cahier des charges | Ouvert |
|
||||||
| 119 | Endpoint unifié GET /dossiers/:numeroDossier (AM ou famille) | ✅ Fermé |
|
| 119 | Endpoint unifié GET /dossiers/:numeroDossier (AM ou famille) | ✅ Fermé |
|
||||||
|
| 120 | [Full-stack] Inscription AM — photo, API et UX alignés sur les parents (suite #91) | Ouvert |
|
||||||
|
|
||||||
*Gitea #1 et #2 = anciens tickets de test (fermés). Liste complète : https://git.ptits-pas.fr/jmartin/petitspas/issues*
|
*Gitea #1 et #2 = anciens tickets de test (fermés). Liste complète : https://git.ptits-pas.fr/jmartin/petitspas/issues*
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,9 @@ class AmRegistrationData extends ChangeNotifier {
|
|||||||
// bool photoConsent = false; // Déplacé ou géré à l'étape 2
|
// bool photoConsent = false; // Déplacé ou géré à l'étape 2
|
||||||
|
|
||||||
// Step 2: Professional Info
|
// Step 2: Professional Info
|
||||||
String? photoPath; // Ajouté pour l'étape 2
|
String? photoPath; // Chemin fichier local (hors web) si pas d’octets en mémoire
|
||||||
|
Uint8List? photoBytes; // Galerie / web (ImagePicker)
|
||||||
|
String? photoFilename; // Nom pour l’API (ex. image.jpg)
|
||||||
bool photoConsent = false; // Ajouté pour l'étape 2
|
bool photoConsent = false; // Ajouté pour l'étape 2
|
||||||
DateTime? dateOfBirth;
|
DateTime? dateOfBirth;
|
||||||
String birthCity = ''; // Nouveau
|
String birthCity = ''; // Nouveau
|
||||||
@ -57,6 +59,8 @@ class AmRegistrationData extends ChangeNotifier {
|
|||||||
|
|
||||||
void updateProfessionalInfo({
|
void updateProfessionalInfo({
|
||||||
String? photoPath,
|
String? photoPath,
|
||||||
|
Uint8List? photoBytes,
|
||||||
|
String? photoFilename,
|
||||||
bool? photoConsent,
|
bool? photoConsent,
|
||||||
DateTime? dateOfBirth,
|
DateTime? dateOfBirth,
|
||||||
String? birthCity, // Nouveau
|
String? birthCity, // Nouveau
|
||||||
@ -66,10 +70,9 @@ class AmRegistrationData extends ChangeNotifier {
|
|||||||
String? agrementNumber,
|
String? agrementNumber,
|
||||||
int? capacity,
|
int? capacity,
|
||||||
}) {
|
}) {
|
||||||
// Allow setting photoPath to null explicitly
|
this.photoPath = photoPath;
|
||||||
if (photoPath != null || this.photoPath != null) {
|
this.photoBytes = photoBytes;
|
||||||
this.photoPath = photoPath;
|
this.photoFilename = photoFilename;
|
||||||
}
|
|
||||||
this.photoConsent = photoConsent ?? this.photoConsent;
|
this.photoConsent = photoConsent ?? this.photoConsent;
|
||||||
this.dateOfBirth = dateOfBirth ?? this.dateOfBirth;
|
this.dateOfBirth = dateOfBirth ?? this.dateOfBirth;
|
||||||
this.birthCity = birthCity ?? this.birthCity; // Nouveau
|
this.birthCity = birthCity ?? this.birthCity; // Nouveau
|
||||||
@ -101,12 +104,15 @@ class AmRegistrationData extends ChangeNotifier {
|
|||||||
email.isNotEmpty;
|
email.isNotEmpty;
|
||||||
// password n'est pas requis à l'inscription (défini après validation par lien email)
|
// password n'est pas requis à l'inscription (défini après validation par lien email)
|
||||||
|
|
||||||
|
/// Photo réelle (pas seulement un placeholder asset).
|
||||||
|
bool get _hasUserPhoto =>
|
||||||
|
(photoBytes != null && photoBytes!.isNotEmpty) ||
|
||||||
|
(photoPath != null &&
|
||||||
|
photoPath!.isNotEmpty &&
|
||||||
|
!photoPath!.startsWith('assets/'));
|
||||||
|
|
||||||
bool get isStep2Complete =>
|
bool get isStep2Complete =>
|
||||||
// photoConsent is mandatory if a photo is system-required, otherwise optional.
|
(_hasUserPhoto ? photoConsent == true : true) &&
|
||||||
// For now, let's assume if photoPath is present, consent should ideally be true.
|
|
||||||
// Or, make consent always mandatory if photo section exists.
|
|
||||||
// Based on new mockup, photo is present, so consent might be implicitly or explicitly needed.
|
|
||||||
(photoPath != null ? photoConsent == true : true) && // Ajuster selon la logique de consentement désirée
|
|
||||||
dateOfBirth != null &&
|
dateOfBirth != null &&
|
||||||
birthCity.isNotEmpty &&
|
birthCity.isNotEmpty &&
|
||||||
birthCountry.isNotEmpty &&
|
birthCountry.isNotEmpty &&
|
||||||
|
|||||||
@ -6,27 +6,17 @@ import '../../models/am_registration_data.dart';
|
|||||||
import '../../models/card_assets.dart';
|
import '../../models/card_assets.dart';
|
||||||
import '../../widgets/professional_info_form_screen.dart';
|
import '../../widgets/professional_info_form_screen.dart';
|
||||||
|
|
||||||
class AmRegisterStep2Screen extends StatefulWidget {
|
class AmRegisterStep2Screen extends StatelessWidget {
|
||||||
const AmRegisterStep2Screen({super.key});
|
const AmRegisterStep2Screen({super.key});
|
||||||
|
|
||||||
@override
|
|
||||||
State<AmRegisterStep2Screen> createState() => _AmRegisterStep2ScreenState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _AmRegisterStep2ScreenState extends State<AmRegisterStep2Screen> {
|
|
||||||
String? _photoPathFramework;
|
|
||||||
|
|
||||||
Future<void> _pickPhoto() async {
|
|
||||||
// TODO: brancher ImagePicker ; ne pas préremplir de chemin factice
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final registrationData = Provider.of<AmRegistrationData>(context, listen: false);
|
final registrationData = Provider.of<AmRegistrationData>(context, listen: false);
|
||||||
|
|
||||||
// Préparer les données initiales
|
final initialData = ProfessionalInfoData(
|
||||||
ProfessionalInfoData initialData = ProfessionalInfoData(
|
|
||||||
photoPath: registrationData.photoPath,
|
photoPath: registrationData.photoPath,
|
||||||
|
photoBytes: registrationData.photoBytes,
|
||||||
|
photoFilename: registrationData.photoFilename,
|
||||||
photoConsent: registrationData.photoConsent,
|
photoConsent: registrationData.photoConsent,
|
||||||
dateOfBirth: registrationData.dateOfBirth,
|
dateOfBirth: registrationData.dateOfBirth,
|
||||||
birthCity: registrationData.birthCity,
|
birthCity: registrationData.birthCity,
|
||||||
@ -42,10 +32,11 @@ class _AmRegisterStep2ScreenState extends State<AmRegisterStep2Screen> {
|
|||||||
cardColor: CardColorHorizontal.green,
|
cardColor: CardColorHorizontal.green,
|
||||||
initialData: initialData,
|
initialData: initialData,
|
||||||
previousRoute: '/am-register-step1',
|
previousRoute: '/am-register-step1',
|
||||||
onPickPhoto: _pickPhoto,
|
|
||||||
onSubmit: (data) {
|
onSubmit: (data) {
|
||||||
registrationData.updateProfessionalInfo(
|
registrationData.updateProfessionalInfo(
|
||||||
photoPath: _photoPathFramework ?? data.photoPath,
|
photoPath: data.photoPath,
|
||||||
|
photoBytes: data.photoBytes,
|
||||||
|
photoFilename: data.photoFilename,
|
||||||
photoConsent: data.photoConsent,
|
photoConsent: data.photoConsent,
|
||||||
dateOfBirth: data.dateOfBirth,
|
dateOfBirth: data.dateOfBirth,
|
||||||
birthCity: data.birthCity,
|
birthCity: data.birthCity,
|
||||||
@ -58,4 +49,4 @@ class _AmRegisterStep2ScreenState extends State<AmRegisterStep2Screen> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -196,8 +196,9 @@ class _AmRegisterStep4ScreenState extends State<AmRegisterStep4Screen> {
|
|||||||
title: 'Informations professionnelles',
|
title: 'Informations professionnelles',
|
||||||
cardColor: CardColorHorizontal.green,
|
cardColor: CardColorHorizontal.green,
|
||||||
initialData: ProfessionalInfoData(
|
initialData: ProfessionalInfoData(
|
||||||
// TODO: Gérer photoPath vs photoFile correctement
|
photoPath: data.photoPath,
|
||||||
photoPath: null, // Pas d'accès facile au fichier ici, on verra
|
photoBytes: data.photoBytes,
|
||||||
|
photoFilename: data.photoFilename,
|
||||||
dateOfBirth: data.dateOfBirth,
|
dateOfBirth: data.dateOfBirth,
|
||||||
birthCity: data.birthCity,
|
birthCity: data.birthCity,
|
||||||
birthCountry: data.birthCountry,
|
birthCountry: data.birthCountry,
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import '../models/user.dart';
|
import '../models/user.dart';
|
||||||
@ -13,6 +15,13 @@ import '../utils/nir_utils.dart';
|
|||||||
class AuthService {
|
class AuthService {
|
||||||
static const String _currentUserKey = 'current_user';
|
static const String _currentUserKey = 'current_user';
|
||||||
|
|
||||||
|
static String? _basenameFromPath(String path) {
|
||||||
|
final s = path.replaceAll(r'\', '/');
|
||||||
|
final i = s.lastIndexOf('/');
|
||||||
|
final name = i >= 0 ? s.substring(i + 1) : s;
|
||||||
|
return name.isEmpty ? null : name;
|
||||||
|
}
|
||||||
|
|
||||||
/// Connexion de l'utilisateur
|
/// Connexion de l'utilisateur
|
||||||
/// Retourne l'utilisateur connecté avec le flag changement_mdp_obligatoire
|
/// Retourne l'utilisateur connecté avec le flag changement_mdp_obligatoire
|
||||||
static Future<AppUser> login(String email, String password) async {
|
static Future<AppUser> login(String email, String password) async {
|
||||||
@ -142,17 +151,29 @@ class AuthService {
|
|||||||
/// En cas de succès (201), aucune donnée utilisateur retournée ; rediriger vers login.
|
/// En cas de succès (201), aucune donnée utilisateur retournée ; rediriger vers login.
|
||||||
static Future<void> registerAM(AmRegistrationData data) async {
|
static Future<void> registerAM(AmRegistrationData data) async {
|
||||||
String? photoBase64;
|
String? photoBase64;
|
||||||
if (data.photoPath != null && data.photoPath!.isNotEmpty && !data.photoPath!.startsWith('assets/')) {
|
String? photoFilename;
|
||||||
try {
|
|
||||||
final file = File(data.photoPath!);
|
if (data.photoBytes != null && data.photoBytes!.isNotEmpty) {
|
||||||
if (await file.exists()) {
|
photoBase64 = 'data:image/jpeg;base64,${base64Encode(data.photoBytes!)}';
|
||||||
final bytes = await file.readAsBytes();
|
final fn = (data.photoFilename ?? '').trim();
|
||||||
photoBase64 = 'data:image/jpeg;base64,${base64Encode(bytes)}';
|
photoFilename = fn.isNotEmpty ? fn : 'photo_am.jpg';
|
||||||
}
|
} else if (data.photoPath != null &&
|
||||||
} catch (_) {}
|
data.photoPath!.isNotEmpty &&
|
||||||
|
!data.photoPath!.startsWith('assets/')) {
|
||||||
|
if (!kIsWeb) {
|
||||||
|
try {
|
||||||
|
final file = File(data.photoPath!);
|
||||||
|
if (await file.exists()) {
|
||||||
|
final bytes = await file.readAsBytes();
|
||||||
|
photoBase64 = 'data:image/jpeg;base64,${base64Encode(bytes)}';
|
||||||
|
photoFilename =
|
||||||
|
_basenameFromPath(data.photoPath!) ?? 'photo_am.jpg';
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final body = {
|
final body = <String, dynamic>{
|
||||||
'email': data.email,
|
'email': data.email,
|
||||||
'prenom': data.firstName,
|
'prenom': data.firstName,
|
||||||
'nom': data.lastName,
|
'nom': data.lastName,
|
||||||
@ -161,6 +182,7 @@ class AuthService {
|
|||||||
'code_postal': data.postalCode.isNotEmpty ? data.postalCode : null,
|
'code_postal': data.postalCode.isNotEmpty ? data.postalCode : null,
|
||||||
'ville': data.city.isNotEmpty ? data.city : null,
|
'ville': data.city.isNotEmpty ? data.city : null,
|
||||||
if (photoBase64 != null) 'photo_base64': photoBase64,
|
if (photoBase64 != null) 'photo_base64': photoBase64,
|
||||||
|
if (photoBase64 != null) 'photo_filename': photoFilename ?? 'photo_am.jpg',
|
||||||
'consentement_photo': data.photoConsent,
|
'consentement_photo': data.photoConsent,
|
||||||
'date_naissance': data.dateOfBirth != null
|
'date_naissance': data.dateOfBirth != null
|
||||||
? '${data.dateOfBirth!.year}-${data.dateOfBirth!.month.toString().padLeft(2, '0')}-${data.dateOfBirth!.day.toString().padLeft(2, '0')}'
|
? '${data.dateOfBirth!.year}-${data.dateOfBirth!.month.toString().padLeft(2, '0')}-${data.dateOfBirth!.day.toString().padLeft(2, '0')}'
|
||||||
@ -175,11 +197,29 @@ class AuthService {
|
|||||||
'acceptation_privacy': data.cguAccepted,
|
'acceptation_privacy': data.cguAccepted,
|
||||||
};
|
};
|
||||||
|
|
||||||
final response = await http.post(
|
final String encodedBody;
|
||||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.registerAM}'),
|
try {
|
||||||
headers: ApiConfig.headers,
|
encodedBody = jsonEncode(body);
|
||||||
body: jsonEncode(body),
|
} catch (_) {
|
||||||
);
|
throw Exception(
|
||||||
|
'Impossible de préparer l’envoi (données trop volumineuses ou invalides). '
|
||||||
|
'Réessayez avec une photo plus légère.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
late final http.Response response;
|
||||||
|
try {
|
||||||
|
response = await http.post(
|
||||||
|
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.registerAM}'),
|
||||||
|
headers: ApiConfig.headers,
|
||||||
|
body: encodedBody,
|
||||||
|
);
|
||||||
|
} on http.ClientException {
|
||||||
|
throw Exception(
|
||||||
|
'Connexion à ${ApiConfig.baseUrl} impossible. Vérifiez votre réseau, '
|
||||||
|
'un pare-feu ou un bloqueur, puis réessayez.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
|
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
@ -19,6 +21,8 @@ import 'custom_navigation_button.dart';
|
|||||||
class ProfessionalInfoData {
|
class ProfessionalInfoData {
|
||||||
final String? photoPath;
|
final String? photoPath;
|
||||||
final File? photoFile;
|
final File? photoFile;
|
||||||
|
final Uint8List? photoBytes;
|
||||||
|
final String? photoFilename;
|
||||||
final bool photoConsent;
|
final bool photoConsent;
|
||||||
final DateTime? dateOfBirth;
|
final DateTime? dateOfBirth;
|
||||||
final String birthCity;
|
final String birthCity;
|
||||||
@ -30,6 +34,8 @@ class ProfessionalInfoData {
|
|||||||
ProfessionalInfoData({
|
ProfessionalInfoData({
|
||||||
this.photoPath,
|
this.photoPath,
|
||||||
this.photoFile,
|
this.photoFile,
|
||||||
|
this.photoBytes,
|
||||||
|
this.photoFilename,
|
||||||
this.photoConsent = false,
|
this.photoConsent = false,
|
||||||
this.dateOfBirth,
|
this.dateOfBirth,
|
||||||
this.birthCity = '',
|
this.birthCity = '',
|
||||||
@ -86,8 +92,17 @@ class _ProfessionalInfoFormScreenState extends State<ProfessionalInfoFormScreen>
|
|||||||
DateTime? _selectedDate;
|
DateTime? _selectedDate;
|
||||||
String? _photoPathFramework;
|
String? _photoPathFramework;
|
||||||
File? _photoFile;
|
File? _photoFile;
|
||||||
|
Uint8List? _photoBytes;
|
||||||
|
String? _photoFilename;
|
||||||
bool _photoConsent = false;
|
bool _photoConsent = false;
|
||||||
|
|
||||||
|
bool get _hasRealPhoto =>
|
||||||
|
(_photoBytes != null && _photoBytes!.isNotEmpty) ||
|
||||||
|
_photoFile != null ||
|
||||||
|
(_photoPathFramework != null &&
|
||||||
|
_photoPathFramework!.isNotEmpty &&
|
||||||
|
!_photoPathFramework!.startsWith('assets/'));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@ -106,6 +121,8 @@ class _ProfessionalInfoFormScreenState extends State<ProfessionalInfoFormScreen>
|
|||||||
_capacityController.text = data.capacity?.toString() ?? '';
|
_capacityController.text = data.capacity?.toString() ?? '';
|
||||||
_photoPathFramework = data.photoPath;
|
_photoPathFramework = data.photoPath;
|
||||||
_photoFile = data.photoFile;
|
_photoFile = data.photoFile;
|
||||||
|
_photoBytes = data.photoBytes;
|
||||||
|
_photoFilename = data.photoFilename;
|
||||||
_photoConsent = data.photoConsent;
|
_photoConsent = data.photoConsent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -140,18 +157,42 @@ class _ProfessionalInfoFormScreenState extends State<ProfessionalInfoFormScreen>
|
|||||||
Future<void> _pickPhoto() async {
|
Future<void> _pickPhoto() async {
|
||||||
if (widget.onPickPhoto != null) {
|
if (widget.onPickPhoto != null) {
|
||||||
await widget.onPickPhoto!();
|
await widget.onPickPhoto!();
|
||||||
} else {
|
return;
|
||||||
// Comportement par défaut : utiliser un asset de test
|
|
||||||
setState(() {
|
|
||||||
_photoPathFramework = 'assets/images/icon_assmat.png';
|
|
||||||
_photoFile = null;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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() {
|
void _submitForm() {
|
||||||
if (_formKey.currentState!.validate()) {
|
if (_formKey.currentState!.validate()) {
|
||||||
if (_photoPathFramework != null && !_photoConsent) {
|
if (_hasRealPhoto && !_photoConsent) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(content: Text('Veuillez accepter le consentement photo pour continuer.')),
|
const SnackBar(content: Text('Veuillez accepter le consentement photo pour continuer.')),
|
||||||
);
|
);
|
||||||
@ -159,8 +200,10 @@ class _ProfessionalInfoFormScreenState extends State<ProfessionalInfoFormScreen>
|
|||||||
}
|
}
|
||||||
|
|
||||||
final data = ProfessionalInfoData(
|
final data = ProfessionalInfoData(
|
||||||
photoPath: _photoPathFramework,
|
photoPath: _photoBytes != null ? null : _photoPathFramework,
|
||||||
photoFile: _photoFile,
|
photoFile: _photoFile,
|
||||||
|
photoBytes: _photoBytes,
|
||||||
|
photoFilename: _photoFilename,
|
||||||
photoConsent: _photoConsent,
|
photoConsent: _photoConsent,
|
||||||
dateOfBirth: _selectedDate,
|
dateOfBirth: _selectedDate,
|
||||||
birthCity: _birthCityController.text,
|
birthCity: _birthCityController.text,
|
||||||
@ -169,7 +212,7 @@ class _ProfessionalInfoFormScreenState extends State<ProfessionalInfoFormScreen>
|
|||||||
agrementNumber: _agrementController.text,
|
agrementNumber: _agrementController.text,
|
||||||
capacity: int.tryParse(_capacityController.text),
|
capacity: int.tryParse(_capacityController.text),
|
||||||
);
|
);
|
||||||
|
|
||||||
widget.onSubmit(data);
|
widget.onSubmit(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -461,11 +504,14 @@ class _ProfessionalInfoFormScreenState extends State<ProfessionalInfoFormScreen>
|
|||||||
),
|
),
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(18),
|
borderRadius: BorderRadius.circular(18),
|
||||||
child: _photoFile != null
|
child: _photoBytes != null && _photoBytes!.isNotEmpty
|
||||||
? Image.file(_photoFile!, fit: BoxFit.cover)
|
? Image.memory(_photoBytes!, fit: BoxFit.cover)
|
||||||
: (_photoPathFramework != null && _photoPathFramework!.startsWith('assets/')
|
: _photoFile != null
|
||||||
? Image.asset(_photoPathFramework!, fit: BoxFit.contain)
|
? Image.file(_photoFile!, fit: BoxFit.cover)
|
||||||
: Image.asset('assets/images/photo.png', fit: BoxFit.contain)),
|
: (_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);
|
final Color hoverPhotoShadow = baseCardColorForShadow.withAlpha(130);
|
||||||
|
|
||||||
ImageProvider? currentImageProvider;
|
ImageProvider? currentImageProvider;
|
||||||
if (_photoFile != null) {
|
if (_photoBytes != null && _photoBytes!.isNotEmpty) {
|
||||||
|
currentImageProvider = MemoryImage(_photoBytes!);
|
||||||
|
} else if (_photoFile != null) {
|
||||||
currentImageProvider = FileImage(_photoFile!);
|
currentImageProvider = FileImage(_photoFile!);
|
||||||
} else if (_photoPathFramework != null && _photoPathFramework!.startsWith('assets/')) {
|
} else if (_photoPathFramework != null && _photoPathFramework!.startsWith('assets/')) {
|
||||||
currentImageProvider = AssetImage(_photoPathFramework!);
|
currentImageProvider = AssetImage(_photoPathFramework!);
|
||||||
@ -777,7 +825,7 @@ class _ProfessionalInfoFormScreenState extends State<ProfessionalInfoFormScreen>
|
|||||||
AppCustomCheckbox(
|
AppCustomCheckbox(
|
||||||
label: 'J\'accepte l\'utilisation\nde ma photo.',
|
label: 'J\'accepte l\'utilisation\nde ma photo.',
|
||||||
value: _photoConsent,
|
value: _photoConsent,
|
||||||
onChanged: (val) => setState(() => _photoConsent = val ?? false),
|
onChanged: (val) => setState(() => _photoConsent = val == true),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user