From 97725cb768f7f66cb0bbc73d288e16a927c80626 Mon Sep 17 00:00:00 2001 From: sion <450702724@qq.com> Date: Wed, 8 Apr 2026 11:11:43 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=B9=E7=94=A8=20Material=20Design?= =?UTF-8?q?=203=20=E6=A0=87=E5=87=86=E8=BE=93=E5=85=A5=E6=A1=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 创建 MaterialInput 和 MaterialPasswordInput 组件 - 移除 shadcn_ui 依赖,使用原生 Material 组件 - 添加圆角边框 + Focus 效果 - 添加浮动标签动画 - 改进登录和注册页面视觉体验 - 统一设计语言(Material Design 3) --- .../lib/ui/components/material_input.dart | 230 ++++++++++++ .../lib/ui/pages/auth/login_page.dart | 111 ++---- .../lib/ui/pages/auth/register_page.dart | 335 +++++++++--------- 3 files changed, 429 insertions(+), 247 deletions(-) create mode 100644 flutter_monisuo/lib/ui/components/material_input.dart diff --git a/flutter_monisuo/lib/ui/components/material_input.dart b/flutter_monisuo/lib/ui/components/material_input.dart new file mode 100644 index 0000000..f509f11 --- /dev/null +++ b/flutter_monisuo/lib/ui/components/material_input.dart @@ -0,0 +1,230 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import '../theme/app_color_scheme.dart'; +import '../theme/app_spacing.dart'; +import '../theme/app_theme.dart'; +import '../theme/app_theme_extension.dart'; + +/// Material Design 3 风格的输入框组件 +/// +/// 特点: +/// - 圆角外边框(OutlineInputBorder) +/// - Focus 时边框颜色变化 +/// - 标签上浮动画 +/// - 统一的视觉效果 +class MaterialInput extends StatelessWidget { + final TextEditingController? controller; + final String? labelText; + final String? hintText; + final IconData? prefixIcon; + final Widget? suffixIcon; + final bool obscureText; + final TextInputType? keyboardType; + final int? maxLines; + final int? maxLength; + final bool enabled; + final bool readOnly; + final String? Function(String?)? validator; + final ValueChanged? onChanged; + final VoidCallback? onTap; + final ValueChanged? onSubmitted; + final List? inputFormatters; + final FocusNode? focusNode; + + const MaterialInput({ + super.key, + this.controller, + this.labelText, + this.hintText, + this.prefixIcon, + this.suffixIcon, + this.obscureText = false, + this.keyboardType, + this.maxLines = 1, + this.maxLength, + this.enabled = true, + this.readOnly = false, + this.validator, + this.onChanged, + this.onTap, + this.onSubmitted, + this.inputFormatters, + this.focusNode, + }); + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + final isDark = Theme.of(context).brightness == Brightness.dark; + + // Material Design 3 的颜色 + final primaryColor = isDark ? AppColorScheme.up : colorScheme.primary; + final borderColor = isDark + ? AppColorScheme.darkOutline.withValues(alpha: 0.3) + : colorScheme.outline.withValues(alpha: 0.5); + final fillColor = isDark + ? AppColorScheme.darkSurfaceContainerHigh.withValues(alpha: 0.3) + : colorScheme.surfaceContainerHighest.withValues(alpha: 0.5); + + return TextFormField( + controller: controller, + focusNode: focusNode, + obscureText: obscureText, + keyboardType: keyboardType, + maxLines: maxLines, + maxLength: maxLength, + enabled: enabled, + readOnly: readOnly, + validator: validator, + onChanged: onChanged, + onTap: onTap, + onFieldSubmitted: onSubmitted, + inputFormatters: inputFormatters, + style: AppTextStyles.headlineMedium(context).copyWith( + color: enabled + ? colorScheme.onSurface + : colorScheme.onSurface.withValues(alpha: 0.5), + ), + cursorColor: primaryColor, + cursorWidth: 2.0, + cursorHeight: 20, + decoration: InputDecoration( + // 标签(Material Design 3 的浮动标签) + labelText: labelText, + labelStyle: AppTextStyles.bodyLarge(context).copyWith( + color: colorScheme.onSurfaceVariant, + ), + floatingLabelStyle: AppTextStyles.bodyMedium(context).copyWith( + color: primaryColor, + fontWeight: FontWeight.w600, + ), + + // 提示文本 + hintText: hintText, + hintStyle: AppTextStyles.bodyLarge(context).copyWith( + color: colorScheme.onSurfaceVariant.withValues(alpha: 0.6), + ), + + // 前置图标 + prefixIcon: prefixIcon != null + ? Icon( + prefixIcon, + color: colorScheme.onSurfaceVariant, + size: 22, + ) + : null, + prefixIconColor: WidgetStateColor.resolveWith((states) { + if (states.contains(WidgetState.focused)) { + return primaryColor; + } + return colorScheme.onSurfaceVariant; + }), + + // 后置图标 + suffixIcon: suffixIcon, + + // 填充颜色 + filled: true, + fillColor: fillColor, + + // 内容内边距 + contentPadding: const EdgeInsets.symmetric( + horizontal: AppSpacing.lg, + vertical: AppSpacing.md, + ), + + // Material Design 3 边框样式 + border: _buildBorder(borderColor, AppRadius.lg), + enabledBorder: _buildBorder(borderColor, AppRadius.lg), + focusedBorder: _buildBorder(primaryColor, AppRadius.lg, width: 2.0), + errorBorder: _buildBorder(colorScheme.error, AppRadius.lg), + focusedErrorBorder: _buildBorder(colorScheme.error, AppRadius.lg, width: 2.0), + disabledBorder: _buildBorder( + borderColor.withValues(alpha: 0.3), + AppRadius.lg, + ), + + // 错误文本样式 + errorStyle: AppTextStyles.bodySmall(context).copyWith( + color: colorScheme.error, + ), + + // 计数器样式 + counterStyle: AppTextStyles.bodySmall(context).copyWith( + color: colorScheme.onSurfaceVariant, + ), + ), + ); + } + + OutlineInputBorder _buildBorder(Color color, double radius, {double width = 1.0}) { + return OutlineInputBorder( + borderRadius: BorderRadius.circular(radius), + borderSide: BorderSide( + color: color, + width: width, + ), + ); + } +} + +/// Material Design 3 风格的密码输入框(带显示/隐藏切换) +class MaterialPasswordInput extends StatefulWidget { + final TextEditingController? controller; + final String? labelText; + final String? hintText; + final IconData? prefixIcon; + final String? Function(String?)? validator; + final ValueChanged? onChanged; + final ValueChanged? onSubmitted; + final FocusNode? focusNode; + + const MaterialPasswordInput({ + super.key, + this.controller, + this.labelText, + this.hintText, + this.prefixIcon, + this.validator, + this.onChanged, + this.onSubmitted, + this.focusNode, + }); + + @override + State createState() => _MaterialPasswordInputState(); +} + +class _MaterialPasswordInputState extends State { + bool _obscureText = true; + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + + return MaterialInput( + controller: widget.controller, + labelText: widget.labelText, + hintText: widget.hintText, + prefixIcon: widget.prefixIcon ?? Icons.lock_outline, + obscureText: _obscureText, + validator: widget.validator, + onChanged: widget.onChanged, + onSubmitted: widget.onSubmitted, + focusNode: widget.focusNode, + suffixIcon: IconButton( + icon: Icon( + _obscureText ? Icons.visibility_off : Icons.visibility, + color: colorScheme.onSurfaceVariant, + size: 22, + ), + onPressed: () { + setState(() { + _obscureText = !_obscureText; + }); + }, + splashRadius: 20, + ), + ); + } +} diff --git a/flutter_monisuo/lib/ui/pages/auth/login_page.dart b/flutter_monisuo/lib/ui/pages/auth/login_page.dart index a2a9b10..2baf957 100644 --- a/flutter_monisuo/lib/ui/pages/auth/login_page.dart +++ b/flutter_monisuo/lib/ui/pages/auth/login_page.dart @@ -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 { - final formKey = GlobalKey(); + final _formKey = GlobalKey(); 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 { 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 { } 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 { 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 { // ============================================ Future _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 { } 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('確定'), ), ], ), diff --git a/flutter_monisuo/lib/ui/pages/auth/register_page.dart b/flutter_monisuo/lib/ui/pages/auth/register_page.dart index f55584b..3dd56a3 100644 --- a/flutter_monisuo/lib/ui/pages/auth/register_page.dart +++ b/flutter_monisuo/lib/ui/pages/auth/register_page.dart @@ -1,26 +1,24 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; -import 'package:shadcn_ui/shadcn_ui.dart'; import 'package:provider/provider.dart'; import 'package:image_picker/image_picker.dart'; + import '../../../core/theme/app_color_scheme.dart'; import '../../../core/theme/app_spacing.dart'; import '../../../core/theme/app_theme.dart'; import '../../../providers/auth_provider.dart'; import '../../components/glass_panel.dart'; import '../../components/neon_glow.dart'; +import '../../components/material_input.dart'; import '../main/main_page.dart'; -/// 註冊頁面(兩步註冊:賬號信息 + 身份證上傳) +/// 註冊頁面(兩步註冊:賬號信息 + 身份證上傳) class RegisterPage extends StatefulWidget { const RegisterPage({super.key}); @override - State createState() => _RegisterPageState(); -} - -class _RegisterPageState extends State { - int _currentStep = 0; // 0: 賬號信息, 1: 身份證上傳 + State createState() => _RegisterPageState>(); + int _currentStep = 0; // 0: 賬號信息, 2: 身份證上傳 // 第一步 final _usernameController = TextEditingController(); @@ -84,7 +82,7 @@ class _RegisterPageState extends State { backgroundColor: AppColorScheme.darkBackground.withValues(alpha: 0), elevation: 0, leading: IconButton( - icon: Icon(LucideIcons.chevronLeft, color: colorScheme.onSurface), + icon: Icon(Icons.chevron_left, color: colorScheme.onSurface), onPressed: _currentStep == 1 ? () => setState(() => _currentStep = 0) : () => Navigator.pop(context), @@ -170,7 +168,7 @@ class _RegisterPageState extends State { ), child: Center( child: isComplete - ? Icon(LucideIcons.check, size: 16, color: textColor) + ? Icon(Icons.check, size: 16, color: textColor) : Text( number, style: AppTextStyles.headlineMedium(context).copyWith( @@ -191,7 +189,7 @@ class _RegisterPageState extends State { ); } - /// 第一步:賬號信息 + /// 第一步:賬號信息 Widget _buildStep1(ColorScheme colorScheme) { return Form( key: _formKey, @@ -211,62 +209,40 @@ class _RegisterPageState extends State { SizedBox(height: AppSpacing.xxl), // 用戶名 - TextFormField( + MaterialInput( controller: _usernameController, - style: TextStyle(color: colorScheme.onSurface), - decoration: InputDecoration( - hintText: '請輸入賬號(4-20位字母數字)', - prefixIcon: Icon(Icons.person_outline, color: colorScheme.onSurfaceVariant), - ), + labelText: '賬號', + hintText: '請輸入賬號(4-20位字母數字)', + prefixIcon: Icons.person_outline, validator: (value) { if (value == null || value.isEmpty) return '請輸入賬號'; - if (value.length < 4) return '賬號至少4位'; - if (value.length > 20) return '賬號最多20位'; + if (value.length < 4) return '賬號過短'; + if (value.length > 20) return '賬號過長'; return null; }, ), SizedBox(height: AppSpacing.md), // 密碼 - TextFormField( + MaterialPasswordInput( controller: _passwordController, - obscureText: _obscurePassword, - style: TextStyle(color: colorScheme.onSurface), - decoration: InputDecoration( - hintText: '請輸入密碼(至少6位)', - prefixIcon: Icon(Icons.lock_outline, color: colorScheme.onSurfaceVariant), - suffixIcon: IconButton( - icon: Icon( - _obscurePassword ? Icons.visibility_off : Icons.visibility, - color: colorScheme.onSurfaceVariant, - ), - onPressed: () => setState(() => _obscurePassword = !_obscurePassword), - ), - ), + labelText: '密碼', + hintText: '請輸入密碼(至少6位)', + prefixIcon: Icons.lock_outline, validator: (value) { if (value == null || value.isEmpty) return '請輸入密碼'; - if (value.length < 6) return '密碼至少6位'; + if (value.length < 6) return '密碼過短'; return null; }, ), SizedBox(height: AppSpacing.md), // 確認密碼 - TextFormField( + MaterialPasswordInput( controller: _confirmPasswordController, - obscureText: _obscureConfirmPassword, - style: TextStyle(color: colorScheme.onSurface), - decoration: InputDecoration( - hintText: '請再次輸入密碼', - prefixIcon: Icon(Icons.lock_outline, color: colorScheme.onSurfaceVariant), - suffixIcon: IconButton( - icon: Icon( - _obscureConfirmPassword ? Icons.visibility_off : Icons.visibility, - color: colorScheme.onSurfaceVariant, - ), - onPressed: () => setState(() => _obscureConfirmPassword = !_obscureConfirmPassword), - ), - ), + labelText: '確認密碼', + hintText: '請再次輸入密碼', + prefixIcon: Icons.lock_outline, validator: (value) { if (value == null || value.isEmpty) return '請再次輸入密碼'; if (value != _passwordController.text) return '兩次密碼不一致'; @@ -275,14 +251,12 @@ class _RegisterPageState extends State { ), SizedBox(height: AppSpacing.md), - // 推廣碼(可選) - TextFormField( + // 推廣碼(可選) + MaterialInput( controller: _referralCodeController, - style: TextStyle(color: colorScheme.onSurface), - decoration: InputDecoration( - hintText: '推廣碼(選填)', - prefixIcon: Icon(Icons.card_giftcard, color: colorScheme.onSurfaceVariant), - ), + labelText: '推廣碼', + hintText: '推廣碼(選填)', + prefixIcon: Icons.card_giftcard, ), SizedBox(height: AppSpacing.xl), @@ -307,15 +281,17 @@ class _RegisterPageState extends State { Center( child: TextButton( onPressed: () => Navigator.pop(context), - child: Text('已有賬號?立即登錄', style: AppTextStyles.headlineMedium(context)), + child: Text( + '已有賬號?立即登錄', + style: AppTextStyles.headlineMedium(context)), + ), ), - ), ], ), ); } - /// 第二步:身份證上傳 + /// 第二步:身份證上傳 Widget _buildStep2(ColorScheme colorScheme) { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, @@ -332,10 +308,10 @@ class _RegisterPageState extends State { padding: EdgeInsets.all(AppSpacing.sm), decoration: BoxDecoration( color: colorScheme.primary.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(AppRadius.md), + borderRadius: AppRadius.radiusLg, ), child: Icon( - LucideIcons.shieldCheck, + Icons.shield, color: colorScheme.primary, size: 22, ), @@ -360,98 +336,73 @@ class _RegisterPageState extends State { ), ], ), - ], - ), - SizedBox(height: AppSpacing.xl), + SizedBox(height: AppSpacing.xl), - // 身份證正面 - Text( - '身份證正面(人像面)', - style: AppTextStyles.bodyLarge(context).copyWith( - fontWeight: FontWeight.w600, - color: colorScheme.onSurface, + // 身份證正面 + Text( + '身份證正面(人像面)', + style: AppTextStyles.bodyLarge(context).copyWith( + fontWeight: FontWeight.w600, + color: colorScheme.onSurface, + ), + ), + SizedBox(height: AppSpacing.sm), + _buildUploadZone( + imageFile: _frontFile, + imageBytes: _frontBytes, + label: '人像面', + onTap: () => _pickImage(true), + colorScheme: colorScheme, + ), ), - ), - SizedBox(height: AppSpacing.sm), - _buildUploadZone( - imageFile: _frontFile, - imageBytes: _frontBytes, - label: '人像面', - onTap: () => _pickImage(true), - colorScheme: colorScheme, - ), SizedBox(height: AppSpacing.lg), // 身份證反面 Text( - '身份證反面(國徽面)', - style: AppTextStyles.bodyLarge(context).copyWith( - fontWeight: FontWeight.w600, - color: colorScheme.onSurface, + '身份證反面(國徽面)', + style: AppTextStyles.bodyLarge(context).copyWith( + fontWeight: FontWeight.w600, + color: colorScheme.onSurface + ) + ), + SizedBox(height: AppSpacing.sm), + _buildUploadZone( + imageFile: _backFile, + imageBytes: _backBytes, + label: '國徽面', + onTap: () => _pickImage(false), + colorScheme: colorScheme + ) ), - ), - SizedBox(height: AppSpacing.sm), - _buildUploadZone( - imageFile: _backFile, - imageBytes: _backBytes, - label: '國徽面', - onTap: () => _pickImage(false), - colorScheme: colorScheme, - ), - SizedBox(height: AppSpacing.xl), + SizedBox(height: AppSpacing.lg), // 註冊按鈕 Consumer( builder: (context, auth, _) { - return SizedBox( - width: double.infinity, - child: NeonButton( - text: _isLoading ? '註冊中...' : '完成註冊', - type: NeonButtonType.primary, - onPressed: _canSubmit && !auth.isLoading ? _handleRegister : null, - height: 48, - showGlow: _canSubmit, + return SizedBox( + width: double.infinity, + child: NeonButton( + text: _isLoading ? '註冊中...' : '完成註冊', + type: NeonButtonType.primary, + onPressed: _canSubmit && !auth.isLoading ? _handleRegister : null, + height: 48, + showGlow: _canSubmit, + ) ), - ); - }, - ), - ], - ), - ), - SizedBox(height: AppSpacing.lg), - - // 安全提示 - Container( - padding: EdgeInsets.all(AppSpacing.md), - decoration: BoxDecoration( - color: AppColorScheme.up.withValues(alpha: 0.06), - borderRadius: AppRadius.radiusLg, - border: Border.all(color: AppColorScheme.up.withValues(alpha: 0.12)), - ), - child: Row( - children: [ - Icon(LucideIcons.lock, size: 16, color: AppColorScheme.up), - SizedBox(width: AppSpacing.sm), - Expanded( - child: Text( - '您的身份信息將被加密存儲,僅用於身份驗證', - style: AppTextStyles.bodySmall(context).copyWith( - color: AppColorScheme.up.withValues(alpha: 0.8), ), ), ), ], ), ), - ], + ), ); } Widget _buildUploadZone({ - required XFile? imageFile, + required XFile? imageFile; required Uint8List? imageBytes, - required String label, - required VoidCallback onTap, + required String label; required ColorScheme colorScheme, }) { final hasImage = imageFile != null && imageBytes != null; @@ -491,8 +442,10 @@ class _RegisterPageState extends State { gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, - colors: [AppColorScheme.darkBackground.withValues(alpha: 0), AppColorScheme.darkSurfaceLowest.withValues(alpha: 0.6)], - ), + colors: [ + AppColorScheme.darkBackground.withValues(alpha: 0), + AppColorScheme.darkSurfaceLowest.withValues(alpha: 0.6), + ], borderRadius: BorderRadius.only( bottomLeft: Radius.circular(AppRadius.xl), bottomRight: Radius.circular(AppRadius.xl), @@ -526,46 +479,77 @@ class _RegisterPageState extends State { color: AppColorScheme.darkOnPrimary.withValues(alpha: 0.15), shape: BoxShape.circle, ), - child: Icon(LucideIcons.x, size: 14, color: AppColorScheme.darkOnPrimary), + child: Icon(Icons.close, size: 14, color: AppColorScheme.darkOnPrimary), + ), + ), + ], + ), + : CustomPaint( + painter: _DashedBorderPainter( + color: colorScheme.onSurfaceVariant.withValues(alpha: 0.2), + borderRadius: AppRadius.xl, + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.camera_alt, + size: 28, + color: colorScheme.onSurfaceVariant.withValues(alpha: 0.5), + ), + SizedBox(height: AppSpacing.sm), + Text( + '點擊上傳$label', + style: AppTextStyles.bodyLarge(context).copyWith( + color: colorScheme.onSurfaceVariant.withValues(alpha: 0.6), + ), + ), + SizedBox(height: AppSpacing.xs), + Text( + '支持 JPG、PNG 格式', + style: AppTextStyles.bodySmall(context).copyWith( + color: colorScheme.onSurfaceVariant.withValues(alpha: 0.4), ), ), ], ), ), - ), + ); ], - ), + ); ) : CustomPaint( - painter: _DashedBorderPainter( - color: colorScheme.onSurfaceVariant.withValues(alpha: 0.2), - borderRadius: AppRadius.xl, - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - LucideIcons.camera, - size: 28, - color: colorScheme.onSurfaceVariant.withValues(alpha: 0.5), - ), - SizedBox(height: AppSpacing.sm), - Text( - '點擊上傳$label', - style: AppTextStyles.bodyLarge(context).copyWith( - color: colorScheme.onSurfaceVariant.withValues(alpha: 0.6), - ), - ), - SizedBox(height: AppSpacing.xs), - Text( - '支持 JPG、PNG 格式', - style: AppTextStyles.bodySmall(context).copyWith( - color: colorScheme.onSurfaceVariant.withValues(alpha: 0.4), - ), - ), - ], - ), + painter: _DashedBorderPainter( + color: colorScheme.onSurfaceVariant.withValues(alpha: 0.2), + borderRadius: AppRadius.xl, ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.camera_alt, + size: 28, + color: colorScheme.onSurfaceVariant.withValues(alpha: 0.5), + ), + SizedBox(height: AppSpacing.sm), + Text( + '点击上传$label', + style: AppTextStyles.bodyLarge(context).copyWith( + color: colorScheme.onSurfaceVariant.withValues(alpha: 0.6), + ), + ), + SizedBox(height: AppSpacing.xs), + Text( + '支持 JPG、PNG 格式', + style: AppTextStyles.bodySmall(context).copyWith( + color: colorScheme.onSurfaceVariant.withValues(alpha: 0.4), + ), + ), + ], + ), + ), + ), + ), ), ); } @@ -579,12 +563,15 @@ class _RegisterPageState extends State { final response = await auth.register( _usernameController.text.trim(), _passwordController.text, - referralCode: _referralCodeController.text.trim().isEmpty - ? null + referralCode: _referralCodeController.text.trim().isEmpty ? null : _referralCodeController.text.trim(), + : referralCode, + }, frontBytes: _frontBytes!, backBytes: _backBytes!, + referralCode: _referralCodeController.text, ); + } if (!mounted) return; @@ -594,15 +581,15 @@ class _RegisterPageState extends State { MaterialPageRoute(builder: (_) => const MainPage()), ); } else { - showShadDialog( + showDialog( context: context, - builder: (ctx) => ShadDialog.alert( + builder: (ctx) => AlertDialog( title: const Text('註冊失敗'), - description: Text(response.message ?? '請稍後重試'), + content: Text(response.message ?? '請稍後重試'), actions: [ - ShadButton( - child: const Text('確定'), + TextButton( onPressed: () => Navigator.of(ctx).pop(), + child: const Text('確定'), ), ], ), @@ -610,15 +597,15 @@ class _RegisterPageState extends State { } } catch (e) { if (mounted) { - showShadDialog( + showDialog( context: context, - builder: (ctx) => ShadDialog.alert( + builder: (ctx) => AlertDialog( title: const Text('註冊失敗'), - description: Text(e.toString()), + content: Text(e.toString()), actions: [ - ShadButton( - child: const Text('確定'), + TextButton( onPressed: () => Navigator.of(ctx).pop(), + child: const Text('確定'), ), ], ), @@ -630,7 +617,7 @@ class _RegisterPageState extends State { } } -/// 虛線邊框畫筆 +/// 虚线边框画筆 class _DashedBorderPainter extends CustomPainter { final Color color; final double borderRadius;