merge: 合并 K41-A 登录页重设计
This commit is contained in:
commit
7285a1cd4e
5 files changed
+233
-141
No files matched your search
@@ -22,11 +22,11 @@ class _AuthFields extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_fieldLabel(state.l10n.authEmailLabel),
|
||||
const SizedBox(height: 8),
|
||||
const SizedBox(height: 6),
|
||||
_emailField(context, hasError: emailError),
|
||||
const SizedBox(height: 16),
|
||||
const SizedBox(height: 14),
|
||||
_fieldLabel(state.l10n.authPasswordLabel),
|
||||
const SizedBox(height: 8),
|
||||
const SizedBox(height: 6),
|
||||
_passwordField(context, hasError: passwordError),
|
||||
if (state.status.signUpMode) ...[
|
||||
const SizedBox(height: 8),
|
||||
@@ -60,7 +60,8 @@ class _AuthFields extends StatelessWidget {
|
||||
autofillHints: const [AutofillHints.email],
|
||||
onChanged: callbacks.onEmailChanged,
|
||||
onSubmitted: (_) => state.fields.passwordFocus.requestFocus(),
|
||||
style: const TextStyle(color: AppColors.onPrimary, fontSize: 15),
|
||||
style: const TextStyle(color: AppColors.onSurface, fontSize: 15),
|
||||
cursorColor: AppColors.blue,
|
||||
decoration: _decoration(
|
||||
context: context,
|
||||
hint: 'name@example.com',
|
||||
@@ -91,7 +92,8 @@ class _AuthFields extends StatelessWidget {
|
||||
autofillHints: const [AutofillHints.password],
|
||||
onChanged: callbacks.onPasswordChanged,
|
||||
onSubmitted: (_) => callbacks.onSubmit(),
|
||||
style: const TextStyle(color: AppColors.onPrimary, fontSize: 15),
|
||||
style: const TextStyle(color: AppColors.onSurface, fontSize: 15),
|
||||
cursorColor: AppColors.blue,
|
||||
decoration: _decoration(
|
||||
context: context,
|
||||
hint: l10n.authPasswordHint,
|
||||
@@ -125,8 +127,8 @@ class _AuthFields extends StatelessWidget {
|
||||
label,
|
||||
style: const TextStyle(
|
||||
color: AppColors.muted,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -140,7 +142,10 @@ class _AuthFields extends StatelessWidget {
|
||||
}) {
|
||||
final border = OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(_fieldRadius),
|
||||
borderSide: BorderSide.none,
|
||||
borderSide: const BorderSide(
|
||||
color: AppColors.border,
|
||||
width: _fieldBorderWidth,
|
||||
),
|
||||
);
|
||||
final errorColor = Theme.of(context).colorScheme.error;
|
||||
final errorBorder = OutlineInputBorder(
|
||||
@@ -149,11 +154,11 @@ class _AuthFields extends StatelessWidget {
|
||||
);
|
||||
return InputDecoration(
|
||||
hintText: hint,
|
||||
hintStyle: const TextStyle(color: AppColors.muted, fontSize: 14),
|
||||
hintStyle: const TextStyle(color: AppColors.subtle, fontSize: 14),
|
||||
prefixIcon: Icon(
|
||||
icon,
|
||||
size: 21,
|
||||
color: focused ? AppColors.blueSoft : AppColors.muted,
|
||||
color: focused ? AppColors.blue : AppColors.muted,
|
||||
),
|
||||
prefixIconConstraints: const BoxConstraints(
|
||||
minWidth: _minTapTarget,
|
||||
@@ -161,7 +166,7 @@ class _AuthFields extends StatelessWidget {
|
||||
),
|
||||
suffixIcon: suffix,
|
||||
filled: true,
|
||||
fillColor: AppColors.bar,
|
||||
fillColor: AppColors.card,
|
||||
isDense: true,
|
||||
contentPadding: const EdgeInsets.only(right: 16),
|
||||
border: border,
|
||||
@@ -229,8 +234,8 @@ class _AuthSubmitButton extends StatelessWidget {
|
||||
: Text(
|
||||
status.signUpMode ? l10n.signUp : l10n.signIn,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -273,7 +278,7 @@ class _ModeSegment extends StatelessWidget {
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: selected ? AppColors.onPrimary : AppColors.muted,
|
||||
color: selected ? AppColors.onPrimary : AppColors.onSurface,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
@@ -327,29 +332,11 @@ class _AuthBrand extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _brandMark() => Container(
|
||||
Widget _brandMark() => _brandMarkWidget(
|
||||
key: const Key('auth-brand-mark'),
|
||||
width: _brandMarkSize,
|
||||
height: _brandMarkSize,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColors.blue.withValues(alpha: 0.32),
|
||||
blurRadius: 24,
|
||||
offset: const Offset(0, 10),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
child: Image.asset(
|
||||
'assets/icon/splash_mark.png',
|
||||
width: _brandMarkSize,
|
||||
height: _brandMarkSize,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
size: compact ? _compactBrandMarkSize : _brandMarkSize,
|
||||
radius: compact ? _compactBrandMarkRadius : _brandMarkRadius,
|
||||
iconSize: compact ? _compactBrandIconSize : _brandIconSize,
|
||||
);
|
||||
|
||||
Widget _brandCopy(BuildContext context, {required bool centered}) => Column(
|
||||
@@ -361,9 +348,9 @@ class _AuthBrand extends StatelessWidget {
|
||||
AppLocalizations.of(context)!.appName,
|
||||
textAlign: centered ? TextAlign.center : TextAlign.start,
|
||||
style: const TextStyle(
|
||||
color: AppColors.onPrimary,
|
||||
fontSize: 26,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: AppColors.onSurface,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w900,
|
||||
height: 1.1,
|
||||
),
|
||||
),
|
||||
@@ -372,8 +359,8 @@ class _AuthBrand extends StatelessWidget {
|
||||
tagline,
|
||||
textAlign: centered ? TextAlign.center : TextAlign.start,
|
||||
style: const TextStyle(
|
||||
color: AppColors.blueSoft,
|
||||
fontSize: 14,
|
||||
color: AppColors.muted,
|
||||
fontSize: 13,
|
||||
height: 1.35,
|
||||
),
|
||||
),
|
||||
@@ -406,7 +393,7 @@ class _AuthError extends StatelessWidget {
|
||||
child: Text(
|
||||
message,
|
||||
key: const Key('auth-error'),
|
||||
style: TextStyle(color: color, fontSize: 13, height: 1.35),
|
||||
style: TextStyle(color: color, fontSize: 12, height: 1.35),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
+103
-71
@@ -11,6 +11,7 @@ import '../../core/platform/haptics.dart';
|
||||
import '../../core/theme/app_colors.dart';
|
||||
import '../../core/theme/app_dimens.dart';
|
||||
import '../../core/theme/app_theme.dart';
|
||||
import '../../core/widgets/subpage_header.dart';
|
||||
import '../../l10n/app_localizations.dart';
|
||||
import 'auth_logic.dart';
|
||||
import 'phone_auth_sheet.dart';
|
||||
@@ -19,18 +20,21 @@ part 'auth_widgets.dart';
|
||||
part 'auth_form_widgets.dart';
|
||||
|
||||
const double _minTapTarget = 44;
|
||||
const double _brandMarkSize = 48;
|
||||
const double _brandMarkSize = 72;
|
||||
const double _compactBrandMarkSize = 48;
|
||||
const double _brandMarkRadius = 20;
|
||||
const double _compactBrandMarkRadius = 14;
|
||||
const double _brandIconSize = 36;
|
||||
const double _compactBrandIconSize = 26;
|
||||
const double _contentMaxWidth = 440;
|
||||
// Auth keeps its approved 20px card silhouette.
|
||||
const double _cardRadius = AppDimens.authCardRadius;
|
||||
const double _cardPadding = AppDimens.cardPadding;
|
||||
const double _cardBorderWidth = 1;
|
||||
const double _cardPadding = 20;
|
||||
const double _fieldHeight = 52;
|
||||
const double _fieldRadius = 14;
|
||||
const double _fieldBorderWidth = 1.5;
|
||||
const double _buttonHeight = 52;
|
||||
const double _buttonRadius = 26;
|
||||
const double _glowOpacity = 0.18;
|
||||
const double _fieldBorderWidth = 1;
|
||||
const double _buttonHeight = 50;
|
||||
const double _buttonRadius = 25;
|
||||
const double _errorOpacity = 0.10;
|
||||
const double _errorBorderOpacity = 0.28;
|
||||
const double _enterSlideDistance = 16;
|
||||
@@ -39,11 +43,48 @@ const double _bottomContentPadding = 28;
|
||||
const double _compactTopContentPadding = 48;
|
||||
const double _compactHeightBreakpoint = 720;
|
||||
const double _compactBrandGap = 16;
|
||||
const double _warmGlowSize = 260;
|
||||
const double _warmGlowOpacity = 0.06;
|
||||
const double _glowSize = 280;
|
||||
const double _warmGlowOpacity = 0.18;
|
||||
const double _entryButtonHeight = 50;
|
||||
const double _entryButtonRadius = 25;
|
||||
const double _entryButtonFontSize = 15;
|
||||
const double _entryIconSize = 20;
|
||||
const Duration _enterDuration = Duration(milliseconds: 220);
|
||||
const Duration _segmentDuration = Duration(milliseconds: 160);
|
||||
|
||||
Widget _brandMarkWidget({
|
||||
Key? key,
|
||||
required double size,
|
||||
required double radius,
|
||||
required double iconSize,
|
||||
}) {
|
||||
return Container(
|
||||
key: key,
|
||||
width: size,
|
||||
height: size,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(radius),
|
||||
gradient: const LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [AppColors.blue, AppColors.secondary],
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColors.blue.withValues(alpha: 0.24),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Icon(
|
||||
Icons.play_arrow_rounded,
|
||||
color: AppColors.onPrimary,
|
||||
size: iconSize,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 登录 / 注册页:邮箱 + 密码。
|
||||
class AuthPage extends StatefulWidget {
|
||||
const AuthPage({super.key, this.signUpMode = false});
|
||||
@@ -147,34 +188,39 @@ class _AuthPageState extends State<AuthPage> {
|
||||
backgroundColor: AppColors.ink,
|
||||
body: Stack(
|
||||
children: [
|
||||
const Positioned.fill(
|
||||
child: IgnorePointer(
|
||||
child: DecoratedBox(
|
||||
key: Key('auth-page-background'),
|
||||
decoration: BoxDecoration(gradient: AppTheme.pageGradient),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: 420,
|
||||
top: -90,
|
||||
right: -70,
|
||||
width: _glowSize,
|
||||
height: _glowSize,
|
||||
child: IgnorePointer(
|
||||
child: Container(
|
||||
key: const Key('auth-background-glow'),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
shape: BoxShape.circle,
|
||||
gradient: RadialGradient(
|
||||
colors: [
|
||||
AppColors.blue.withValues(alpha: _glowOpacity),
|
||||
AppColors.blue.withValues(alpha: 0.05),
|
||||
AppColors.ink.withValues(alpha: 0),
|
||||
AppColors.surfaceTop,
|
||||
AppColors.surfaceTop.withValues(alpha: 0),
|
||||
],
|
||||
stops: const [0, 0.55, 1],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 72,
|
||||
right: -80,
|
||||
width: _warmGlowSize,
|
||||
height: _warmGlowSize,
|
||||
left: -110,
|
||||
bottom: -90,
|
||||
width: _glowSize,
|
||||
height: _glowSize,
|
||||
child: IgnorePointer(
|
||||
child: Container(
|
||||
key: const Key('auth-background-warm-glow'),
|
||||
@@ -239,23 +285,16 @@ class _AuthPageState extends State<AuthPage> {
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 4, top: 4),
|
||||
child: IconButton(
|
||||
key: const Key('auth-back'),
|
||||
tooltip: MaterialLocalizations.of(
|
||||
context,
|
||||
).backButtonTooltip,
|
||||
onPressed: () {
|
||||
padding: const EdgeInsets.only(left: 14, top: 8),
|
||||
child: AppSubpageBackButton(
|
||||
backKey: const Key('auth-back'),
|
||||
onBack: () {
|
||||
if (_emailEntryOpen) {
|
||||
setState(() => _emailEntryOpen = false);
|
||||
} else {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.arrow_back_rounded,
|
||||
color: AppColors.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -298,58 +337,44 @@ class _AuthPageState extends State<AuthPage> {
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Center(
|
||||
child: Container(
|
||||
child: _brandMarkWidget(
|
||||
key: const Key('auth-brand-logo'),
|
||||
width: 64,
|
||||
height: 64,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
gradient: const LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [AppColors.blue, AppColors.secondary],
|
||||
),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.play_arrow_rounded,
|
||||
color: AppColors.onPrimary,
|
||||
size: 32,
|
||||
),
|
||||
size: _brandMarkSize,
|
||||
radius: _brandMarkRadius,
|
||||
iconSize: _brandIconSize,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
l10n.fruitShortsBrand,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Outfit',
|
||||
color: AppColors.onSurface,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
l10n.authLandingSubtitle,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(color: AppColors.subtle, fontSize: 11),
|
||||
style: const TextStyle(color: AppColors.muted, fontSize: 13),
|
||||
),
|
||||
const SizedBox(height: 22),
|
||||
const SizedBox(height: 28),
|
||||
_AuthEntryButton(
|
||||
key: const Key('auth-entry-phone'),
|
||||
label: l10n.authPhoneSignIn,
|
||||
icon: Icons.phone_android_rounded,
|
||||
background: AppColors.input,
|
||||
foreground: AppColors.onSurface,
|
||||
primary: true,
|
||||
onTap: _openPhoneSheet,
|
||||
),
|
||||
const SizedBox(height: 11),
|
||||
const SizedBox(height: 12),
|
||||
_AuthEntryButton(
|
||||
key: const Key('auth-entry-email'),
|
||||
label: l10n.authEmailSignIn,
|
||||
icon: Icons.mail_outline_rounded,
|
||||
background: AppColors.input,
|
||||
foreground: AppColors.onSurface,
|
||||
primary: false,
|
||||
onTap: () => setState(() => _emailEntryOpen = true),
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
@@ -417,33 +442,40 @@ class _AuthEntryButton extends StatelessWidget {
|
||||
const _AuthEntryButton({
|
||||
super.key,
|
||||
required this.label,
|
||||
required this.background,
|
||||
required this.foreground,
|
||||
required this.primary,
|
||||
required this.onTap,
|
||||
this.icon,
|
||||
});
|
||||
|
||||
final String label;
|
||||
final Color background;
|
||||
final Color foreground;
|
||||
final bool primary;
|
||||
final VoidCallback onTap;
|
||||
final IconData? icon;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final background = primary ? AppColors.blue : AppColors.card;
|
||||
final foreground = primary ? AppColors.onPrimary : AppColors.onSurface;
|
||||
return Material(
|
||||
color: background,
|
||||
borderRadius: BorderRadius.circular(9),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(_entryButtonRadius),
|
||||
side: primary
|
||||
? BorderSide.none
|
||||
: const BorderSide(color: AppColors.border),
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(_entryButtonRadius),
|
||||
child: SizedBox(
|
||||
height: 44,
|
||||
height: _entryButtonHeight,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (icon != null) Icon(icon, size: 16, color: foreground),
|
||||
if (icon != null) const SizedBox(width: 7),
|
||||
if (icon != null)
|
||||
Icon(icon, size: _entryIconSize, color: foreground),
|
||||
if (icon != null) const SizedBox(width: 8),
|
||||
Flexible(
|
||||
child: Text(
|
||||
label,
|
||||
@@ -451,8 +483,8 @@ class _AuthEntryButton extends StatelessWidget {
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: foreground,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: _entryButtonFontSize,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -76,10 +76,7 @@ class _AuthCard extends StatelessWidget {
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.card,
|
||||
borderRadius: BorderRadius.circular(_cardRadius),
|
||||
border: Border.all(
|
||||
color: AppColors.outlineVariantDark,
|
||||
width: _cardBorderWidth,
|
||||
),
|
||||
boxShadow: const [AppTheme.cardShadow],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
@@ -94,7 +91,7 @@ class _AuthCard extends StatelessWidget {
|
||||
Text(
|
||||
status.signUpMode ? l10n.authSignUpTitle : l10n.authSignInTitle,
|
||||
style: const TextStyle(
|
||||
color: AppColors.onPrimary,
|
||||
color: AppColors.onSurface,
|
||||
fontSize: 25,
|
||||
fontWeight: FontWeight.w800,
|
||||
height: 1.2,
|
||||
@@ -149,12 +146,11 @@ class _AuthModeSwitch extends StatelessWidget {
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(22)),
|
||||
),
|
||||
child: Container(
|
||||
height: _minTapTarget,
|
||||
padding: const EdgeInsets.all(3),
|
||||
height: 36,
|
||||
padding: const EdgeInsets.all(2),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.bar,
|
||||
borderRadius: BorderRadius.circular(22),
|
||||
border: Border.all(color: AppColors.outlineVariantDark),
|
||||
color: AppColors.raised,
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
|
||||
@@ -53,7 +53,7 @@ void main() {
|
||||
final errorBorder = email.decoration!.errorBorder! as OutlineInputBorder;
|
||||
final scheme = Theme.of(tester.element(emailFinder)).colorScheme;
|
||||
expect(errorBorder.borderSide.color, scheme.error);
|
||||
expect(errorBorder.borderSide.width, 1.5);
|
||||
expect(errorBorder.borderSide.width, 1);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import 'package:fruitshorts/core/data/services/auth_controller.dart';
|
||||
import 'package:fruitshorts/core/data/services/auth_session_store.dart';
|
||||
import 'package:fruitshorts/core/platform/haptics.dart';
|
||||
import 'package:fruitshorts/core/theme/app_colors.dart';
|
||||
import 'package:fruitshorts/core/theme/app_theme.dart';
|
||||
import 'package:fruitshorts/l10n/app_localizations.dart';
|
||||
import 'package:fruitshorts/pages/auth/auth_view.dart';
|
||||
|
||||
@@ -30,6 +31,7 @@ void main() {
|
||||
AuthApi? api,
|
||||
FakeHaptics? haptics,
|
||||
bool openEmail = true,
|
||||
bool asRoute = false,
|
||||
}) async {
|
||||
final auth = AuthController(
|
||||
api ??
|
||||
@@ -53,11 +55,15 @@ void main() {
|
||||
locale: const Locale('en'),
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: const AuthPage(),
|
||||
home: asRoute ? const _AuthRouteHost() : const AuthPage(),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump(const Duration(milliseconds: 220));
|
||||
if (asRoute) {
|
||||
await tester.tap(find.byKey(const Key('auth-open')));
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
if (openEmail) {
|
||||
await tester.ensureVisible(find.byKey(const Key('auth-entry-email')));
|
||||
await tester.tap(find.byKey(const Key('auth-entry-email')));
|
||||
@@ -78,21 +84,46 @@ void main() {
|
||||
);
|
||||
expect(
|
||||
tester.getSize(find.byKey(const Key('auth-brand-logo'))),
|
||||
const Size(64, 64),
|
||||
const Size(72, 72),
|
||||
);
|
||||
expect(find.byKey(const Key('auth-entry-google')), findsNothing);
|
||||
expect(find.byKey(const Key('auth-entry-phone')), findsOneWidget);
|
||||
expect(find.byKey(const Key('auth-entry-email')), findsOneWidget);
|
||||
expect(find.byKey(const Key('auth-legal')), findsOneWidget);
|
||||
expect(
|
||||
tester.getSize(find.byKey(const Key('auth-entry-phone'))).height,
|
||||
50,
|
||||
);
|
||||
expect(
|
||||
tester.getSize(find.byKey(const Key('auth-entry-email'))).height,
|
||||
50,
|
||||
);
|
||||
final phoneButton = tester.widget<Material>(
|
||||
find.descendant(
|
||||
of: find.byKey(const Key('auth-entry-phone')),
|
||||
matching: find.byType(Material),
|
||||
),
|
||||
);
|
||||
expect(phoneButton.color, AppColors.blue);
|
||||
final emailButton = tester.widget<Material>(
|
||||
find.descendant(
|
||||
of: find.byKey(const Key('auth-entry-email')),
|
||||
matching: find.byType(Material),
|
||||
),
|
||||
);
|
||||
expect(emailButton.color, AppColors.card);
|
||||
expect(
|
||||
(emailButton.shape! as RoundedRectangleBorder).side.color,
|
||||
AppColors.border,
|
||||
);
|
||||
|
||||
final glow = tester.widget<Container>(
|
||||
find.byKey(const Key('auth-background-glow')),
|
||||
);
|
||||
final glowDecoration = glow.decoration! as BoxDecoration;
|
||||
final gradient = glowDecoration.gradient! as LinearGradient;
|
||||
for (final color in gradient.colors) {
|
||||
expect(color.a, lessThanOrEqualTo(0.18));
|
||||
}
|
||||
final gradient = glowDecoration.gradient! as RadialGradient;
|
||||
expect(gradient.colors.first, AppColors.surfaceTop);
|
||||
expect(gradient.colors.last.a, 0);
|
||||
|
||||
await tester.tap(find.byKey(const Key('auth-entry-email')));
|
||||
await tester.pumpAndSettle();
|
||||
@@ -101,19 +132,28 @@ void main() {
|
||||
final cardDecoration = card.decoration! as BoxDecoration;
|
||||
expect(cardDecoration.color, AppColors.card);
|
||||
expect(cardDecoration.borderRadius, BorderRadius.circular(20));
|
||||
expect(
|
||||
(cardDecoration.border! as Border).top.color,
|
||||
AppColors.outlineVariantDark,
|
||||
);
|
||||
expect((cardDecoration.border! as Border).top.width, 1);
|
||||
expect(cardDecoration.boxShadow, contains(AppTheme.cardShadow));
|
||||
expect(card.padding, const EdgeInsets.all(20));
|
||||
|
||||
final email = tester.widget<TextField>(
|
||||
find.byKey(const Key('auth-email')),
|
||||
);
|
||||
expect(email.decoration?.fillColor, AppColors.bar);
|
||||
expect(email.decoration?.fillColor, AppColors.card);
|
||||
expect(email.style?.color, AppColors.onSurface);
|
||||
expect(email.style?.fontSize, 15);
|
||||
expect(email.cursorColor, AppColors.blue);
|
||||
expect(tester.getSize(find.byKey(const Key('auth-email'))).height, 52);
|
||||
final emailBorder = email.decoration!.border! as OutlineInputBorder;
|
||||
final emailBorder =
|
||||
email.decoration!.enabledBorder! as OutlineInputBorder;
|
||||
expect(emailBorder.borderRadius, BorderRadius.circular(14));
|
||||
expect(emailBorder.borderSide.color, AppColors.border);
|
||||
expect(emailBorder.borderSide.width, 1);
|
||||
final password = tester.widget<TextField>(
|
||||
find.byKey(const Key('auth-password')),
|
||||
);
|
||||
expect(password.style?.color, AppColors.onSurface);
|
||||
expect(password.style?.fontSize, 15);
|
||||
expect(password.cursorColor, AppColors.blue);
|
||||
|
||||
final submit = tester.widget<FilledButton>(
|
||||
find.byKey(const Key('auth-submit')),
|
||||
@@ -123,11 +163,11 @@ void main() {
|
||||
submit.style!.backgroundColor!.resolve(const {WidgetState.pressed}),
|
||||
AppColors.bluePressed,
|
||||
);
|
||||
expect(tester.getSize(find.byKey(const Key('auth-submit'))).height, 52);
|
||||
expect(tester.getSize(find.byKey(const Key('auth-submit'))).height, 50);
|
||||
expect(
|
||||
(submit.style!.shape!.resolve(const {})! as RoundedRectangleBorder)
|
||||
.borderRadius,
|
||||
BorderRadius.circular(26),
|
||||
BorderRadius.circular(25),
|
||||
);
|
||||
|
||||
expect(find.text('Sign In'), findsWidgets);
|
||||
@@ -139,6 +179,23 @@ void main() {
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('should_render_40px_back_button_when_page_is_pushed', (
|
||||
tester,
|
||||
) async {
|
||||
await pumpAuthPage(tester, openEmail: false, asRoute: true);
|
||||
|
||||
final back = find.byKey(const Key('auth-back'));
|
||||
expect(tester.getSize(back), const Size(40, 40));
|
||||
expect(
|
||||
tester
|
||||
.widget<Material>(
|
||||
find.descendant(of: back, matching: find.byType(Material)),
|
||||
)
|
||||
.color,
|
||||
AppColors.card,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('should_fade_and_slide_content_in_within_220ms', (tester) async {
|
||||
await pumpAuthPage(tester);
|
||||
|
||||
@@ -198,10 +255,10 @@ void main() {
|
||||
final focusedBorder =
|
||||
email.decoration!.focusedBorder! as OutlineInputBorder;
|
||||
expect(focusedBorder.borderSide.color, AppColors.blue);
|
||||
expect(focusedBorder.borderSide.width, 1.5);
|
||||
expect(focusedBorder.borderSide.width, 1);
|
||||
expect(
|
||||
tester.widget<Icon>(find.byIcon(Icons.mail_outline_rounded)).color,
|
||||
AppColors.blueSoft,
|
||||
AppColors.blue,
|
||||
);
|
||||
|
||||
await tester.tap(find.byKey(const Key('auth-password-visibility')));
|
||||
@@ -280,6 +337,7 @@ void main() {
|
||||
final error = tester.widget<Text>(errorFinder);
|
||||
final scheme = Theme.of(tester.element(errorFinder)).colorScheme;
|
||||
expect(error.style?.color, scheme.error);
|
||||
expect(error.style?.fontSize, 12);
|
||||
expect(
|
||||
find.descendant(
|
||||
of: capsuleFinder,
|
||||
@@ -325,3 +383,22 @@ class _PendingAuthApi extends AuthApi {
|
||||
throw const ApiException(500, 'pending failure');
|
||||
}
|
||||
}
|
||||
|
||||
class _AuthRouteHost extends StatelessWidget {
|
||||
const _AuthRouteHost();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
child: TextButton(
|
||||
key: const Key('auth-open'),
|
||||
onPressed: () => Navigator.of(
|
||||
context,
|
||||
).push<void>(MaterialPageRoute(builder: (_) => const AuthPage())),
|
||||
child: const Text('Open auth'),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user