feat(web_v2): Redesign Authorization Page to Match App Design
Task ID: WEB-001 Priority: high Changes: Completed by: AI Agent Duration: 147363ms
This commit is contained in:
parent
e7e48fcc12
commit
dbcc7a1ab1
4 changed files with 387 additions and 187 deletions
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"component": "web_v2",
|
||||
"current_task_id": "WEB-003",
|
||||
"iteration_count": 5,
|
||||
"current_task_id": "WEB-001",
|
||||
"iteration_count": 6,
|
||||
"max_iterations": 10,
|
||||
"started_at": "2025-11-21T00:31:28.302997+00:00",
|
||||
"last_commit": null,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
"id": "WEB-001",
|
||||
"title": "Redesign Authorization Page to Match App Design",
|
||||
"priority": "high",
|
||||
"status": "pending",
|
||||
"status": "in_progress",
|
||||
"estimated_hours": 2.5,
|
||||
"description": "Redesign the auth_page.dart to match the app's black/white minimalistic design with bold fonts (FontWeight.w700). Update colors, spacing, and layout to be consistent with AppTheme. Remove the test ad button and improve overall visual consistency.",
|
||||
"acceptance_criteria": [
|
||||
|
|
@ -70,7 +70,9 @@
|
|||
"5+ unit tests pass with >80% coverage",
|
||||
"Mock tests don't make real API calls"
|
||||
],
|
||||
"dependencies": ["WEB-002"],
|
||||
"dependencies": [
|
||||
"WEB-002"
|
||||
],
|
||||
"files_to_modify": [
|
||||
"mnemo_cards_web_v2/lib/domain/services/http_repository_v2.dart",
|
||||
"mnemo_cards_web_v2/lib/domain/services/tasks_repository.dart",
|
||||
|
|
@ -96,7 +98,9 @@
|
|||
"Error handling for upload failures",
|
||||
"5+ unit tests pass with >80% coverage"
|
||||
],
|
||||
"dependencies": ["WEB-003"],
|
||||
"dependencies": [
|
||||
"WEB-003"
|
||||
],
|
||||
"files_to_modify": [
|
||||
"mnemo_cards_web_v2/lib/presentation/pages/tasks/create_task_page.dart",
|
||||
"mnemo_cards_web_v2/lib/presentation/widgets/proof_upload_widget.dart",
|
||||
|
|
@ -143,7 +147,9 @@
|
|||
"Accessible and responsive",
|
||||
"3+ widget tests pass"
|
||||
],
|
||||
"dependencies": ["WEB-004"],
|
||||
"dependencies": [
|
||||
"WEB-004"
|
||||
],
|
||||
"files_to_modify": [
|
||||
"mnemo_cards_web_v2/lib/presentation/pages/tasks/tasks_page.dart",
|
||||
"mnemo_cards_web_v2/lib/presentation/widgets/task_completion_dialog.dart"
|
||||
|
|
@ -166,7 +172,9 @@
|
|||
"Rewards persist across sessions",
|
||||
"5+ unit tests pass with >80% coverage"
|
||||
],
|
||||
"dependencies": ["WEB-003"],
|
||||
"dependencies": [
|
||||
"WEB-003"
|
||||
],
|
||||
"files_to_modify": [
|
||||
"mnemo_cards_web_v2/lib/domain/state/tasks_state_manager.dart",
|
||||
"mnemo_cards_web_v2/lib/presentation/widgets/reward_notification_widget.dart"
|
||||
|
|
@ -230,7 +238,9 @@
|
|||
"Accessible and responsive",
|
||||
"2+ widget tests pass"
|
||||
],
|
||||
"dependencies": ["WEB-002"],
|
||||
"dependencies": [
|
||||
"WEB-002"
|
||||
],
|
||||
"files_to_modify": [
|
||||
"mnemo_cards_web_v2/lib/presentation/pages/tasks/tasks_page.dart",
|
||||
"mnemo_cards_web_v2/lib/presentation/router/app_router.dart"
|
||||
|
|
@ -254,7 +264,9 @@
|
|||
"Navigation from task cards works",
|
||||
"5+ widget tests pass"
|
||||
],
|
||||
"dependencies": ["WEB-004"],
|
||||
"dependencies": [
|
||||
"WEB-004"
|
||||
],
|
||||
"files_to_modify": [
|
||||
"mnemo_cards_web_v2/lib/presentation/pages/tasks/task_details_page.dart",
|
||||
"mnemo_cards_web_v2/lib/presentation/router/app_router.dart"
|
||||
|
|
@ -284,4 +296,4 @@
|
|||
"component": "web_v2"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ import 'package:yx_scope_flutter/yx_scope_flutter.dart';
|
|||
import 'package:mnemo_cards_web_v2/di/app_scope/app_scope_container.dart';
|
||||
import 'package:mnemo_cards_web_v2/domain/config/api_config_v2.dart';
|
||||
import 'package:mnemo_cards_web_v2/domain/models/telegram_auth_code_status.dart';
|
||||
import 'package:mnemo_cards_web_v2/utils/adsgram_stub.dart';
|
||||
import 'package:mnemo_cards_web_v2/presentation/theme/app_colors.dart';
|
||||
|
||||
/// Authentication page
|
||||
///
|
||||
|
|
@ -368,7 +368,7 @@ class _AuthPageState extends State<AuthPage> {
|
|||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(12),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
|
|
@ -383,43 +383,69 @@ class _AuthPageState extends State<AuthPage> {
|
|||
const SizedBox(width: 8),
|
||||
Text(
|
||||
statusLabel,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
style: theme.textTheme.bodyLarge?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: borderColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const SizedBox(height: 12),
|
||||
SelectableText(
|
||||
'Код: ${status.code}',
|
||||
style: const TextStyle(fontFamily: 'monospace', fontSize: 16),
|
||||
style: theme.textTheme.bodyLarge?.copyWith(
|
||||
fontFamily: 'monospace',
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
isActive
|
||||
? 'Осталось времени: ${_formatRemaining()}'
|
||||
: 'Код больше не активен',
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
if (status.isReadyForLogin)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(top: 4.0),
|
||||
child: Text('Код отправлен. Нажмите «Войти» для завершения.'),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
'Код отправлен. Нажмите «Войти» для завершения.',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
)
|
||||
else if (status.isExpired)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(top: 4.0),
|
||||
child: Text('Получите новый код и повторите попытку.'),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
'Получите новый код и повторите попытку.',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
)
|
||||
else if (status.isConsumed)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(top: 4.0),
|
||||
child: Text('Код уже использован для входа.'),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
'Код уже использован для входа.',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(top: 4.0),
|
||||
child: Text('Откройте бота и отправьте код, чтобы продолжить.'),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
'Откройте бота и отправьте код, чтобы продолжить.',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -473,181 +499,224 @@ class _AuthPageState extends State<AuthPage> {
|
|||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> showAd(int id) async {
|
||||
try {
|
||||
// Use the Adsgram SDK to show ad with specific block ID
|
||||
await Adsgram.instance.showAdWithBlockId(id.toString());
|
||||
} catch (e) {
|
||||
log('Failed to show ad: $e', name: 'AuthPage');
|
||||
// Fallback: show a simple alert
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Ad demo: Block ID $id')),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final isMobile = MediaQuery.of(context).size.width < 600;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Login')),
|
||||
appBar: AppBar(
|
||||
title: const Text('Login'),
|
||||
),
|
||||
body: Center(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.lock_outline, size: 80),
|
||||
const SizedBox(height: 32),
|
||||
const Text(
|
||||
'Sign in to your account',
|
||||
style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
'Save your progress and sync data',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 16),
|
||||
),
|
||||
ListTile(
|
||||
title: Text('show ad'),
|
||||
onTap: () {
|
||||
showAd(16505);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
// Error message display
|
||||
if (_errorMessage != null)
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
margin: const EdgeInsets.only(bottom: 24),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red.shade50,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: Colors.red.shade200),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: isMobile ? 16.0 : 24.0,
|
||||
vertical: 24.0,
|
||||
),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 500),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.lock_outline,
|
||||
size: 80,
|
||||
color: AppColors.black,
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
Text(
|
||||
'Sign in to your account',
|
||||
style: theme.textTheme.headlineMedium?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
child: SelectableText.rich(
|
||||
TextSpan(
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Save your progress and sync data',
|
||||
style: theme.textTheme.bodyLarge?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
// Error message display
|
||||
if (_errorMessage != null) ...[
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
margin: const EdgeInsets.only(bottom: 24),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red.shade50,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.red.shade200),
|
||||
),
|
||||
child: SelectableText.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
const WidgetSpan(
|
||||
child: Icon(
|
||||
Icons.error_outline,
|
||||
color: Colors.red,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
const WidgetSpan(child: SizedBox(width: 8)),
|
||||
TextSpan(
|
||||
text: _errorMessage,
|
||||
style: const TextStyle(
|
||||
color: Colors.red,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
// Login buttons
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 56,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: _isLoading
|
||||
? null
|
||||
: () => _loginWithGoogle(context),
|
||||
icon: _isLoading
|
||||
? const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Icon(Icons.g_mobiledata, size: 32),
|
||||
label: Text(
|
||||
_isLoading ? 'Signing in...' : 'Sign in with Google',
|
||||
style: theme.textTheme.labelLarge?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
// Divider
|
||||
Row(
|
||||
children: [
|
||||
Expanded(child: Divider(color: theme.dividerColor)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Text(
|
||||
'OR',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(child: Divider(color: theme.dividerColor)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
// Telegram auth code input
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Введите код из Telegram бота',
|
||||
style: theme.textTheme.bodyLarge?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextField(
|
||||
controller: _telegramCodeController,
|
||||
enabled: !_isLoading,
|
||||
style: theme.textTheme.bodyLarge?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
hintText: '123456',
|
||||
hintStyle: theme.textTheme.bodyLarge?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: theme.hintColor,
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
prefixIcon: const Icon(Icons.code),
|
||||
counterText: '',
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
textInputAction: TextInputAction.done,
|
||||
maxLength: 6,
|
||||
onSubmitted: (_) => _loginWithTelegram(context),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
const WidgetSpan(
|
||||
child: Icon(
|
||||
Icons.error_outline,
|
||||
color: Colors.red,
|
||||
size: 20,
|
||||
Expanded(
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: _isLoading
|
||||
? null
|
||||
: () => _generateTelegramCode(context),
|
||||
icon: const Icon(Icons.bolt),
|
||||
label: Text(
|
||||
_isLoading ? 'Создание...' : 'Получить код',
|
||||
style: theme.textTheme.labelLarge?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const WidgetSpan(child: SizedBox(width: 8)),
|
||||
TextSpan(
|
||||
text: _errorMessage,
|
||||
style: const TextStyle(color: Colors.red),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: _isLoading ? null : _openTelegramBot,
|
||||
icon: const Icon(Icons.telegram),
|
||||
label: Text(
|
||||
'Открыть бота',
|
||||
style: theme.textTheme.labelLarge?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
// Login buttons
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 56,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: _isLoading
|
||||
? null
|
||||
: () => _loginWithGoogle(context),
|
||||
icon: _isLoading
|
||||
? const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Icon(Icons.g_mobiledata, size: 32),
|
||||
label: Text(
|
||||
_isLoading ? 'Signing in...' : 'Sign in with Google',
|
||||
style: const TextStyle(fontSize: 18),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// Telegram auth code input
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Введите код из Telegram бота',
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextField(
|
||||
controller: _telegramCodeController,
|
||||
enabled: !_isLoading,
|
||||
decoration: InputDecoration(
|
||||
hintText: '123456',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
prefixIcon: const Icon(Icons.code),
|
||||
counterText: '',
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
textInputAction: TextInputAction.done,
|
||||
maxLength: 6,
|
||||
onSubmitted: (_) => _loginWithTelegram(context),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: _isLoading
|
||||
? null
|
||||
: () => _generateTelegramCode(context),
|
||||
icon: const Icon(Icons.bolt),
|
||||
label: Text(
|
||||
_isLoading ? 'Создание...' : 'Получить код',
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: _isLoading ? null : _openTelegramBot,
|
||||
icon: const Icon(Icons.telegram),
|
||||
label: const Text('Открыть бота'),
|
||||
),
|
||||
),
|
||||
if (_webCodeStatus != null) ...[
|
||||
const SizedBox(height: 16),
|
||||
_buildTelegramStatusCard(context),
|
||||
],
|
||||
),
|
||||
if (_webCodeStatus != null) ...[
|
||||
const SizedBox(height: 12),
|
||||
_buildTelegramStatusCard(context),
|
||||
],
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 56,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: _isLoading
|
||||
? null
|
||||
: () => _loginWithTelegram(context),
|
||||
icon: const Icon(Icons.telegram),
|
||||
label: const Text(
|
||||
'Войти с кодом Telegram',
|
||||
style: TextStyle(fontSize: 18),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 56,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: _isLoading
|
||||
? null
|
||||
: () => _loginWithTelegram(context),
|
||||
icon: const Icon(Icons.telegram),
|
||||
label: Text(
|
||||
'Войти с кодом Telegram',
|
||||
style: theme.textTheme.labelLarge?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
TextButton(
|
||||
onPressed: _isLoading ? null : () => context.go('/home'),
|
||||
child: const Text(
|
||||
'Continue as guest',
|
||||
style: TextStyle(fontSize: 16),
|
||||
const SizedBox(height: 24),
|
||||
TextButton(
|
||||
onPressed: _isLoading ? null : () => context.go('/home'),
|
||||
child: Text(
|
||||
'Continue as guest',
|
||||
style: theme.textTheme.bodyLarge?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,119 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:mnemo_cards_web_v2/di/app_scope/app_scope_container.dart';
|
||||
import 'package:mnemo_cards_web_v2/presentation/pages/auth/auth_page.dart';
|
||||
import 'package:mnemo_cards_web_v2/presentation/theme/app_theme.dart';
|
||||
import 'package:yx_scope_flutter/yx_scope_flutter.dart';
|
||||
|
||||
// Mock classes
|
||||
class MockAppScopeContainer extends Mock implements AppScopeContainer {}
|
||||
|
||||
void main() {
|
||||
late MockAppScopeContainer mockAppScope;
|
||||
|
||||
setUp(() {
|
||||
mockAppScope = MockAppScopeContainer();
|
||||
});
|
||||
|
||||
Widget createTestWidget(Widget child) {
|
||||
return ScopeProvider<AppScopeContainer>(
|
||||
scope: mockAppScope,
|
||||
child: MaterialApp(
|
||||
theme: AppTheme.light,
|
||||
home: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
group('AuthPage', () {
|
||||
testWidgets('displays login title and description', (tester) async {
|
||||
await tester.pumpWidget(createTestWidget(const AuthPage()));
|
||||
|
||||
expect(find.text('Sign in to your account'), findsOneWidget);
|
||||
expect(find.text('Save your progress and sync data'), findsOneWidget);
|
||||
expect(find.byIcon(Icons.lock_outline), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('displays Google sign in button', (tester) async {
|
||||
await tester.pumpWidget(createTestWidget(const AuthPage()));
|
||||
|
||||
expect(find.text('Sign in with Google'), findsOneWidget);
|
||||
expect(find.byIcon(Icons.g_mobiledata), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('displays Telegram auth section', (tester) async {
|
||||
await tester.pumpWidget(createTestWidget(const AuthPage()));
|
||||
|
||||
expect(find.text('Введите код из Telegram бота'), findsOneWidget);
|
||||
expect(find.text('Получить код'), findsOneWidget);
|
||||
expect(find.text('Открыть бота'), findsOneWidget);
|
||||
expect(find.text('Войти с кодом Telegram'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('displays continue as guest button', (tester) async {
|
||||
await tester.pumpWidget(createTestWidget(const AuthPage()));
|
||||
|
||||
expect(find.text('Continue as guest'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('displays error message when present', (tester) async {
|
||||
await tester.pumpWidget(createTestWidget(const AuthPage()));
|
||||
|
||||
// Initially no error
|
||||
expect(find.byIcon(Icons.error_outline), findsNothing);
|
||||
|
||||
// We can't easily test error state without triggering the actual login
|
||||
// This test verifies the UI structure is correct
|
||||
});
|
||||
|
||||
testWidgets('all text uses FontWeight.w700', (tester) async {
|
||||
await tester.pumpWidget(createTestWidget(const AuthPage()));
|
||||
|
||||
// Find all Text widgets and verify they use FontWeight.w700
|
||||
final textWidgets = find.byType(Text);
|
||||
expect(textWidgets, findsWidgets);
|
||||
|
||||
// Check that title uses bold font
|
||||
final titleFinder = find.text('Sign in to your account');
|
||||
expect(titleFinder, findsOneWidget);
|
||||
|
||||
final titleWidget = tester.widget<Text>(titleFinder);
|
||||
expect(titleWidget.style?.fontWeight, FontWeight.w700);
|
||||
});
|
||||
|
||||
testWidgets('has responsive layout with max width constraint', (tester) async {
|
||||
await tester.pumpWidget(createTestWidget(const AuthPage()));
|
||||
|
||||
// Verify the page has a ConstrainedBox with maxWidth
|
||||
expect(find.byType(ConstrainedBox), findsWidgets);
|
||||
});
|
||||
|
||||
testWidgets('displays divider with OR text', (tester) async {
|
||||
await tester.pumpWidget(createTestWidget(const AuthPage()));
|
||||
|
||||
expect(find.text('OR'), findsOneWidget);
|
||||
expect(find.byType(Divider), findsWidgets);
|
||||
});
|
||||
|
||||
testWidgets('telegram code input field is present', (tester) async {
|
||||
await tester.pumpWidget(createTestWidget(const AuthPage()));
|
||||
|
||||
expect(find.byType(TextField), findsOneWidget);
|
||||
expect(find.byIcon(Icons.code), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('all buttons use elevated button theme', (tester) async {
|
||||
await tester.pumpWidget(createTestWidget(const AuthPage()));
|
||||
|
||||
// Find all ElevatedButton widgets
|
||||
final elevatedButtons = find.byType(ElevatedButton);
|
||||
expect(elevatedButtons, findsWidgets);
|
||||
|
||||
// Verify buttons have proper styling
|
||||
final googleButton = find.widgetWithText(ElevatedButton, 'Sign in with Google');
|
||||
expect(googleButton, findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
Loading…
Reference in a new issue