Files
fin-tracker/app/lib/features/login/login_page.dart
T
Dmitry a559d6de3e feat(app): смена сервера API и очистка данных устройства при выходе
Адрес бэкенда хранится в shared_preferences и читается до runApp; switchApiBaseUrl сбрасывает токены и кэш старого сервера. Выход стирает refresh-токен и sqlite-кэш ответов. Экран настроек переделан под разделы.
2026-09-19 22:14:14 +03:00

128 lines
4.5 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../core/auth/auth_controller.dart';
import '../../core/config.dart';
import '../settings/api_base_url_dialog.dart';
class LoginPage extends ConsumerStatefulWidget {
const LoginPage({super.key});
@override
ConsumerState<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends ConsumerState<LoginPage> {
final _email = TextEditingController();
final _password = TextEditingController();
String? _error;
bool _busy = false;
@override
void dispose() {
_email.dispose();
_password.dispose();
super.dispose();
}
Future<void> _submit() async {
setState(() {
_busy = true;
_error = null;
});
final error = await ref
.read(authControllerProvider.notifier)
.login(_email.text.trim(), _password.text);
if (!mounted) return;
setState(() {
_busy = false;
_error = error;
});
}
@override
Widget build(BuildContext context) {
final status = ref.watch(authControllerProvider).status;
return Scaffold(
body: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 360),
child: Padding(
padding: const EdgeInsets.all(24),
child: status == AuthStatus.unknown
? const CircularProgressIndicator()
: AutofillGroup(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
'fin-tracker',
style: Theme.of(context).textTheme.headlineMedium,
),
const SizedBox(height: 4),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Flexible(
child: Text(
ref.watch(apiBaseUrlProvider),
style: Theme.of(context).textTheme.bodySmall,
overflow: TextOverflow.ellipsis,
),
),
IconButton(
icon: const Icon(Icons.edit_outlined, size: 16),
tooltip: 'Изменить адрес',
onPressed: () => showApiBaseUrlDialog(context),
),
],
),
const SizedBox(height: 24),
TextField(
controller: _email,
autofillHints: const [AutofillHints.username],
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(labelText: 'Email'),
),
const SizedBox(height: 12),
TextField(
controller: _password,
autofillHints: const [AutofillHints.password],
obscureText: true,
onSubmitted: (_) => _busy ? null : _submit(),
decoration: const InputDecoration(
labelText: 'Пароль',
),
),
if (_error != null) ...[
const SizedBox(height: 12),
Text(
_error!,
style: TextStyle(
color: Theme.of(context).colorScheme.error,
),
),
],
const SizedBox(height: 24),
FilledButton(
onPressed: _busy ? null : _submit,
child: _busy
? const SizedBox.square(
dimension: 18,
child: CircularProgressIndicator(
strokeWidth: 2,
),
)
: const Text('Войти'),
),
],
),
),
),
),
),
);
}
}