// lib/utils/asset_server.dart import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:flutter/services.dart' show rootBundle; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; class AssetServer { AssetServer._internal(); static final AssetServer _instance = AssetServer._internal(); factory AssetServer() => _instance; HttpServer? _server; int? _port; bool get running => _server != null; Future start() async { if (_server != null && _port != null) { return Uri.parse('http://127.0.0.1:$_port'); } // Bind to ephemeral port _server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0); _port = _server!.port; _server!.listen((HttpRequest request) async { try { final path = request.uri.path.isEmpty ? '/map.html' : request.uri.path; debugPrint('[AssetServer] -> Request: $path'); // map to assets/map/ final assetPath = 'assets/map$path'; // simple content-type detection final contentType = _contentTypeFromPath(path); if (assetPath.endsWith('.html') || assetPath.endsWith('.htm')) { // For HTML, we prefer to read as text final html = await rootBundle.loadString('assets/map/map.html'); request.response.headers.contentType = ContentType('text', 'html', charset: 'utf-8'); request.response.add(utf8.encode(html)); await request.response.close(); return; } // Other assets as bytes try { final bd = await rootBundle.load(assetPath); final bytes = bd.buffer.asUint8List(); if (contentType != null) request.response.headers.contentType = contentType; request.response.add(bytes); await request.response.close(); } catch (e) { debugPrint('[AssetServer] asset not found: $assetPath -> $e'); request.response.statusCode = HttpStatus.notFound; request.response.write('Not Found'); await request.response.close(); } } catch (e) { debugPrint('[AssetServer] internal error: $e'); try { request.response.statusCode = HttpStatus.internalServerError; request.response.write('Internal Server Error'); await request.response.close(); } catch (_) {} } }); debugPrint('[AssetServer] started at http://127.0.0.1:$_port'); return Uri.parse('http://127.0.0.1:$_port'); } Future stop() async { try { await _server?.close(force: true); _server = null; _port = null; debugPrint('[AssetServer] stopped'); } catch (e) { debugPrint('[AssetServer] stop error: $e'); } } ContentType? _contentTypeFromPath(String path) { final lower = path.toLowerCase(); if (lower.endsWith('.js')) return ContentType('application', 'javascript', charset: 'utf-8'); if (lower.endsWith('.css')) return ContentType('text', 'css', charset: 'utf-8'); if (lower.endsWith('.html') || lower.endsWith('.htm')) return ContentType('text', 'html', charset: 'utf-8'); if (lower.endsWith('.json')) return ContentType('application', 'json', charset: 'utf-8'); if (lower.endsWith('.png')) return ContentType('image', 'png'); if (lower.endsWith('.jpg') || lower.endsWith('.jpeg')) return ContentType('image', 'jpeg'); if (lower.endsWith('.svg')) return ContentType('image', 'svg+xml'); if (lower.endsWith('.gif')) return ContentType('image', 'gif'); if (lower.endsWith('.webp')) return ContentType('image', 'webp'); return ContentType('application', 'octet-stream'); } }