Files
recipe-app/flutter/lib/features/auth/presentation/login_screen.dart
T
Nils-Johan Gynther 2e117718a7 feat(localization): Implement Swedish localization and error messages
- Added localization support for Swedish and English languages.
- Integrated localized strings for user messages in the API error mapper.
- Updated UI components to use localized strings for labels and messages.
- Ensured all error messages are context-aware and utilize the localization framework.
- Created regression test to prevent common ASCII fallbacks in Swedish UI text.
2026-04-22 19:16:23 +02:00

118 lines
4.1 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../../core/api/api_error_mapper.dart';
import '../../../core/l10n/l10n.dart';
import '../data/auth_providers.dart';
class LoginScreen extends ConsumerStatefulWidget {
const LoginScreen({super.key});
@override
ConsumerState<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends ConsumerState<LoginScreen> {
final _formKey = GlobalKey<FormState>();
final _usernameCtrl = TextEditingController();
final _passwordCtrl = TextEditingController();
final _passwordFocus = FocusNode();
@override
void dispose() {
_usernameCtrl.dispose();
_passwordCtrl.dispose();
_passwordFocus.dispose();
super.dispose();
}
Future<void> _submit() async {
if (!(_formKey.currentState?.validate() ?? false)) {
return;
}
await ref.read(authStateProvider.notifier).login(
_usernameCtrl.text.trim(),
_passwordCtrl.text,
);
// Router redirect handles navigation when authStateProvider updates.
}
@override
Widget build(BuildContext context) {
final authState = ref.watch(authStateProvider);
final isLoading = authState is AsyncLoading;
final l10n = context.l10n;
return Scaffold(
appBar: AppBar(title: Text(l10n.loginTitle)),
body: Center(
child: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 400),
child: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextFormField(
controller: _usernameCtrl,
decoration: InputDecoration(labelText: l10n.usernameLabel),
textInputAction: TextInputAction.next,
autofocus: true,
enabled: !isLoading,
onFieldSubmitted: (_) =>
FocusScope.of(context).requestFocus(_passwordFocus),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return l10n.usernameRequired;
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
controller: _passwordCtrl,
focusNode: _passwordFocus,
decoration: InputDecoration(labelText: l10n.passwordLabel),
obscureText: true,
textInputAction: TextInputAction.done,
enabled: !isLoading,
onFieldSubmitted: (_) => _submit(),
validator: (value) {
if (value == null || value.isEmpty) {
return l10n.passwordRequired;
}
return null;
},
),
const SizedBox(height: 28),
if (isLoading)
const Center(child: CircularProgressIndicator())
else
FilledButton(
onPressed: _submit,
child: Text(l10n.loginAction),
),
if (authState is AsyncError)
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text(
mapErrorToUserMessage(authState.error!, context),
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).colorScheme.error),
),
),
],
),
),
),
),
),
);
}
}