// Author: fruit.com.cn import 'package:flutter/foundation.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'app_config.dart'; /// Debug-only runtime server endpoint persisted outside build-time defines. abstract final class ServerEndpoint { /// Preference key used for the runtime server address. static const String preferenceKey = 'dev.api_base_url'; /// Returns a normalized http(s) URL, or null for invalid input. static String? normalize(String? raw) { final value = raw?.trim() ?? ''; final uri = Uri.tryParse(value); if (uri == null || (uri.scheme != 'http' && uri.scheme != 'https') || uri.host.isEmpty) { return null; } return value.replaceFirst(RegExp(r'/+$'), ''); } /// 运行时手改优先,其次构建期默认地址;两者都必须先通过规范校验。 static String resolve({required String? stored, required String fallback}) { return normalize(stored) ?? normalize(fallback) ?? fallback; } /// Loads the persisted override before the app creates its ApiClient. /// /// In release builds this returns immediately without reading preferences. static Future load({ bool debug = kDebugMode, SharedPreferencesAsync? preferences, }) async { if (!debug) return; try { final prefs = preferences ?? SharedPreferencesAsync(); final stored = await prefs.getString(preferenceKey); AppConfig.applyRuntimeBaseUrl( resolve(stored: stored, fallback: AppConfig.baseUrl), ); } catch (_) { AppConfig.clearRuntimeBaseUrl(); } } /// Persists a normalized runtime endpoint. static Future save( String raw, { SharedPreferencesAsync? preferences, }) async { final normalized = normalize(raw); if (normalized == null) return false; try { await (preferences ?? SharedPreferencesAsync()).setString( preferenceKey, normalized, ); AppConfig.applyRuntimeBaseUrl(normalized); return true; } catch (_) { return false; } } /// Removes the persisted runtime endpoint. static Future clear({SharedPreferencesAsync? preferences}) async { try { await (preferences ?? SharedPreferencesAsync()).remove(preferenceKey); } catch (_) { // Clearing an optional developer override is best effort. } } }