211 lines
6.4 KiB
Dart
211 lines
6.4 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:moonwell_launcher/app/design_system/mw_authentication.dart';
|
|
import 'package:moonwell_launcher/app/design_system/mw_shells.dart';
|
|
import 'package:moonwell_launcher/app/home_screen/home_screen.dart';
|
|
import 'package:moonwell_launcher/features/launcher/data/launcher_api_client.dart';
|
|
import 'package:moonwell_launcher/features/preferences/domain/repositories/preferences_repository.dart';
|
|
import 'package:moonwell_launcher/service_container.dart';
|
|
import 'package:window_manager/window_manager.dart';
|
|
|
|
class LoginScreen extends StatefulWidget {
|
|
const LoginScreen({super.key});
|
|
|
|
@override
|
|
State<LoginScreen> createState() => _LoginScreenState();
|
|
}
|
|
|
|
class _LoginScreenState extends State<LoginScreen> {
|
|
final _usernameController = TextEditingController();
|
|
final _emailController = TextEditingController();
|
|
final _passwordController = TextEditingController();
|
|
final _passwordConfirmationController = TextEditingController();
|
|
final _inviteCodeController = TextEditingController();
|
|
MwAuthenticationMode _mode = MwAuthenticationMode.login;
|
|
bool _termsAccepted = false;
|
|
bool _isLoading = false;
|
|
String? _error;
|
|
String? _success;
|
|
|
|
@override
|
|
void dispose() {
|
|
_usernameController.dispose();
|
|
_emailController.dispose();
|
|
_passwordController.dispose();
|
|
_passwordConfirmationController.dispose();
|
|
_inviteCodeController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<void> _submit() {
|
|
return _mode == MwAuthenticationMode.login ? _login() : _register();
|
|
}
|
|
|
|
Future<void> _login() async {
|
|
final username = _usernameController.text.trim();
|
|
final password = _passwordController.text.trim();
|
|
|
|
if (username.isEmpty || password.isEmpty) {
|
|
setState(() => _error = 'Введите логин и пароль.');
|
|
return;
|
|
}
|
|
|
|
setState(() {
|
|
_isLoading = true;
|
|
_error = null;
|
|
_success = null;
|
|
});
|
|
|
|
try {
|
|
final api = getIt<LauncherApiClient>();
|
|
final preferencesRepository = getIt<PreferencesRepository>();
|
|
final session = await api.login(username: username, password: password);
|
|
final manifest = await api.fetchManifest(session.accessToken);
|
|
await preferencesRepository.setLauncherSession(session);
|
|
|
|
if (!mounted) return;
|
|
|
|
await Navigator.of(context).pushReplacement(
|
|
PageRouteBuilder(
|
|
pageBuilder: (_, _, _) =>
|
|
HomeScreen(session: session, manifest: manifest),
|
|
transitionsBuilder: (_, animation, _, child) {
|
|
return FadeTransition(opacity: animation, child: child);
|
|
},
|
|
transitionDuration: const Duration(milliseconds: 400),
|
|
),
|
|
);
|
|
} catch (e) {
|
|
if (!mounted) return;
|
|
setState(() {
|
|
_isLoading = false;
|
|
_error = _formatError(e);
|
|
});
|
|
}
|
|
}
|
|
|
|
Future<void> _register() async {
|
|
final username = _usernameController.text.trim();
|
|
final email = _emailController.text.trim();
|
|
final password = _passwordController.text;
|
|
final passwordConfirmation = _passwordConfirmationController.text;
|
|
|
|
final validationError = _validateRegistration(
|
|
username: username,
|
|
email: email,
|
|
password: password,
|
|
passwordConfirmation: passwordConfirmation,
|
|
);
|
|
if (validationError != null) {
|
|
setState(() => _error = validationError);
|
|
return;
|
|
}
|
|
|
|
setState(() {
|
|
_isLoading = true;
|
|
_error = null;
|
|
_success = null;
|
|
});
|
|
|
|
try {
|
|
final message = await getIt<LauncherApiClient>().register(
|
|
username: username,
|
|
email: email,
|
|
password: password,
|
|
passwordConfirmation: passwordConfirmation,
|
|
terms: _termsAccepted,
|
|
inviteCode: _inviteCodeController.text,
|
|
);
|
|
|
|
if (!mounted) return;
|
|
setState(() {
|
|
_mode = MwAuthenticationMode.login;
|
|
_isLoading = false;
|
|
_termsAccepted = false;
|
|
_passwordConfirmationController.clear();
|
|
_inviteCodeController.clear();
|
|
_success = message;
|
|
});
|
|
} catch (error) {
|
|
if (!mounted) return;
|
|
setState(() {
|
|
_isLoading = false;
|
|
_error = _formatError(error);
|
|
});
|
|
}
|
|
}
|
|
|
|
String? _validateRegistration({
|
|
required String username,
|
|
required String email,
|
|
required String password,
|
|
required String passwordConfirmation,
|
|
}) {
|
|
if (!RegExp(r'^[A-Za-z0-9]{3,32}$').hasMatch(username)) {
|
|
return 'Логин должен содержать от 3 до 32 латинских букв или цифр.';
|
|
}
|
|
if (!RegExp(r'^[^@\s]+@[^@\s]+\.[^@\s]+$').hasMatch(email)) {
|
|
return 'Введите корректный email.';
|
|
}
|
|
if (password.length < 8 || password.length > 32) {
|
|
return 'Пароль должен содержать от 8 до 32 символов.';
|
|
}
|
|
if (password != passwordConfirmation) {
|
|
return 'Пароли не совпадают.';
|
|
}
|
|
if (!_termsAccepted) {
|
|
return 'Примите правила сервера и пользовательское соглашение.';
|
|
}
|
|
return null;
|
|
}
|
|
|
|
void _setMode(MwAuthenticationMode mode) {
|
|
setState(() {
|
|
_mode = mode;
|
|
_error = null;
|
|
_success = null;
|
|
});
|
|
}
|
|
|
|
String _formatError(Object error) {
|
|
final raw = error.toString();
|
|
if (raw.startsWith('Exception: ')) return raw.substring(11);
|
|
return raw;
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return MwLoginShell(
|
|
form: MwAuthenticationForm(
|
|
usernameController: _usernameController,
|
|
emailController: _emailController,
|
|
passwordController: _passwordController,
|
|
passwordConfirmationController: _passwordConfirmationController,
|
|
inviteCodeController: _inviteCodeController,
|
|
mode: _mode,
|
|
onModeChanged: _setMode,
|
|
termsAccepted: _termsAccepted,
|
|
onTermsChanged: (value) => setState(() => _termsAccepted = value),
|
|
loading: _isLoading,
|
|
error: _error,
|
|
success: _success,
|
|
onSubmit: _submit,
|
|
),
|
|
onDrag: windowManager.startDragging,
|
|
onDoubleTap: () => unawaited(_toggleMaximized()),
|
|
onMinimize: windowManager.minimize,
|
|
onMaximizeRestore: () => unawaited(_toggleMaximized()),
|
|
onClose: () => unawaited(windowManager.destroy()),
|
|
);
|
|
}
|
|
|
|
Future<void> _toggleMaximized() async {
|
|
if (await windowManager.isMaximized()) {
|
|
await windowManager.unmaximize();
|
|
} else {
|
|
await windowManager.maximize();
|
|
}
|
|
}
|
|
}
|