3cf874ab69
AuthController is an AsyncNotifier holding the currently-logged-in User. server_url, session_token, current_user all live in flutter_secure_storage. ServerUrlScreen probes /healthz before saving the URL so we don't store a bogus base.
82 lines
2.7 KiB
Dart
82 lines
2.7 KiB
Dart
import 'package:dio/dio.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
import '../api/endpoints/health.dart';
|
|
import '../theme/theme_extension.dart';
|
|
import 'auth_provider.dart';
|
|
|
|
class ServerUrlScreen extends ConsumerStatefulWidget {
|
|
const ServerUrlScreen({super.key});
|
|
|
|
@override
|
|
ConsumerState<ServerUrlScreen> createState() => _ServerUrlScreenState();
|
|
}
|
|
|
|
class _ServerUrlScreenState extends ConsumerState<ServerUrlScreen> {
|
|
final _ctrl = TextEditingController();
|
|
bool _busy = false;
|
|
String? _error;
|
|
|
|
Future<void> _connect() async {
|
|
final url = _ctrl.text.trim();
|
|
if (url.isEmpty) {
|
|
setState(() => _error = 'Enter a server URL.');
|
|
return;
|
|
}
|
|
setState(() {
|
|
_busy = true;
|
|
_error = null;
|
|
});
|
|
try {
|
|
final dio = Dio(BaseOptions(baseUrl: url, connectTimeout: const Duration(seconds: 5)));
|
|
final body = await HealthApi(dio).check();
|
|
if (body['status'] != 'ok') {
|
|
throw StateError('unhealthy');
|
|
}
|
|
await ref.read(authControllerProvider.notifier).setServerUrl(url);
|
|
if (!mounted) return;
|
|
Navigator.of(context).pushReplacementNamed('/login');
|
|
} on DioException catch (_) {
|
|
setState(() => _error = "Couldn't reach that server.");
|
|
} catch (_) {
|
|
setState(() => _error = "Couldn't reach that server.");
|
|
} finally {
|
|
if (mounted) setState(() => _busy = false);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final fs = Theme.of(context).extension<FabledSwordTheme>()!;
|
|
return Scaffold(
|
|
body: SafeArea(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(24),
|
|
child: Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
|
|
const SizedBox(height: 48),
|
|
Text('Connect to your Minstrel', style: TextStyle(fontFamily: fs.display.fontFamily, fontSize: 28)),
|
|
const SizedBox(height: 24),
|
|
TextField(
|
|
controller: _ctrl,
|
|
keyboardType: TextInputType.url,
|
|
decoration: const InputDecoration(labelText: 'Server URL', hintText: 'https://music.example.com'),
|
|
),
|
|
if (_error != null) Padding(
|
|
padding: const EdgeInsets.only(top: 8),
|
|
child: Text(_error!, style: TextStyle(color: fs.error)),
|
|
),
|
|
const SizedBox(height: 16),
|
|
FilledButton(
|
|
onPressed: _busy ? null : _connect,
|
|
child: _busy
|
|
? const SizedBox(width: 18, height: 18, child: CircularProgressIndicator(strokeWidth: 2))
|
|
: const Text('Connect'),
|
|
),
|
|
]),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|