feat: 改用 Material Design 3 标准输入框
- 创建 MaterialInput 和 MaterialPasswordInput 组件 - 移除 shadcn_ui 依赖,使用原生 Material 组件 - 添加圆角边框 + Focus 效果 - 添加浮动标签动画 - 改进登录和注册页面视觉体验 - 统一设计语言(Material Design 3)
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../../core/theme/app_color_scheme.dart';
|
||||
@@ -7,6 +6,7 @@ import '../../../core/theme/app_spacing.dart';
|
||||
import '../../../core/theme/app_theme.dart';
|
||||
import '../../../core/theme/app_theme_extension.dart';
|
||||
import '../../../providers/auth_provider.dart';
|
||||
import '../../components/material_input.dart';
|
||||
import '../main/main_page.dart';
|
||||
import 'register_page.dart';
|
||||
|
||||
@@ -18,14 +18,13 @@ class LoginPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _LoginPageState extends State<LoginPage> {
|
||||
final formKey = GlobalKey<ShadFormState>();
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _usernameController = TextEditingController();
|
||||
final _passwordController = TextEditingController();
|
||||
bool _obscurePassword = true;
|
||||
|
||||
static const _loadingIndicatorSize = 16.0;
|
||||
static const _logoCircleSize = 80.0;
|
||||
static const _inputHeight = 52.0;
|
||||
static const _buttonHeight = 52.0;
|
||||
|
||||
@override
|
||||
@@ -45,8 +44,8 @@ class _LoginPageState extends State<LoginPage> {
|
||||
horizontal: AppSpacing.xl,
|
||||
vertical: AppSpacing.xxl,
|
||||
),
|
||||
child: ShadForm(
|
||||
key: formKey,
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
children: [
|
||||
// 頂部品牌區域
|
||||
@@ -135,63 +134,22 @@ class _LoginPageState extends State<LoginPage> {
|
||||
}
|
||||
|
||||
Widget _buildUsernameField() {
|
||||
return SizedBox(
|
||||
height: _inputHeight,
|
||||
child: ShadInputFormField(
|
||||
id: 'username',
|
||||
placeholder: const Text('請輸入用戶名'),
|
||||
leading: Padding(
|
||||
padding: const EdgeInsets.only(right: AppSpacing.sm),
|
||||
child: Icon(LucideIcons.user, size: 18, color: context.appColors.onSurfaceMuted),
|
||||
),
|
||||
validator: _validateUsername,
|
||||
controller: _usernameController,
|
||||
decoration: ShadDecoration(
|
||||
border: ShadBorder.all(
|
||||
color: context.colors.outlineVariant,
|
||||
radius: AppRadius.radiusLg,
|
||||
),
|
||||
),
|
||||
style: AppTextStyles.headlineMedium(context).copyWith(
|
||||
color: context.colors.onSurface,
|
||||
),
|
||||
),
|
||||
return MaterialInput(
|
||||
controller: _usernameController,
|
||||
labelText: '用戶名',
|
||||
hintText: '請輸入用戶名',
|
||||
prefixIcon: Icons.person_outline,
|
||||
validator: _validateUsername,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPasswordField() {
|
||||
final iconColor = context.appColors.onSurfaceMuted;
|
||||
|
||||
return SizedBox(
|
||||
height: _inputHeight,
|
||||
child: ShadInputFormField(
|
||||
id: 'password',
|
||||
placeholder: const Text('請輸入密碼'),
|
||||
obscureText: _obscurePassword,
|
||||
leading: Padding(
|
||||
padding: const EdgeInsets.only(right: AppSpacing.sm),
|
||||
child: Icon(LucideIcons.lock, size: 18, color: iconColor),
|
||||
),
|
||||
trailing: GestureDetector(
|
||||
onTap: () => setState(() => _obscurePassword = !_obscurePassword),
|
||||
child: Icon(
|
||||
_obscurePassword ? LucideIcons.eyeOff : LucideIcons.eye,
|
||||
size: 18,
|
||||
color: iconColor,
|
||||
),
|
||||
),
|
||||
validator: _validatePassword,
|
||||
controller: _passwordController,
|
||||
decoration: ShadDecoration(
|
||||
border: ShadBorder.all(
|
||||
color: context.colors.outlineVariant,
|
||||
radius: AppRadius.radiusLg,
|
||||
),
|
||||
),
|
||||
style: AppTextStyles.headlineMedium(context).copyWith(
|
||||
color: context.colors.onSurface,
|
||||
),
|
||||
),
|
||||
return MaterialPasswordInput(
|
||||
controller: _passwordController,
|
||||
labelText: '密碼',
|
||||
hintText: '請輸入密碼',
|
||||
prefixIcon: Icons.lock_outline,
|
||||
validator: _validatePassword,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -204,13 +162,21 @@ class _LoginPageState extends State<LoginPage> {
|
||||
builder: (context, auth, _) {
|
||||
return SizedBox(
|
||||
height: _buttonHeight,
|
||||
child: ShadButton(
|
||||
child: ElevatedButton(
|
||||
onPressed: auth.isLoading ? null : () => _handleLogin(auth),
|
||||
backgroundColor: buttonColor,
|
||||
foregroundColor: textColor,
|
||||
decoration: ShadDecoration(
|
||||
border: ShadBorder.all(
|
||||
radius: AppRadius.radiusLg,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: buttonColor,
|
||||
foregroundColor: textColor,
|
||||
disabledBackgroundColor: buttonColor.withValues(alpha: 0.5),
|
||||
disabledForegroundColor: textColor.withValues(alpha: 0.5),
|
||||
elevation: 0,
|
||||
shadowColor: Colors.transparent,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: AppSpacing.xl,
|
||||
vertical: AppSpacing.md,
|
||||
),
|
||||
),
|
||||
child: auth.isLoading
|
||||
@@ -299,12 +265,11 @@ class _LoginPageState extends State<LoginPage> {
|
||||
// ============================================
|
||||
|
||||
Future<void> _handleLogin(AuthProvider auth) async {
|
||||
if (!formKey.currentState!.saveAndValidate()) return;
|
||||
if (!_formKey.currentState!.validate()) return;
|
||||
|
||||
final values = formKey.currentState!.value;
|
||||
final response = await auth.login(
|
||||
values['username'],
|
||||
values['password'],
|
||||
_usernameController.text.trim(),
|
||||
_passwordController.text,
|
||||
);
|
||||
|
||||
if (!mounted) return;
|
||||
@@ -331,15 +296,15 @@ class _LoginPageState extends State<LoginPage> {
|
||||
}
|
||||
|
||||
void _showErrorDialog(String message) {
|
||||
showShadDialog(
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => ShadDialog.alert(
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('登錄失敗'),
|
||||
description: Text(message),
|
||||
content: Text(message),
|
||||
actions: [
|
||||
ShadButton(
|
||||
child: const Text('確定'),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('確定'),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user