Адрес бэкенда хранится в shared_preferences и читается до runApp; switchApiBaseUrl сбрасывает токены и кэш старого сервера. Выход стирает refresh-токен и sqlite-кэш ответов. Экран настроек переделан под разделы.
59 lines
2.4 KiB
Dart
59 lines
2.4 KiB
Dart
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
const _prefsKey = 'api_base_url';
|
|
|
|
/// Backend origin when the user has not chosen one. Override at build time:
|
|
/// `flutter run --dart-define=API_BASE_URL=https://fin.example.com`.
|
|
/// On web the default is the page's own origin (Caddy serves app and API together).
|
|
String defaultApiBaseUrl() {
|
|
const fromEnv = String.fromEnvironment('API_BASE_URL');
|
|
if (fromEnv.isNotEmpty) return fromEnv;
|
|
if (kIsWeb) return Uri.base.origin;
|
|
return 'http://127.0.0.1:8000';
|
|
}
|
|
|
|
/// The address saved on this device, read in `main()` before `runApp`. Loading it up front,
|
|
/// rather than lazily like the theme, matters: the session restore fires a request on
|
|
/// startup and it must already go to the right server.
|
|
Future<String?> loadStoredApiBaseUrl() async =>
|
|
(await SharedPreferences.getInstance()).getString(_prefsKey);
|
|
|
|
/// Overridden in `main()` with [loadStoredApiBaseUrl]; null (tests, nothing saved) means default.
|
|
final initialApiBaseUrlProvider = Provider<String?>((_) => null);
|
|
|
|
/// Backend origin every client is built against. Watched by `apiProvider` and
|
|
/// `authDioProvider`, so changing it rebuilds them. To switch servers use
|
|
/// `switchApiBaseUrl`: tokens and cached data belong to the old one.
|
|
final apiBaseUrlProvider = NotifierProvider<ApiBaseUrlController, String>(
|
|
ApiBaseUrlController.new,
|
|
);
|
|
|
|
class ApiBaseUrlController extends Notifier<String> {
|
|
@override
|
|
String build() => ref.watch(initialApiBaseUrlProvider) ?? defaultApiBaseUrl();
|
|
|
|
/// [input] as an origin-like URL without a trailing slash, or null if it is not an
|
|
/// absolute http(s) URL.
|
|
static String? normalize(String input) {
|
|
final uri = Uri.tryParse(input.trim());
|
|
if (uri == null || !uri.hasAuthority || uri.host.isEmpty) return null;
|
|
if (uri.scheme != 'http' && uri.scheme != 'https') return null;
|
|
final s = uri.toString();
|
|
return s.endsWith('/') ? s.substring(0, s.length - 1) : s;
|
|
}
|
|
|
|
/// Persists [url]; null goes back to [defaultApiBaseUrl].
|
|
Future<void> save(String? url) async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
if (url == null) {
|
|
await prefs.remove(_prefsKey);
|
|
state = defaultApiBaseUrl();
|
|
} else {
|
|
await prefs.setString(_prefsKey, url);
|
|
state = url;
|
|
}
|
|
}
|
|
}
|