// lib/remote/remote_models.dart import 'url_utils.dart'; class RemotePhotoItem { final String id; final String name; final String path; final String? thub1, thub2; final String? mimeType; final int? width, height, sizeBytes; final int? rotation; /// EXIF/metadata final DateTime? takenAtUtc; final String? dataExifLegacy; /// GPS + location final double? lat, lng, alt; final RemoteLocation? location; /// extra final String? user; final String? cartella; // utile come folder final String? fastHash; // utile per link locale <-> remoto final int? durationMillis; /// Timestamp server-side per sync robusta final DateTime? createdAtUtc; final DateTime? updatedAtUtc; final DateTime? deletedAtUtc; // soft delete /// timestamp file ms dal server per dateModifiedMillis DB final int? mtimeMs; RemotePhotoItem({ required this.id, required this.name, required this.path, this.thub1, this.thub2, this.mimeType, this.width, this.height, this.rotation, this.sizeBytes, this.takenAtUtc, this.dataExifLegacy, this.lat, this.lng, this.alt, this.user, this.cartella, this.fastHash, this.durationMillis, this.location, this.createdAtUtc, this.updatedAtUtc, this.deletedAtUtc, this.mtimeMs, }); /// Costruzione URL assoluto delegata a utility, in base alle impostazioni. String absoluteUrl(String baseUrl) => buildAbsoluteUri(baseUrl, path).toString(); static DateTime? _tryParseIsoUtc(dynamic v) { if (v == null) return null; try { return DateTime.parse(v.toString()).toUtc(); } catch (_) { return null; } } static double? _toDouble(dynamic v) { if (v == null) return null; if (v is num) return v.toDouble(); return double.tryParse(v.toString()); } /// Converte secondi -> ms se < 1000, altrimenti assume già millisecondi. static int? _toMillis(dynamic v) { if (v == null) return null; final num? n = v is num ? v : num.tryParse(v.toString()); if (n == null) return null; return n >= 1000 ? n.toInt() : (n * 1000).toInt(); } /// mtimeMs nel server può essere float, ad esempio 1775724485418.3494. static int? _toIntMs(dynamic v) { if (v == null) return null; final num? n = v is num ? v : num.tryParse(v.toString()); if (n == null) return null; return n.round(); } /// Soft delete? bool get isSoftDeleted => deletedAtUtc != null; factory RemotePhotoItem.fromJson(Map j) { final gps = j['gps'] is Map ? j['gps'] as Map : null; final loc = j['location'] is Map ? RemoteLocation.fromJson(j['location'] as Map) : null; return RemotePhotoItem( id: (j['id'] ?? j['name']).toString(), name: (j['name'] ?? '').toString(), path: (j['path'] ?? '').toString(), thub1: j['thub1']?.toString(), thub2: j['thub2']?.toString(), mimeType: j['mime_type']?.toString(), width: (j['width'] as num?)?.toInt(), height: (j['height'] as num?)?.toInt(), rotation: (j['rotation'] as num?)?.toInt(), sizeBytes: (j['size_bytes'] as num?)?.toInt(), takenAtUtc: _tryParseIsoUtc(j['taken_at']), dataExifLegacy: j['data']?.toString(), lat: gps != null ? _toDouble(gps['lat']) : null, lng: gps != null ? _toDouble(gps['lng']) : null, alt: gps != null ? _toDouble(gps['alt']) : null, user: j['user']?.toString(), cartella: j['cartella']?.toString(), fastHash: j['fast_hash']?.toString(), // durata: dal server è duration_ms durationMillis: _toMillis(j['duration_ms']), location: loc, // campi sync createdAtUtc: _tryParseIsoUtc(j['created_at']), updatedAtUtc: _tryParseIsoUtc(j['updated_at']), deletedAtUtc: _tryParseIsoUtc(j['deleted_at']), // per DB dateModifiedMillis mtimeMs: _toIntMs(j['mtimeMs']), ); } } class RemoteLocation { final String? continent; /// Country / Paese /// /// Esempio: /// country = Ireland /// countryCode = IE final String? country; final String? countryCode; /// County / contea /// /// Esempio: /// county = County Dublin /// countyCode = eventuale codice se il server/provider lo fornisce final String? county; final String? countyCode; /// Regione/stato amministrativo final String? region; final String? postcode; final String? city; final String? address; final String? timezone; final String? timeOffset; RemoteLocation({ this.continent, this.country, this.countryCode, this.county, this.countyCode, this.region, this.postcode, this.city, this.address, this.timezone, this.timeOffset, }); factory RemoteLocation.fromJson(Map j) => RemoteLocation( continent: j['continent']?.toString(), // Country / Paese. // Supporta sia snake_case dal server JS sia camelCase futuro. country: j['country']?.toString(), countryCode: (j['country_code'] ?? j['countryCode'])?.toString(), // County / contea. county: j['county']?.toString(), countyCode: (j['county_code'] ?? j['countyCode'])?.toString(), region: j['region']?.toString(), postcode: j['postcode']?.toString(), city: j['city']?.toString(), address: j['address']?.toString(), timezone: j['timezone']?.toString(), timeOffset: j['time']?.toString(), ); }