Files
CheckTheethKids/lib/consultorios/clinic.dart
Carlos Correia 89b27e5e86 CTK 1.1.0
2026-07-22 01:34:26 +01:00

104 lines
3.3 KiB
Dart

import '../strings/consultorios_strings.dart';
/// Um consultório dentário devolvido pela Overpass API. Os campos de texto
/// nunca são nulos — qualquer tag em falta no OpenStreetMap já é resolvida
/// para um texto de substituição aqui — mas [hasPhone]/[hasOpeningHours]
/// dizem se esse texto é um valor real ou só o substituto, para os widgets
/// decidirem o que é "acionável" (ex.: só ligar quando há telefone real).
class Clinic {
const Clinic({
required this.id,
required this.name,
required this.lat,
required this.lon,
required this.address,
required this.phone,
required this.hasPhone,
required this.openingHours,
required this.hasOpeningHours,
this.distanceKm,
});
final String id;
final String name;
final double lat;
final double lon;
final String address;
final String phone;
final bool hasPhone;
final String openingHours;
final bool hasOpeningHours;
/// Distância (km) até à morada guardada — só preenchida depois de
/// [OverpassService.fetchNearbyClinics] a calcular; `null` até lá.
final double? distanceKm;
Clinic copyWithDistance(double distanceKm) {
return Clinic(
id: id,
name: name,
lat: lat,
lon: lon,
address: address,
phone: phone,
hasPhone: hasPhone,
openingHours: openingHours,
hasOpeningHours: hasOpeningHours,
distanceKm: distanceKm,
);
}
/// Constrói a partir de um elemento da resposta da Overpass API (`node`
/// ou `way`, com `out center tags;` para que `way` também tenha
/// coordenadas via `element['center']`).
factory Clinic.fromOverpassElement(Map<String, dynamic> element) {
final tags = (element['tags'] as Map?)?.cast<String, dynamic>() ?? const {};
final double lat;
final double lon;
if (element['type'] == 'node') {
lat = (element['lat'] as num).toDouble();
lon = (element['lon'] as num).toDouble();
} else {
final center = (element['center'] as Map?)?.cast<String, dynamic>() ?? const {};
lat = ((center['lat'] as num?) ?? 0).toDouble();
lon = ((center['lon'] as num?) ?? 0).toDouble();
}
final name = (tags['name'] as String?)?.trim();
final addressParts = [
[
tags['addr:street'],
tags['addr:housenumber'],
].whereType<String>().join(', '),
tags['addr:postcode'],
(tags['addr:city'] ?? tags['addr:suburb']),
].whereType<String>().where((s) => s.trim().isNotEmpty).toList();
final phone = (tags['phone'] as String?)?.trim() ??
(tags['contact:phone'] as String?)?.trim();
final openingHours = (tags['opening_hours'] as String?)?.trim();
final hasPhone = phone != null && phone.isNotEmpty;
final hasOpeningHours = openingHours != null && openingHours.isNotEmpty;
return Clinic(
id: '${element['type']}/${element['id']}',
name: (name != null && name.isNotEmpty)
? name
: ConsultoriosStrings.unnamedClinic,
lat: lat,
lon: lon,
address: addressParts.isNotEmpty
? addressParts.join('')
: ConsultoriosStrings.noAddress,
phone: hasPhone ? phone : ConsultoriosStrings.noPhone,
hasPhone: hasPhone,
openingHours: hasOpeningHours
? openingHours
: ConsultoriosStrings.noOpeningHours,
hasOpeningHours: hasOpeningHours,
);
}
}