// lib/remote/remote_client.dart import 'dart:convert'; import 'package:http/http.dart' as http; import 'remote_models.dart'; import 'auth_client.dart'; class RemoteJsonClient { final Uri indexUri; // es. https://prova.patachina.it/photos/ final RemoteAuth? auth; // opzionale: se presente, aggiunge Bearer RemoteJsonClient( String baseUrl, String indexPath, { this.auth, }) : indexUri = Uri.parse(baseUrl.endsWith('/') ? baseUrl : '$baseUrl/') .resolve(indexPath); Future> fetchAll() async { Map headers = {}; if (auth != null) { headers = await auth!.authHeaders(); } // DEBUG: stampa la URL precisa // ignore: avoid_print print('[remote-client] GET $indexUri'); http.Response res; try { res = await http.get(indexUri, headers: headers).timeout(const Duration(seconds: 20)); } catch (e) { throw Exception('Errore rete su $indexUri: $e'); } // Retry 1 volta in caso di 401 (token scaduto/invalidato) if (res.statusCode == 401 && auth != null) { headers = await auth!.refreshAndHeaders(); res = await http.get(indexUri, headers: headers).timeout(const Duration(seconds: 20)); } // Follow 30x mantenendo Authorization if ({301, 302, 307, 308}.contains(res.statusCode) && res.headers['location'] != null) { final loc = res.headers['location']!; final redirectUri = indexUri.resolve(loc); res = await http.get(redirectUri, headers: headers).timeout(const Duration(seconds: 20)); } if (res.statusCode != 200) { final snippet = utf8.decode(res.bodyBytes.take(200).toList()); throw Exception('HTTP ${res.statusCode} ${res.reasonPhrase} su $indexUri. Body: $snippet'); } final body = utf8.decode(res.bodyBytes); // Qui siamo espliciti: ci aspettiamo SEMPRE una lista top-level final dynamic decoded = json.decode(body); if (decoded is! List) { throw Exception('JSON inatteso: atteso array top-level, ricevuto ${decoded.runtimeType}'); } final List rawList = decoded; // --- DIAGNOSTICA: conteggio pattern dai dati del SERVER (non stampo il JSON intero) int withOriginal = 0, withoutOriginal = 0, leadingSlash = 0, noLeadingSlash = 0; for (final e in rawList) { if (e is Map) { final p = (e['path'] ?? '').toString(); if (p.startsWith('/')) { leadingSlash++; } else { noLeadingSlash++; } if (p.contains('/original/')) { withOriginal++; } else { withoutOriginal++; } } } // ignore: avoid_print print('[remote-client] SERVER paths: withOriginal=$withOriginal ' 'withoutOriginal=$withoutOriginal leadingSlash=$leadingSlash noLeadingSlash=$noLeadingSlash'); // Costruiamo a mano la List, tipizzata esplicitamente final List items = rawList.map((e) { if (e is! Map) { throw Exception('Elemento JSON non รจ una mappa: ${e.runtimeType} -> $e'); } return RemotePhotoItem.fromJson(e); }).toList(); return items; } }