111
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
@@ -7,6 +8,7 @@ import 'package:google_fonts/google_fonts.dart';
|
||||
import '../../../core/theme/app_color_scheme.dart';
|
||||
import '../../../core/theme/app_spacing.dart';
|
||||
import '../../../core/utils/toast_utils.dart';
|
||||
import '../../../core/event/app_event_bus.dart';
|
||||
import '../../../providers/asset_provider.dart';
|
||||
import '../../../providers/auth_provider.dart';
|
||||
import '../../shared/ui_constants.dart';
|
||||
@@ -14,7 +16,6 @@ import '../../components/glass_panel.dart';
|
||||
import '../../components/neon_glow.dart';
|
||||
import '../orders/fund_orders_page.dart';
|
||||
import 'transfer_page.dart';
|
||||
import '../mine/kyc_page.dart';
|
||||
|
||||
/// 资产页面 - Material Design 3 风格
|
||||
class AssetPage extends StatefulWidget {
|
||||
@@ -26,6 +27,7 @@ class AssetPage extends StatefulWidget {
|
||||
|
||||
class _AssetPageState extends State<AssetPage> with AutomaticKeepAliveClientMixin {
|
||||
int _activeTab = 0;
|
||||
StreamSubscription<AppEvent>? _eventSub;
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
@@ -33,12 +35,28 @@ class _AssetPageState extends State<AssetPage> with AutomaticKeepAliveClientMixi
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// 强制刷新数据,确保加载最新数据
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _loadData());
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_loadData();
|
||||
_listenEvents();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_eventSub?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _listenEvents() {
|
||||
final eventBus = context.read<AppEventBus>();
|
||||
_eventSub = eventBus.on(AppEventType.assetChanged, (_) {
|
||||
if (mounted) {
|
||||
context.read<AssetProvider>().refreshAll(force: true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _loadData() {
|
||||
// 强制刷新,不使用缓存
|
||||
context.read<AssetProvider>().refreshAll(force: true);
|
||||
}
|
||||
|
||||
@@ -628,9 +646,15 @@ void _showDepositDialog(BuildContext context) {
|
||||
id: 'amount',
|
||||
controller: amountController,
|
||||
label: const Text('充值金额'),
|
||||
placeholder: const Text('0.00'),
|
||||
placeholder: const Text('最低 1000 USDT'),
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||
validator: Validators.amount,
|
||||
validator: (v) {
|
||||
if (v == null || v.isEmpty) return '请输入金额';
|
||||
final n = double.tryParse(v);
|
||||
if (n == null || n <= 0) return '请输入有效金额';
|
||||
if (n < 1000) return '单笔最低充值1000 USDT';
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
SizedBox(height: AppSpacing.lg),
|
||||
@@ -896,13 +920,6 @@ class _WalletAddressCard extends StatelessWidget {
|
||||
}
|
||||
|
||||
void _showWithdrawDialog(BuildContext context, String? balance) {
|
||||
// KYC校验:未完成实名认证则引导去认证
|
||||
final auth = context.read<AuthProvider>();
|
||||
if (auth.user?.kycStatus != 2) {
|
||||
_showKycRequiredDialog(context);
|
||||
return;
|
||||
}
|
||||
|
||||
final amountController = TextEditingController();
|
||||
final addressController = TextEditingController();
|
||||
final contactController = TextEditingController();
|
||||
@@ -1145,84 +1162,3 @@ void _showResultDialog(BuildContext context, String title, String? message) {
|
||||
);
|
||||
}
|
||||
|
||||
void _showKycRequiredDialog(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
showShadDialog(
|
||||
context: context,
|
||||
builder: (ctx) => Dialog(
|
||||
backgroundColor: Colors.transparent,
|
||||
child: GlassPanel(
|
||||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||||
padding: EdgeInsets.all(AppSpacing.lg),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(AppSpacing.md),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColorScheme.warning.withOpacity(0.1),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
LucideIcons.shieldAlert,
|
||||
color: AppColorScheme.warning,
|
||||
size: 32,
|
||||
),
|
||||
),
|
||||
SizedBox(height: AppSpacing.md),
|
||||
Text(
|
||||
'需要实名认证',
|
||||
style: GoogleFonts.spaceGrotesk(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
SizedBox(height: AppSpacing.sm),
|
||||
Text(
|
||||
'提现前请先完成实名认证,保障账户安全',
|
||||
style: TextStyle(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
fontSize: 14,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
SizedBox(height: AppSpacing.lg),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: NeonButton(
|
||||
text: '取消',
|
||||
type: NeonButtonType.outline,
|
||||
onPressed: () => Navigator.of(ctx).pop(),
|
||||
height: 44,
|
||||
showGlow: false,
|
||||
),
|
||||
),
|
||||
SizedBox(width: AppSpacing.sm),
|
||||
Expanded(
|
||||
child: NeonButton(
|
||||
text: '去认证',
|
||||
type: NeonButtonType.primary,
|
||||
onPressed: () {
|
||||
Navigator.of(ctx).pop();
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => const KycPage(returnToWithdraw: true),
|
||||
),
|
||||
);
|
||||
},
|
||||
height: 44,
|
||||
showGlow: true,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import 'dart:typed_data';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import '../../../core/theme/app_color_scheme.dart';
|
||||
import '../../../core/theme/app_spacing.dart';
|
||||
import '../../../providers/auth_provider.dart';
|
||||
import '../../components/glass_panel.dart';
|
||||
import '../../components/neon_glow.dart';
|
||||
import '../main/main_page.dart';
|
||||
|
||||
/// 注册页面
|
||||
/// 注册页面(两步注册:账号信息 + 身份证上传)
|
||||
class RegisterPage extends StatefulWidget {
|
||||
const RegisterPage({super.key});
|
||||
|
||||
@@ -14,211 +20,666 @@ class RegisterPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _RegisterPageState extends State<RegisterPage> {
|
||||
int _currentStep = 0; // 0: 账号信息, 1: 身份证上传
|
||||
|
||||
// 第一步
|
||||
final _usernameController = TextEditingController();
|
||||
final _passwordController = TextEditingController();
|
||||
final _confirmPasswordController = TextEditingController();
|
||||
final _referralCodeController = TextEditingController();
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
bool _obscurePassword = true;
|
||||
bool _obscureConfirmPassword = true;
|
||||
|
||||
// 第二步
|
||||
XFile? _frontFile;
|
||||
XFile? _backFile;
|
||||
Uint8List? _frontBytes;
|
||||
Uint8List? _backBytes;
|
||||
|
||||
final _picker = ImagePicker();
|
||||
|
||||
bool get _canSubmit =>
|
||||
_frontFile != null && _backFile != null && !_isLoading;
|
||||
|
||||
bool _isLoading = false;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_usernameController.dispose();
|
||||
_passwordController.dispose();
|
||||
_confirmPasswordController.dispose();
|
||||
_referralCodeController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _pickImage(bool isFront) async {
|
||||
final picked = await _picker.pickImage(
|
||||
source: ImageSource.gallery,
|
||||
maxWidth: 1920,
|
||||
maxHeight: 1920,
|
||||
imageQuality: 85,
|
||||
);
|
||||
if (picked != null) {
|
||||
final bytes = await picked.readAsBytes();
|
||||
setState(() {
|
||||
if (isFront) {
|
||||
_frontFile = picked;
|
||||
_frontBytes = bytes;
|
||||
} else {
|
||||
_backFile = picked;
|
||||
_backBytes = bytes;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: AppColorScheme.darkBackground,
|
||||
backgroundColor: colorScheme.background,
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back, color: AppColorScheme.darkOnSurface),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
icon: Icon(LucideIcons.chevronLeft, color: colorScheme.onSurface),
|
||||
onPressed: _currentStep == 1
|
||||
? () => setState(() => _currentStep = 0)
|
||||
: () => Navigator.pop(context),
|
||||
),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(AppSpacing.lg),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// Logo
|
||||
const Center(
|
||||
child: Text(
|
||||
'\u20BF',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
color: AppColorScheme.darkPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Center(
|
||||
child: Text(
|
||||
'注册账号',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColorScheme.darkOnSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 48),
|
||||
// 用户名
|
||||
TextFormField(
|
||||
controller: _usernameController,
|
||||
style: const TextStyle(color: AppColorScheme.darkOnSurface),
|
||||
decoration: const InputDecoration(
|
||||
hintText: '请输入账号(4-20位字母数字)',
|
||||
prefixIcon: Icon(Icons.person_outline, color: AppColorScheme.darkOnSurfaceVariant),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return '请输入账号';
|
||||
}
|
||||
if (value.length < 4) {
|
||||
return '账号至少4位';
|
||||
}
|
||||
if (value.length > 20) {
|
||||
return '账号最多20位';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// 密码
|
||||
TextFormField(
|
||||
controller: _passwordController,
|
||||
obscureText: _obscurePassword,
|
||||
style: const TextStyle(color: AppColorScheme.darkOnSurface),
|
||||
decoration: InputDecoration(
|
||||
hintText: '请输入密码(至少6位)',
|
||||
prefixIcon: const Icon(Icons.lock_outline, color: AppColorScheme.darkOnSurfaceVariant),
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
_obscurePassword ? Icons.visibility_off : Icons.visibility,
|
||||
color: AppColorScheme.darkOnSurfaceVariant,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_obscurePassword = !_obscurePassword;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return '请输入密码';
|
||||
}
|
||||
if (value.length < 6) {
|
||||
return '密码至少6位';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// 确认密码
|
||||
TextFormField(
|
||||
controller: _confirmPasswordController,
|
||||
obscureText: _obscureConfirmPassword,
|
||||
style: const TextStyle(color: AppColorScheme.darkOnSurface),
|
||||
decoration: InputDecoration(
|
||||
hintText: '请再次输入密码',
|
||||
prefixIcon: const Icon(Icons.lock_outline, color: AppColorScheme.darkOnSurfaceVariant),
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
_obscureConfirmPassword ? Icons.visibility_off : Icons.visibility,
|
||||
color: AppColorScheme.darkOnSurfaceVariant,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_obscureConfirmPassword = !_obscureConfirmPassword;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return '请再次输入密码';
|
||||
}
|
||||
if (value != _passwordController.text) {
|
||||
return '两次密码不一致';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
// 注册按钮
|
||||
Consumer<AuthProvider>(
|
||||
builder: (context, auth, _) {
|
||||
return ElevatedButton(
|
||||
onPressed: auth.isLoading ? null : _handleRegister,
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.xxl),
|
||||
),
|
||||
),
|
||||
child: auth.isLoading
|
||||
? const SizedBox(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: Colors.white,
|
||||
),
|
||||
)
|
||||
: const Text('注 册', style: TextStyle(fontSize: 16)),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// 登录链接
|
||||
Center(
|
||||
child: TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text(
|
||||
'已有账号?立即登录',
|
||||
style: TextStyle(fontSize: 14),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
padding: AppSpacing.pagePadding,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// 步骤指示器
|
||||
_buildStepIndicator(colorScheme),
|
||||
SizedBox(height: AppSpacing.xl),
|
||||
|
||||
// 内容区
|
||||
_currentStep == 0 ? _buildStep1(colorScheme) : _buildStep2(colorScheme),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _handleRegister() async {
|
||||
if (!_formKey.currentState!.validate()) return;
|
||||
|
||||
final auth = context.read<AuthProvider>();
|
||||
final response = await auth.register(
|
||||
_usernameController.text.trim(),
|
||||
_passwordController.text,
|
||||
Widget _buildStepIndicator(ColorScheme colorScheme) {
|
||||
return Row(
|
||||
children: [
|
||||
_buildStepCircle(
|
||||
number: '1',
|
||||
label: '账号信息',
|
||||
isActive: true,
|
||||
isComplete: _currentStep > 0,
|
||||
colorScheme: colorScheme,
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
height: 2,
|
||||
color: _currentStep > 0
|
||||
? AppColorScheme.up
|
||||
: colorScheme.outlineVariant.withOpacity(0.2),
|
||||
),
|
||||
),
|
||||
_buildStepCircle(
|
||||
number: '2',
|
||||
label: '身份验证',
|
||||
isActive: _currentStep >= 1,
|
||||
isComplete: false,
|
||||
colorScheme: colorScheme,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
if (response.success && mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('注册成功')),
|
||||
);
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const MainPage()),
|
||||
);
|
||||
} else if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(response.message ?? '注册失败')),
|
||||
Widget _buildStepCircle({
|
||||
required String number,
|
||||
required String label,
|
||||
required bool isActive,
|
||||
required bool isComplete,
|
||||
required ColorScheme colorScheme,
|
||||
}) {
|
||||
final Color circleColor;
|
||||
final Color textColor;
|
||||
|
||||
if (isComplete) {
|
||||
circleColor = AppColorScheme.up;
|
||||
textColor = Colors.white;
|
||||
} else if (isActive) {
|
||||
circleColor = colorScheme.primary;
|
||||
textColor = Colors.white;
|
||||
} else {
|
||||
circleColor = colorScheme.surfaceContainerHigh;
|
||||
textColor = colorScheme.onSurfaceVariant;
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: circleColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Center(
|
||||
child: isComplete
|
||||
? Icon(LucideIcons.check, size: 16, color: textColor)
|
||||
: Text(
|
||||
number,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: textColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// 第一步:账号信息
|
||||
Widget _buildStep1(ColorScheme colorScheme) {
|
||||
return Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// 标题
|
||||
Center(
|
||||
child: Text(
|
||||
'创建账号',
|
||||
style: GoogleFonts.spaceGrotesk(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 48),
|
||||
|
||||
// 用户名
|
||||
TextFormField(
|
||||
controller: _usernameController,
|
||||
style: TextStyle(color: colorScheme.onSurface),
|
||||
decoration: InputDecoration(
|
||||
hintText: '请输入账号(4-20位字母数字)',
|
||||
prefixIcon: Icon(Icons.person_outline, color: colorScheme.onSurfaceVariant),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) return '请输入账号';
|
||||
if (value.length < 4) return '账号至少4位';
|
||||
if (value.length > 20) return '账号最多20位';
|
||||
return null;
|
||||
},
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
|
||||
// 密码
|
||||
TextFormField(
|
||||
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),
|
||||
),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) return '请输入密码';
|
||||
if (value.length < 6) return '密码至少6位';
|
||||
return null;
|
||||
},
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
|
||||
// 确认密码
|
||||
TextFormField(
|
||||
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),
|
||||
),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) return '请再次输入密码';
|
||||
if (value != _passwordController.text) return '两次密码不一致';
|
||||
return null;
|
||||
},
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
|
||||
// 推广码(可选)
|
||||
TextFormField(
|
||||
controller: _referralCodeController,
|
||||
style: TextStyle(color: colorScheme.onSurface),
|
||||
decoration: InputDecoration(
|
||||
hintText: '推广码(选填)',
|
||||
prefixIcon: Icon(Icons.card_giftcard, color: colorScheme.onSurfaceVariant),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 32),
|
||||
|
||||
// 下一步按钮
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: NeonButton(
|
||||
text: '下一步',
|
||||
type: NeonButtonType.primary,
|
||||
onPressed: () {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
setState(() => _currentStep = 1);
|
||||
}
|
||||
},
|
||||
height: 48,
|
||||
showGlow: true,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
|
||||
// 登录链接
|
||||
Center(
|
||||
child: TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('已有账号?立即登录', style: TextStyle(fontSize: 14)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 第二步:身份证上传
|
||||
Widget _buildStep2(ColorScheme colorScheme) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// 标题区
|
||||
GlassPanel(
|
||||
padding: EdgeInsets.all(AppSpacing.lg),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(AppSpacing.sm),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.primary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(AppRadius.md),
|
||||
),
|
||||
child: Icon(
|
||||
LucideIcons.shieldCheck,
|
||||
color: colorScheme.primary,
|
||||
size: 22,
|
||||
),
|
||||
),
|
||||
SizedBox(width: AppSpacing.md),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'身份验证',
|
||||
style: GoogleFonts.spaceGrotesk(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 2),
|
||||
Text(
|
||||
'上传身份证正反面完成注册',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: AppSpacing.xl),
|
||||
|
||||
// 身份证正面
|
||||
Text(
|
||||
'身份证正面(人像面)',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
SizedBox(height: AppSpacing.sm),
|
||||
_buildUploadZone(
|
||||
imageFile: _frontFile,
|
||||
imageBytes: _frontBytes,
|
||||
label: '人像面',
|
||||
onTap: () => _pickImage(true),
|
||||
colorScheme: colorScheme,
|
||||
),
|
||||
SizedBox(height: AppSpacing.lg),
|
||||
|
||||
// 身份证反面
|
||||
Text(
|
||||
'身份证反面(国徽面)',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
SizedBox(height: AppSpacing.sm),
|
||||
_buildUploadZone(
|
||||
imageFile: _backFile,
|
||||
imageBytes: _backBytes,
|
||||
label: '国徽面',
|
||||
onTap: () => _pickImage(false),
|
||||
colorScheme: colorScheme,
|
||||
),
|
||||
SizedBox(height: AppSpacing.xl),
|
||||
|
||||
// 注册按钮
|
||||
Consumer<AuthProvider>(
|
||||
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,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: AppSpacing.lg),
|
||||
|
||||
// 安全提示
|
||||
Container(
|
||||
padding: EdgeInsets.all(AppSpacing.md),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColorScheme.up.withOpacity(0.06),
|
||||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||||
border: Border.all(color: AppColorScheme.up.withOpacity(0.12)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(LucideIcons.lock, size: 16, color: AppColorScheme.up),
|
||||
SizedBox(width: AppSpacing.sm),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'您的身份信息将被加密存储,仅用于身份验证',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: AppColorScheme.up.withOpacity(0.8),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildUploadZone({
|
||||
required XFile? imageFile,
|
||||
required Uint8List? imageBytes,
|
||||
required String label,
|
||||
required VoidCallback onTap,
|
||||
required ColorScheme colorScheme,
|
||||
}) {
|
||||
final hasImage = imageFile != null && imageBytes != null;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
width: double.infinity,
|
||||
height: 140,
|
||||
decoration: BoxDecoration(
|
||||
color: hasImage
|
||||
? AppColorScheme.up.withOpacity(0.06)
|
||||
: colorScheme.surfaceContainerHigh.withOpacity(0.3),
|
||||
borderRadius: BorderRadius.circular(AppRadius.xl),
|
||||
border: Border.all(
|
||||
color: hasImage ? AppColorScheme.up.withOpacity(0.3) : Colors.transparent,
|
||||
),
|
||||
),
|
||||
child: hasImage
|
||||
? ClipRRect(
|
||||
borderRadius: BorderRadius.circular(AppRadius.xl),
|
||||
child: Stack(
|
||||
fit: StackFit.passthrough,
|
||||
children: [
|
||||
Image.memory(imageBytes!, fit: BoxFit.cover),
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: AppSpacing.sm,
|
||||
horizontal: AppSpacing.md,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Colors.transparent, Colors.black54],
|
||||
),
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(AppRadius.xl),
|
||||
bottomRight: Radius.circular(AppRadius.xl),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'$label已选择',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
if (label == '人像面') {
|
||||
_frontFile = null;
|
||||
_frontBytes = null;
|
||||
} else {
|
||||
_backFile = null;
|
||||
_backBytes = null;
|
||||
}
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white24,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(LucideIcons.x, size: 14, color: Colors.white),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: CustomPaint(
|
||||
painter: _DashedBorderPainter(
|
||||
color: colorScheme.onSurfaceVariant.withOpacity(0.2),
|
||||
borderRadius: AppRadius.xl,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
LucideIcons.camera,
|
||||
size: 28,
|
||||
color: colorScheme.onSurfaceVariant.withOpacity(0.5),
|
||||
),
|
||||
SizedBox(height: AppSpacing.sm),
|
||||
Text(
|
||||
'点击上传$label',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: colorScheme.onSurfaceVariant.withOpacity(0.6),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Text(
|
||||
'支持 JPG、PNG 格式',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: colorScheme.onSurfaceVariant.withOpacity(0.4),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _handleRegister() async {
|
||||
if (!_canSubmit) return;
|
||||
|
||||
setState(() => _isLoading = true);
|
||||
try {
|
||||
final auth = context.read<AuthProvider>();
|
||||
final response = await auth.register(
|
||||
_usernameController.text.trim(),
|
||||
_passwordController.text,
|
||||
referralCode: _referralCodeController.text.trim().isEmpty
|
||||
? null
|
||||
: _referralCodeController.text.trim(),
|
||||
frontBytes: _frontBytes!,
|
||||
backBytes: _backBytes!,
|
||||
);
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
if (response.success) {
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const MainPage()),
|
||||
);
|
||||
} else {
|
||||
showShadDialog(
|
||||
context: context,
|
||||
builder: (ctx) => ShadDialog.alert(
|
||||
title: const Text('注册失败'),
|
||||
description: Text(response.message ?? '请稍后重试'),
|
||||
actions: [
|
||||
ShadButton(
|
||||
child: const Text('确定'),
|
||||
onPressed: () => Navigator.of(ctx).pop(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
showShadDialog(
|
||||
context: context,
|
||||
builder: (ctx) => ShadDialog.alert(
|
||||
title: const Text('注册失败'),
|
||||
description: Text(e.toString()),
|
||||
actions: [
|
||||
ShadButton(
|
||||
child: const Text('确定'),
|
||||
onPressed: () => Navigator.of(ctx).pop(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) setState(() => _isLoading = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 虚线边框画笔
|
||||
class _DashedBorderPainter extends CustomPainter {
|
||||
final Color color;
|
||||
final double borderRadius;
|
||||
|
||||
_DashedBorderPainter({
|
||||
required this.color,
|
||||
required this.borderRadius,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint()
|
||||
..color = color
|
||||
..strokeWidth = 1.5
|
||||
..style = PaintingStyle.stroke;
|
||||
|
||||
final dashWidth = 6.0;
|
||||
final dashSpace = 4.0;
|
||||
|
||||
final path = Path()
|
||||
..addRRect(RRect.fromRectAndRadius(
|
||||
Rect.fromLTWH(0, 0, size.width, size.height),
|
||||
Radius.circular(borderRadius),
|
||||
));
|
||||
|
||||
final metrics = path.computeMetrics();
|
||||
for (final metric in metrics) {
|
||||
double distance = 0;
|
||||
while (distance < metric.length) {
|
||||
final end = (distance + dashWidth).clamp(0.0, metric.length);
|
||||
canvas.drawPath(metric.extractPath(distance, end), paint);
|
||||
distance += dashWidth + dashSpace;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _DashedBorderPainter oldDelegate) {
|
||||
return oldDelegate.color != color || oldDelegate.borderRadius != borderRadius;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
@@ -7,6 +8,7 @@ import 'package:google_fonts/google_fonts.dart';
|
||||
import '../../../core/theme/app_color_scheme.dart';
|
||||
import '../../../core/theme/app_spacing.dart';
|
||||
import '../../../core/utils/toast_utils.dart';
|
||||
import '../../../core/event/app_event_bus.dart';
|
||||
import '../../../data/models/account_models.dart';
|
||||
import '../../../data/services/asset_service.dart';
|
||||
import '../../../data/services/bonus_service.dart';
|
||||
@@ -15,6 +17,7 @@ import '../../../providers/auth_provider.dart';
|
||||
import '../../components/glass_panel.dart';
|
||||
import '../../components/neon_glow.dart';
|
||||
import '../main/main_page.dart';
|
||||
import '../mine/welfare_center_page.dart';
|
||||
|
||||
/// 首页
|
||||
class HomePage extends StatefulWidget {
|
||||
@@ -26,8 +29,8 @@ class HomePage extends StatefulWidget {
|
||||
|
||||
class _HomePageState extends State<HomePage>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
bool _bonusClaimed = false;
|
||||
bool _bonusLoading = false;
|
||||
int _totalClaimable = 0;
|
||||
StreamSubscription<AppEvent>? _eventSub;
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
@@ -35,7 +38,26 @@ class _HomePageState extends State<HomePage>
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _loadData());
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_loadData();
|
||||
_listenEvents();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_eventSub?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _listenEvents() {
|
||||
final eventBus = context.read<AppEventBus>();
|
||||
_eventSub = eventBus.on(AppEventType.assetChanged, (_) {
|
||||
if (mounted) {
|
||||
context.read<AssetProvider>().loadOverview(force: true);
|
||||
_checkBonusStatus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _loadData() {
|
||||
@@ -48,10 +70,10 @@ class _HomePageState extends State<HomePage>
|
||||
Future<void> _checkBonusStatus() async {
|
||||
try {
|
||||
final bonusService = context.read<BonusService>();
|
||||
final response = await bonusService.getStatus();
|
||||
final response = await bonusService.getWelfareStatus();
|
||||
if (response.success && response.data != null) {
|
||||
setState(() {
|
||||
_bonusClaimed = response.data!['claimed'] == true;
|
||||
_totalClaimable = response.data!['totalClaimable'] as int? ?? 0;
|
||||
});
|
||||
}
|
||||
} catch (_) {}
|
||||
@@ -89,11 +111,13 @@ class _HomePageState extends State<HomePage>
|
||||
onDeposit: _showDeposit,
|
||||
),
|
||||
SizedBox(height: AppSpacing.md),
|
||||
// 新人福利卡片
|
||||
_BonusCard(
|
||||
isClaimed: _bonusClaimed,
|
||||
onClaim: _claimBonus,
|
||||
isLoading: _bonusLoading,
|
||||
// 福利中心入口卡片
|
||||
_WelfareCard(
|
||||
totalClaimable: _totalClaimable,
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const WelfareCenterPage()),
|
||||
),
|
||||
),
|
||||
SizedBox(height: AppSpacing.lg),
|
||||
// 持仓
|
||||
@@ -384,64 +408,11 @@ class _HomePageState extends State<HomePage>
|
||||
mainState?.switchToTab(3);
|
||||
}
|
||||
|
||||
Future<void> _claimBonus() async {
|
||||
setState(() => _bonusLoading = true);
|
||||
try {
|
||||
final bonusService = context.read<BonusService>();
|
||||
final response = await bonusService.claim();
|
||||
if (!mounted) return;
|
||||
|
||||
if (response.success) {
|
||||
setState(() => _bonusClaimed = true);
|
||||
context.read<AssetProvider>().refreshAll(force: true);
|
||||
showShadDialog(
|
||||
context: context,
|
||||
builder: (ctx) => ShadDialog.alert(
|
||||
title: const Text('领取成功'),
|
||||
description:
|
||||
Text('50 USDT 已到账,请划转至交易账户后即可开始交易'),
|
||||
actions: [
|
||||
ShadButton(
|
||||
child: const Text('确定'),
|
||||
onPressed: () => Navigator.of(ctx).pop(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
showShadDialog(
|
||||
context: context,
|
||||
builder: (ctx) => ShadDialog.alert(
|
||||
title: const Text('领取失败'),
|
||||
description: Text(response.message ?? '请稍后重试'),
|
||||
actions: [
|
||||
ShadButton(
|
||||
child: const Text('确定'),
|
||||
onPressed: () => Navigator.of(ctx).pop(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
showShadDialog(
|
||||
context: context,
|
||||
builder: (ctx) => ShadDialog.alert(
|
||||
title: const Text('领取失败'),
|
||||
description: Text(e.toString()),
|
||||
actions: [
|
||||
ShadButton(
|
||||
child: const Text('确定'),
|
||||
onPressed: () => Navigator.of(ctx).pop(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) setState(() => _bonusLoading = false);
|
||||
}
|
||||
void _navigateToWelfareCenter() {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const WelfareCenterPage()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -963,120 +934,122 @@ class _AssetCardState extends State<_AssetCard> {
|
||||
}
|
||||
}
|
||||
|
||||
/// 新人福利卡片
|
||||
class _BonusCard extends StatelessWidget {
|
||||
final bool isClaimed;
|
||||
final VoidCallback onClaim;
|
||||
final bool isLoading;
|
||||
/// 福利中心入口卡片
|
||||
class _WelfareCard extends StatelessWidget {
|
||||
final int totalClaimable;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const _BonusCard({required this.isClaimed, required this.onClaim, required this.isLoading});
|
||||
const _WelfareCard({required this.totalClaimable, required this.onTap});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
|
||||
return Opacity(
|
||||
opacity: isClaimed ? 0.6 : 1.0,
|
||||
child: GestureDetector(
|
||||
onTap: isClaimed || isLoading ? null : onClaim,
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: EdgeInsets.all(AppSpacing.lg),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
colorScheme.primary.withOpacity(0.15),
|
||||
colorScheme.secondary.withOpacity(0.1),
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(AppRadius.xl),
|
||||
border: Border.all(color: colorScheme.primary.withOpacity(0.2)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// 左侧图标
|
||||
Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: isClaimed
|
||||
? colorScheme.onSurfaceVariant.withOpacity(0.1)
|
||||
: colorScheme.primary.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||||
),
|
||||
child: Icon(
|
||||
isClaimed ? LucideIcons.check : LucideIcons.gift,
|
||||
color: isClaimed ? AppColorScheme.up : colorScheme.primary,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
SizedBox(width: AppSpacing.md),
|
||||
// 中间文字
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'新人福利',
|
||||
style: GoogleFonts.spaceGrotesk(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
SizedBox(height: AppSpacing.xs),
|
||||
Text(
|
||||
isClaimed ? '50 USDT 体验金已到账' : '领取 50 USDT 体验金,开始您的交易之旅',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// 右侧按钮
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: AppSpacing.md,
|
||||
vertical: AppSpacing.sm,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: isClaimed
|
||||
? colorScheme.onSurfaceVariant.withOpacity(0.15)
|
||||
: null,
|
||||
gradient: isClaimed
|
||||
? null
|
||||
: (isDark
|
||||
? AppColorScheme.darkCtaGradient
|
||||
: AppColorScheme.lightCtaGradient),
|
||||
borderRadius: BorderRadius.circular(AppRadius.full),
|
||||
),
|
||||
child: isLoading
|
||||
? SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: isDark ? colorScheme.background : Colors.white,
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
isClaimed ? '已领取' : '立即领取',
|
||||
style: TextStyle(
|
||||
color: isClaimed
|
||||
? colorScheme.onSurfaceVariant
|
||||
: (isDark ? colorScheme.background : Colors.white),
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: EdgeInsets.all(AppSpacing.lg),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
colorScheme.primary.withOpacity(0.15),
|
||||
colorScheme.secondary.withOpacity(0.1),
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(AppRadius.xl),
|
||||
border: Border.all(color: colorScheme.primary.withOpacity(0.2)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// 左侧图标
|
||||
Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.primary.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||||
),
|
||||
child: Icon(
|
||||
LucideIcons.gift,
|
||||
color: colorScheme.primary,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
SizedBox(width: AppSpacing.md),
|
||||
// 中间文字
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'福利中心',
|
||||
style: GoogleFonts.spaceGrotesk(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
SizedBox(height: AppSpacing.xs),
|
||||
Text(
|
||||
totalClaimable > 0
|
||||
? '您有 $totalClaimable 个奖励待领取'
|
||||
: '首充奖励 + 推广奖励',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// 右侧按钮
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: AppSpacing.md,
|
||||
vertical: AppSpacing.sm,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
gradient: isDark
|
||||
? AppColorScheme.darkCtaGradient
|
||||
: AppColorScheme.lightCtaGradient,
|
||||
borderRadius: BorderRadius.circular(AppRadius.full),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (totalClaimable > 0)
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Text(
|
||||
'$totalClaimable',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: totalClaimable > 0 ? 6 : 0),
|
||||
Text(
|
||||
'查看',
|
||||
style: TextStyle(
|
||||
color: isDark ? colorScheme.background : Colors.white,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
import '../../../core/theme/app_spacing.dart';
|
||||
import '../../../providers/asset_provider.dart';
|
||||
import '../../../providers/market_provider.dart';
|
||||
import '../home/home_page.dart';
|
||||
import '../market/market_page.dart';
|
||||
import '../trade/trade_page.dart';
|
||||
@@ -29,6 +32,9 @@ class MainPageState extends State<MainPage> {
|
||||
String? _tradeCoinCode; // 交易页面选中的币种代码
|
||||
late final List<Widget> _pages;
|
||||
|
||||
// 防抖:记录上次刷新时间,同一 Tab 500ms 内不重复刷新
|
||||
final Map<int, DateTime> _lastRefreshTime = {};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -42,10 +48,35 @@ class MainPageState extends State<MainPage> {
|
||||
}
|
||||
|
||||
void _onTabChanged(int index) {
|
||||
final wasLoaded = _loadedPages.contains(index);
|
||||
setState(() {
|
||||
_currentIndex = index;
|
||||
_loadedPages.add(index);
|
||||
});
|
||||
// 切换到已加载的 Tab 时刷新数据
|
||||
if (wasLoaded) {
|
||||
_refreshTab(index);
|
||||
}
|
||||
}
|
||||
|
||||
/// 刷新对应 Tab 的数据(带防抖)
|
||||
void _refreshTab(int index) {
|
||||
final now = DateTime.now();
|
||||
final last = _lastRefreshTime[index];
|
||||
if (last != null && now.difference(last).inMilliseconds < 500) return;
|
||||
_lastRefreshTime[index] = now;
|
||||
|
||||
switch (index) {
|
||||
case 0: // 首页 - 刷新资产概览
|
||||
context.read<AssetProvider>().loadOverview(force: true);
|
||||
break;
|
||||
case 1: // 行情 - 刷新币种列表
|
||||
context.read<MarketProvider>().loadCoins(force: true);
|
||||
break;
|
||||
case 3: // 资产 - 刷新全部资产
|
||||
context.read<AssetProvider>().refreshAll(force: true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// 切换到交易页面并选中指定币种
|
||||
|
||||
@@ -10,6 +10,7 @@ import '../../../providers/theme_provider.dart';
|
||||
import '../auth/login_page.dart';
|
||||
import '../../components/glass_panel.dart';
|
||||
import '../../components/neon_glow.dart';
|
||||
import 'welfare_center_page.dart';
|
||||
|
||||
/// 菜单项数据模型
|
||||
class _MenuItem {
|
||||
@@ -367,6 +368,18 @@ class _MenuList extends StatelessWidget {
|
||||
|
||||
List<Widget> _buildMenuItems(BuildContext context, ColorScheme colorScheme) {
|
||||
final items = [
|
||||
_MenuItem(
|
||||
icon: LucideIcons.gift,
|
||||
title: '福利中心',
|
||||
subtitle: '首充奖励 + 推广奖励',
|
||||
iconColor: colorScheme.primary,
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const WelfareCenterPage()),
|
||||
);
|
||||
},
|
||||
),
|
||||
_MenuItem(
|
||||
icon: LucideIcons.userCheck,
|
||||
title: '实名认证',
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
import 'package:bot_toast/bot_toast.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../../../core/utils/toast_utils.dart';
|
||||
import '../../../core/event/app_event_bus.dart';
|
||||
import '../../../providers/asset_provider.dart';
|
||||
import '../../../data/models/order_models.dart';
|
||||
|
||||
@@ -17,6 +19,7 @@ class FundOrdersPage extends StatefulWidget {
|
||||
|
||||
class _FundOrdersPageState extends State<FundOrdersPage> {
|
||||
int _activeTab = 0; // 0=全部, 1=充值, 2=提现
|
||||
StreamSubscription<AppEvent>? _eventSub;
|
||||
|
||||
// 颜色常量
|
||||
static const upColor = Color(0xFF00C853);
|
||||
@@ -27,6 +30,20 @@ class _FundOrdersPageState extends State<FundOrdersPage> {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_loadData();
|
||||
_listenEvents();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_eventSub?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _listenEvents() {
|
||||
final eventBus = context.read<AppEventBus>();
|
||||
_eventSub = eventBus.on(AppEventType.orderChanged, (_) {
|
||||
if (mounted) _loadData();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user