Root cause behind most "stuck loading" reports: the backend changed shape (schedules became an array, location_preferences a string) while the mobile parser still hard-cast to Map/int. The TypeError was swallowed by a silent catch that returned null, and screens only handled the success state, so a parse failure rendered as a permanent spinner. Same class of bug appeared across service lists via non-null map lookups and a total absence of request timeouts. Ported from prosappweb: - in-app suggestions (POST /suggestions) - policies/terms from GET /settings/policies - configurable appointment length (slot_duration_minutes) - block/unblock calendar slots (POST /services/block) - GPS city detection on the profile (Nominatim) - server-side professional search with haversine distance - retry cooldown after a rejected professional application Reliability: - parse schedules array (day_of_week 0=Mon) and string location_preferences - read times as wall clock, so 08:00 stays 08:00 across timezones - carry minutes into hours in TimeOfDay.add; a minute-based step used to loop forever and freeze the calendar (covered by test/time_slots_test.dart) - semver update check instead of string equality, which blocked every build that did not exactly match the configured version - request timeouts across all repositories - surface HTTP >= 400 instead of reporting failed writes as success - error states with retry instead of an indefinite shimmer Includes pre-existing uncommitted work from the UI redesign. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
182 lines
6.1 KiB
Dart
182 lines
6.1 KiB
Dart
import 'package:equatable/equatable.dart';
|
|
import 'package:professional_repository/professional_repository.dart';
|
|
|
|
class ProfessionalEntity extends Equatable {
|
|
final String id;
|
|
final String identification;
|
|
final String address;
|
|
final String aditionalAddress;
|
|
final String profession;
|
|
final bool ratePreferences;
|
|
final String rate;
|
|
final LocationPreferences locationPreferences;
|
|
final String bannerPicture;
|
|
final String identificationPicture;
|
|
final String certificatePicture;
|
|
final double latitude;
|
|
final double longitude;
|
|
final List<String> specializations;
|
|
final List<String> specializationsPictures;
|
|
final Schedules schedules;
|
|
final PaymentMethodEntity paymentMethods;
|
|
final int slotDurationMinutes;
|
|
|
|
const ProfessionalEntity({
|
|
required this.id,
|
|
required this.identification,
|
|
required this.address,
|
|
required this.aditionalAddress,
|
|
required this.profession,
|
|
required this.ratePreferences,
|
|
required this.rate,
|
|
required this.locationPreferences,
|
|
required this.bannerPicture,
|
|
required this.identificationPicture,
|
|
required this.certificatePicture,
|
|
required this.latitude,
|
|
required this.longitude,
|
|
required this.specializations,
|
|
required this.specializationsPictures,
|
|
required this.schedules,
|
|
required this.paymentMethods,
|
|
this.slotDurationMinutes = 120,
|
|
});
|
|
|
|
static ProfessionalEntity fromDocument(Map<String, dynamic> doc) {
|
|
return ProfessionalEntity(
|
|
id: doc['id'] as String,
|
|
identification: doc['identification'] as String,
|
|
address: doc['address'] as String,
|
|
aditionalAddress: doc['aditional_address'] as String,
|
|
profession: doc['profession'] as String,
|
|
ratePreferences: doc['rate_preferences'] as bool,
|
|
rate: doc['rate'] as String,
|
|
locationPreferences: intToEnum(doc['location_preferences'] as int),
|
|
bannerPicture: doc['banner_picture'] as String,
|
|
identificationPicture: doc['identification_picture'] as String,
|
|
certificatePicture: doc['certificate_picture'] as String,
|
|
latitude: double.parse(doc['latitude'].toString()),
|
|
longitude: double.parse(doc['longitude'].toString()),
|
|
specializations: List<String>.from(doc['specializations']),
|
|
specializationsPictures:
|
|
List<String>.from(doc['specializations_pictures']),
|
|
schedules: Schedules.fromDocument(doc['schedules']),
|
|
paymentMethods: PaymentMethodEntity.fromDocument(doc['payment_methods']),
|
|
slotDurationMinutes:
|
|
(doc['slot_duration_minutes'] as num?)?.toInt() ?? 120,
|
|
);
|
|
}
|
|
|
|
ProfessionalEntity copyWith({
|
|
String? id,
|
|
String? identification,
|
|
String? address,
|
|
String? aditionalAddress,
|
|
String? profession,
|
|
bool ratePreferences = false,
|
|
String? rate,
|
|
LocationPreferences? locationPreferences,
|
|
String? bannerPicture,
|
|
String? identificationPicture,
|
|
String? certificatePicture,
|
|
double? latitude,
|
|
double? longitude,
|
|
List<String>? specializations,
|
|
List<String>? specializationsPictures,
|
|
Schedules? schedules,
|
|
PaymentMethodEntity? paymentMethods,
|
|
int? slotDurationMinutes,
|
|
}) {
|
|
return ProfessionalEntity(
|
|
id: id ?? this.id,
|
|
identification: identification ?? this.identification,
|
|
address: address ?? this.address,
|
|
aditionalAddress: aditionalAddress ?? this.aditionalAddress,
|
|
profession: profession ?? this.profession,
|
|
ratePreferences: ratePreferences,
|
|
rate: rate ?? this.rate,
|
|
locationPreferences: locationPreferences ?? this.locationPreferences,
|
|
bannerPicture: bannerPicture ?? this.bannerPicture,
|
|
identificationPicture:
|
|
identificationPicture ?? this.identificationPicture,
|
|
certificatePicture: certificatePicture ?? this.certificatePicture,
|
|
latitude: latitude ?? this.latitude,
|
|
longitude: longitude ?? this.longitude,
|
|
specializations: specializations ?? this.specializations,
|
|
specializationsPictures:
|
|
specializationsPictures ?? this.specializationsPictures,
|
|
schedules: schedules ?? this.schedules,
|
|
paymentMethods: paymentMethods ?? this.paymentMethods,
|
|
slotDurationMinutes: slotDurationMinutes ?? this.slotDurationMinutes,
|
|
);
|
|
}
|
|
|
|
Map<String, dynamic> toDocument() {
|
|
return {
|
|
'id': id,
|
|
'identification': identification,
|
|
'address': address,
|
|
'aditional_address': aditionalAddress,
|
|
'profession': profession,
|
|
'rate_preferences': ratePreferences,
|
|
'rate': rate,
|
|
'location_preferences': enumToInt(locationPreferences),
|
|
'banner_picture': bannerPicture,
|
|
'identification_picture': identificationPicture,
|
|
'certificate_picture': certificatePicture,
|
|
'latitude': latitude,
|
|
'longitude': longitude,
|
|
'specializations': specializations,
|
|
'specializations_pictures': specializationsPictures,
|
|
'schedules': schedules.toJson(),
|
|
'payment_methods': paymentMethods.toDocument(),
|
|
'slot_duration_minutes': slotDurationMinutes,
|
|
};
|
|
}
|
|
|
|
@override
|
|
List<Object?> get props => [
|
|
id,
|
|
identification,
|
|
address,
|
|
aditionalAddress,
|
|
profession,
|
|
ratePreferences,
|
|
rate,
|
|
locationPreferences,
|
|
bannerPicture,
|
|
identificationPicture,
|
|
certificatePicture,
|
|
latitude,
|
|
longitude,
|
|
specializations,
|
|
specializationsPictures,
|
|
schedules,
|
|
paymentMethods,
|
|
slotDurationMinutes,
|
|
];
|
|
|
|
@override
|
|
String toString() {
|
|
return '''ProfessionalEntity{ {
|
|
id: $id,
|
|
identification: $identification,
|
|
address: $address,
|
|
aditional_address: $aditionalAddress,
|
|
profession: $profession,
|
|
rate_preferences: $ratePreferences,
|
|
rate: $rate,
|
|
location_preferences: $locationPreferences,
|
|
bannerPicture: $bannerPicture,
|
|
identificationPicture: $identificationPicture,
|
|
certificatePicture: $certificatePicture,
|
|
latitude: $latitude,
|
|
longitude: $longitude,
|
|
specializations: $specializations,
|
|
specializationsPictures: $specializationsPictures,
|
|
schedule: $schedules,
|
|
paymentMethods: $paymentMethods
|
|
}''';
|
|
}
|
|
}
|