Files
meezi/mobile/meezi_waiter/lib/features/auth/login_screen.dart
T
soroush.asadi a85890f30a chore: Flutter mobile app, CI, and dev tooling
- mobile/: Flutter/Dart merchant mobile app skeleton
- .github/: GitHub Actions CI workflows
- .dockerignore: exclude host node_modules from build context
- .cursorrules: Cursor IDE project rules
- .claude/: Claude Code project settings and launch config

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-05-27 21:35:27 +03:30

214 lines
6.8 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../core/auth/auth_provider.dart';
class LoginScreen extends ConsumerStatefulWidget {
const LoginScreen({super.key});
@override
ConsumerState<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends ConsumerState<LoginScreen> {
final _phoneCtrl = TextEditingController();
final _otpCtrl = TextEditingController();
final _phoneFocus = FocusNode();
final _otpFocus = FocusNode();
bool _otpSent = false;
bool _loading = false;
String? _error;
@override
void dispose() {
_phoneCtrl.dispose();
_otpCtrl.dispose();
_phoneFocus.dispose();
_otpFocus.dispose();
super.dispose();
}
Future<void> _sendOtp() async {
final phone = _phoneCtrl.text.trim();
if (phone.isEmpty) {
setState(() => _error = 'شماره موبایل را وارد کنید');
return;
}
setState(() {
_loading = true;
_error = null;
});
try {
await ref.read(authProvider.notifier).sendOtp(phone);
setState(() {
_otpSent = true;
_loading = false;
});
_otpFocus.requestFocus();
} catch (e) {
setState(() {
_loading = false;
_error = 'ارسال کد ناموفق بود. دوباره تلاش کنید.';
});
}
}
Future<void> _verify() async {
final phone = _phoneCtrl.text.trim();
final otp = _otpCtrl.text.trim();
if (otp.length < 4) {
setState(() => _error = 'کد تأیید را وارد کنید');
return;
}
setState(() {
_loading = true;
_error = null;
});
try {
await ref.read(authProvider.notifier).verifyOtp(phone, otp);
if (mounted) context.go('/home');
} catch (e) {
setState(() {
_loading = false;
_error = 'کد اشتباه یا منقضی شده است';
});
}
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Directionality(
textDirection: TextDirection.rtl,
child: Scaffold(
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(28),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Spacer(),
// Brand
Center(
child: Column(
children: [
Container(
width: 72,
height: 72,
decoration: BoxDecoration(
color: theme.colorScheme.primaryContainer,
borderRadius: BorderRadius.circular(20),
),
child: Icon(
Icons.notifications_active_rounded,
size: 38,
color: theme.colorScheme.primary,
),
),
const SizedBox(height: 16),
Text(
'میزی — گارسون',
style: theme.textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 6),
Text(
'برای دریافت اعلان‌های میز وارد شوید',
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
textAlign: TextAlign.center,
),
],
),
),
const Spacer(),
// Phone field
TextField(
controller: _phoneCtrl,
focusNode: _phoneFocus,
keyboardType: TextInputType.phone,
enabled: !_otpSent,
textDirection: TextDirection.ltr,
decoration: InputDecoration(
labelText: 'شماره موبایل',
hintText: '09121234567',
border: const OutlineInputBorder(),
prefixIcon: const Icon(Icons.phone),
suffixIcon: _otpSent
? IconButton(
icon: const Icon(Icons.edit),
onPressed: () =>
setState(() => _otpSent = false),
tooltip: 'ویرایش شماره',
)
: null,
),
),
const SizedBox(height: 14),
// OTP field
if (_otpSent) ...[
TextField(
controller: _otpCtrl,
focusNode: _otpFocus,
keyboardType: TextInputType.number,
textDirection: TextDirection.ltr,
maxLength: 6,
decoration: const InputDecoration(
labelText: 'کد تأیید',
hintText: '۶ رقم',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.lock_outline),
),
),
const SizedBox(height: 4),
],
// Error
if (_error != null) ...[
const SizedBox(height: 8),
Text(
_error!,
style: TextStyle(
color: theme.colorScheme.error, fontSize: 13),
textAlign: TextAlign.center,
),
],
const SizedBox(height: 16),
// Action button
FilledButton(
onPressed: _loading
? null
: (_otpSent ? _verify : _sendOtp),
style: FilledButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14),
),
),
child: _loading
? const SizedBox(
height: 20,
width: 20,
child:
CircularProgressIndicator(strokeWidth: 2.5),
)
: Text(_otpSent ? 'ورود' : 'ارسال کد'),
),
const SizedBox(height: 32),
],
),
),
),
),
);
}
}