feat(theme): update color scheme with new Slate theme and improved surface hierarchy
Updated the app's color scheme to implement a new "Slate" theme with refined dark and light variants. Changed background colors from #0A0E14 to #0B1120 for dark mode and updated surface layer colors to follow Material Design 3 specifications. Modified text colors and outline variants for better contrast and accessibility. Updated font sizes in transaction details screen from 11px to 12px for improved readability.
This commit is contained in:
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../../core/theme/app_color_scheme.dart';
|
||||
import '../../../core/theme/app_spacing.dart';
|
||||
import '../../../providers/auth_provider.dart';
|
||||
import '../main/main_page.dart';
|
||||
@@ -16,38 +17,52 @@ class LoginPage extends StatefulWidget {
|
||||
|
||||
class _LoginPageState extends State<LoginPage> {
|
||||
final formKey = GlobalKey<ShadFormState>();
|
||||
final _usernameController = TextEditingController();
|
||||
final _passwordController = TextEditingController();
|
||||
bool _obscurePassword = true;
|
||||
|
||||
static const _maxFormWidth = 400.0;
|
||||
static const _logoSize = 64.0;
|
||||
static const _loadingIndicatorSize = 16.0;
|
||||
static const _logoCircleSize = 80.0;
|
||||
static const _inputHeight = 52.0;
|
||||
static const _buttonHeight = 52.0;
|
||||
|
||||
/// 设计稿 radius-lg = 14
|
||||
static const _designRadiusLg = 14.0;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_usernameController.dispose();
|
||||
_passwordController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = ShadTheme.of(context);
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: _maxFormWidth),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(AppSpacing.lg),
|
||||
child: ShadForm(
|
||||
key: formKey,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_buildHeader(theme),
|
||||
SizedBox(height: AppSpacing.xxl),
|
||||
_buildUsernameField(),
|
||||
SizedBox(height: AppSpacing.md),
|
||||
_buildPasswordField(),
|
||||
SizedBox(height: AppSpacing.lg),
|
||||
_buildLoginButton(),
|
||||
SizedBox(height: AppSpacing.md),
|
||||
_buildRegisterLink(theme),
|
||||
],
|
||||
),
|
||||
backgroundColor: isDark
|
||||
? AppColorScheme.darkBackground
|
||||
: AppColorScheme.lightSurface,
|
||||
body: SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: AppSpacing.xl,
|
||||
vertical: AppSpacing.xxl,
|
||||
),
|
||||
child: ShadForm(
|
||||
key: formKey,
|
||||
child: Column(
|
||||
children: [
|
||||
// 顶部品牌区域
|
||||
_buildBrandSection(isDark),
|
||||
const SizedBox(height: AppSpacing.xxl),
|
||||
// 表单区域
|
||||
_buildFormSection(isDark),
|
||||
const SizedBox(height: AppSpacing.xl),
|
||||
// 底部注册链接
|
||||
_buildRegisterRow(isDark),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -55,87 +70,258 @@ class _LoginPageState extends State<LoginPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader(ShadThemeData theme) {
|
||||
// ============================================
|
||||
// 品牌区域 - Logo + 品牌名 + 标语
|
||||
// ============================================
|
||||
|
||||
Widget _buildBrandSection(bool isDark) {
|
||||
return Column(
|
||||
children: [
|
||||
Icon(
|
||||
LucideIcons.trendingUp,
|
||||
size: _logoSize,
|
||||
color: theme.colorScheme.primary,
|
||||
// Logo 圆形:渐变 #1F2937 → #374151,内含 "M"
|
||||
Container(
|
||||
width: _logoCircleSize,
|
||||
height: _logoCircleSize,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: const LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xFF1F2937), Color(0xFF374151)],
|
||||
),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
'M',
|
||||
style: TextStyle(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: isDark
|
||||
? AppColorScheme.darkOnSurface
|
||||
: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: AppSpacing.lg),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
// 品牌名 "MONISUO"
|
||||
Text(
|
||||
'模拟所',
|
||||
style: theme.textTheme.h1,
|
||||
'MONISUO',
|
||||
style: TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.w800,
|
||||
letterSpacing: 3,
|
||||
color: isDark
|
||||
? AppColorScheme.darkOnSurface
|
||||
: AppColorScheme.lightOnSurface,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
SizedBox(height: AppSpacing.sm),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
// 标语
|
||||
Text(
|
||||
'虚拟货币模拟交易平台',
|
||||
style: theme.textTheme.muted,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: isDark
|
||||
? AppColorScheme.darkOnSurfaceVariant
|
||||
: AppColorScheme.lightOnSurfaceVariant,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildUsernameField() {
|
||||
return ShadInputFormField(
|
||||
id: 'username',
|
||||
label: const Text('用户名'),
|
||||
placeholder: const Text('请输入用户名'),
|
||||
leading: const Icon(LucideIcons.user),
|
||||
validator: _validateUsername,
|
||||
// ============================================
|
||||
// 表单区域 - 用户名 + 密码 + 登录按钮
|
||||
// ============================================
|
||||
|
||||
Widget _buildFormSection(bool isDark) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_buildUsernameField(isDark),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
_buildPasswordField(isDark),
|
||||
const SizedBox(height: AppSpacing.sm),
|
||||
_buildLoginButton(isDark),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPasswordField() {
|
||||
return ShadInputFormField(
|
||||
id: 'password',
|
||||
label: const Text('密码'),
|
||||
placeholder: const Text('请输入密码'),
|
||||
obscureText: true,
|
||||
leading: const Icon(LucideIcons.lock),
|
||||
validator: _validatePassword,
|
||||
Widget _buildUsernameField(bool isDark) {
|
||||
final borderColor = isDark
|
||||
? AppColorScheme.darkOutlineVariant
|
||||
: AppColorScheme.lightOutlineVariant;
|
||||
final cardColor = isDark
|
||||
? AppColorScheme.darkSurfaceContainer
|
||||
: AppColorScheme.lightSurfaceLowest;
|
||||
final iconColor = isDark
|
||||
? AppColorScheme.darkOnSurfaceMuted
|
||||
: AppColorScheme.lightOnSurfaceMuted;
|
||||
|
||||
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: iconColor),
|
||||
),
|
||||
validator: _validateUsername,
|
||||
controller: _usernameController,
|
||||
decoration: ShadDecoration(
|
||||
border: ShadBorder.all(
|
||||
color: borderColor,
|
||||
radius: BorderRadius.circular(_designRadiusLg),
|
||||
),
|
||||
),
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: isDark
|
||||
? AppColorScheme.darkOnSurface
|
||||
: AppColorScheme.lightOnSurface,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLoginButton() {
|
||||
Widget _buildPasswordField(bool isDark) {
|
||||
final borderColor = isDark
|
||||
? AppColorScheme.darkOutlineVariant
|
||||
: AppColorScheme.lightOutlineVariant;
|
||||
final iconColor = isDark
|
||||
? AppColorScheme.darkOnSurfaceMuted
|
||||
: AppColorScheme.lightOnSurfaceMuted;
|
||||
|
||||
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: borderColor,
|
||||
radius: BorderRadius.circular(_designRadiusLg),
|
||||
),
|
||||
),
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: isDark
|
||||
? AppColorScheme.darkOnSurface
|
||||
: AppColorScheme.lightOnSurface,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLoginButton(bool isDark) {
|
||||
// 设计稿: accent-primary = light:#1F2937 / dark:#D4AF37
|
||||
final buttonColor = isDark
|
||||
? AppColorScheme.darkSecondary
|
||||
: const Color(0xFF1F2937);
|
||||
final textColor = isDark
|
||||
? AppColorScheme.darkBackground
|
||||
: Colors.white;
|
||||
|
||||
return Consumer<AuthProvider>(
|
||||
builder: (context, auth, _) {
|
||||
return ShadButton(
|
||||
onPressed: auth.isLoading ? null : () => _handleLogin(auth),
|
||||
child: auth.isLoading
|
||||
? const SizedBox.square(
|
||||
dimension: _loadingIndicatorSize,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: Colors.white,
|
||||
return SizedBox(
|
||||
height: _buttonHeight,
|
||||
child: ShadButton(
|
||||
onPressed: auth.isLoading ? null : () => _handleLogin(auth),
|
||||
backgroundColor: buttonColor,
|
||||
foregroundColor: textColor,
|
||||
decoration: ShadDecoration(
|
||||
border: ShadBorder.all(
|
||||
radius: BorderRadius.circular(_designRadiusLg),
|
||||
),
|
||||
),
|
||||
child: auth.isLoading
|
||||
? SizedBox.square(
|
||||
dimension: _loadingIndicatorSize,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: textColor,
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
'登录',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: textColor,
|
||||
),
|
||||
),
|
||||
)
|
||||
: const Text('登录'),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRegisterLink(ShadThemeData theme) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'还没有账号?',
|
||||
style: theme.textTheme.muted,
|
||||
),
|
||||
ShadButton.link(
|
||||
onPressed: _navigateToRegister,
|
||||
child: const Text('立即注册'),
|
||||
),
|
||||
],
|
||||
// ============================================
|
||||
// 底部注册链接
|
||||
// ============================================
|
||||
|
||||
Widget _buildRegisterRow(bool isDark) {
|
||||
// gold-accent: light=#F59E0B / dark=#D4AF37
|
||||
final goldColor = isDark
|
||||
? AppColorScheme.darkSecondary
|
||||
: const Color(0xFFF59E0B);
|
||||
final secondaryTextColor = isDark
|
||||
? AppColorScheme.darkOnSurfaceVariant
|
||||
: AppColorScheme.lightOnSurfaceVariant;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: AppSpacing.xl),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'还没有账户?',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: secondaryTextColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: AppSpacing.xs),
|
||||
GestureDetector(
|
||||
onTap: _navigateToRegister,
|
||||
child: Text(
|
||||
'立即注册',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: goldColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Validators
|
||||
// ============================================
|
||||
|
||||
String? _validateUsername(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return '请输入用户名';
|
||||
@@ -156,7 +342,10 @@ class _LoginPageState extends State<LoginPage> {
|
||||
return null;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Actions
|
||||
// ============================================
|
||||
|
||||
Future<void> _handleLogin(AuthProvider auth) async {
|
||||
if (!formKey.currentState!.saveAndValidate()) return;
|
||||
|
||||
@@ -176,7 +365,6 @@ class _LoginPageState extends State<LoginPage> {
|
||||
}
|
||||
|
||||
void _navigateToMainPage() {
|
||||
// 使用 Navigator 跳转到主页面,替换当前页面
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(builder: (context) => const MainPage()),
|
||||
(route) => false,
|
||||
|
||||
Reference in New Issue
Block a user