This commit is contained in:
sion
2026-04-07 01:05:05 +08:00
parent edad10ff06
commit 5ca1274607
83 changed files with 1561 additions and 1241 deletions

View File

@@ -1,111 +1,111 @@
/// API 端配置
/// API 端配置
class ApiEndpoints {
ApiEndpoints._();
/// 环境类
/// 環境類
static const String _env = String.fromEnvironment('ENV', defaultValue: 'dev');
/// 基URL - 根据环境自动切换
/// 基URL - 根據環境自動切換
static const String baseUrl = _env == 'prod'
? 'http://8.155.172.147:5010'
: 'http://localhost:5010';
/// 是否为生产环
/// 是否為生產環
static const bool isProduction = _env == 'prod';
// ==================== 用户模块 ====================
// ==================== 用戶模塊 ====================
/// 用户登录
/// 用戶登錄
static const String login = '/api/user/login';
/// 用户注册
/// 用戶註冊
static const String register = '/api/user/register';
/// 取用信息
/// 取用信息
static const String userInfo = '/api/user/info';
/// 上KYC
/// 上KYC
static const String kyc = '/api/user/kyc';
/// 取推广码
/// 取推廣碼
static const String referralCode = '/api/user/referral-code';
/// 退出登
/// 退出登
static const String logout = '/api/user/logout';
// ==================== 行情模 ====================
// ==================== 行情模 ====================
/// 获取币种列表
/// 獲取幣種列表
static const String coinList = '/api/market/list';
/// 获取币种详
/// 獲取幣種詳
static const String coinDetail = '/api/market/detail';
/// 搜索币种
/// 搜索幣種
static const String coinSearch = '/api/market/search';
// ==================== 交易模 ====================
// ==================== 交易模 ====================
///
///
static const String buy = '/api/trade/buy';
///
///
static const String sell = '/api/trade/sell';
/// 取交易记录
/// 取交易記錄
static const String tradeOrders = '/api/trade/orders';
/// 获取订单详
/// 獲取訂單詳
static const String tradeOrderDetail = '/api/trade/order/detail';
// ==================== 资产模块 ====================
// ==================== 資產模塊 ====================
/// 获取资产总览
/// 獲取資產總覽
static const String assetOverview = '/api/asset/overview';
/// 获取资金账户
/// 獲取資金賬戶
static const String fundAccount = '/api/asset/fund';
/// 取交易账户
/// 取交易賬戶
static const String tradeAccount = '/api/asset/trade';
/// 资金划转
/// 資金劃轉
static const String transfer = '/api/asset/transfer';
/// 获取资金流水
/// 獲取資金流水
static const String assetFlow = '/api/asset/flow';
// ==================== 充提模 ====================
// ==================== 充提模 ====================
/// 申充值
/// 申充值
static const String deposit = '/api/fund/deposit';
/// 确认已打款
/// 確認已打款
static const String confirmPay = '/api/fund/confirmPay';
/// 申请提现
/// 申請提現
static const String withdraw = '/api/fund/withdraw';
/// 取消订单
/// 取消訂單
static const String cancelOrder = '/api/fund/cancel';
/// 取可用提现网络列表
/// 取可用提現網絡列表
static const String walletNetworks = '/api/wallet/networks';
/// 取充提记录
/// 取充提記錄
static const String fundOrders = '/api/fund/orders';
/// 取默认钱包地址
/// 取默認錢包地址
static const String defaultWallet = '/api/wallet/default';
// ==================== 福利模 ====================
// ==================== 福利模 ====================
/// 福利中心状态
/// 福利中心狀態
static const String bonusWelfare = '/api/bonus/welfare';
/// 领取奖励
/// 領取獎勵
static const String bonusClaim = '/api/bonus/claim';
/// 每日盈
/// 每日盈
static const String dailyProfit = '/api/asset/daily-profit';
}

View File

@@ -1,15 +1,15 @@
import 'dart:async';
/// 用事件
/// 用事件
enum AppEventType {
/// 资产变动(余额、持等)
/// 資產變動(餘額、持等)
assetChanged,
/// 订单变动(充提订单状态变化)
/// 訂單變動(充提訂單狀態變化)
orderChanged,
}
/// 用事件
/// 用事件
class AppEvent {
final AppEventType type;
final Map<String, dynamic>? data;
@@ -17,20 +17,20 @@ class AppEvent {
const AppEvent(this.type, {this.data});
}
/// 轻量级应用内事件总线
/// 基 StreamController.broadcast零外部依
/// 輕量級應用內事件總線
/// 基 StreamController.broadcast零外部依
class AppEventBus {
final StreamController<AppEvent> _controller =
StreamController<AppEvent>.broadcast();
/// 广播事件
/// 播事件
void fire(AppEventType type, {Map<String, dynamic>? data}) {
if (!_controller.isClosed) {
_controller.add(AppEvent(type, data: data));
}
}
/// 监听指定型事件
/// 監聽指定型事件
StreamSubscription<AppEvent> on(
AppEventType type,
void Function(AppEvent) callback,
@@ -40,10 +40,10 @@ class AppEventBus {
.listen(callback);
}
/// 监听任意事件
/// 監聽任意事件
Stream<AppEvent> get stream => _controller.stream;
/// 销毁
/// 銷燬
void dispose() {
_controller.close();
}

View File

@@ -1,4 +1,4 @@
/// API 异常类
/// API 異常類
class ApiException implements Exception {
final String message;
final String code;
@@ -12,7 +12,7 @@ class ApiException implements Exception {
factory ApiException.unauthorized([String? message]) {
return ApiException(
message: message ?? '未授',
message: message ?? '未授',
code: '0002',
statusCode: 401,
);
@@ -20,14 +20,14 @@ class ApiException implements Exception {
factory ApiException.networkError([String? message]) {
return ApiException(
message: message ?? '网络错误',
message: message ?? '網絡錯誤',
code: 'NETWORK_ERROR',
);
}
factory ApiException.serverError([String? message]) {
return ApiException(
message: message ?? '务器错误',
message: message ?? '務器錯誤',
code: 'SERVER_ERROR',
statusCode: 500,
);

View File

@@ -1,11 +1,11 @@
/// API 响应状态码
/// API 響應狀態碼
class ResponseCode {
static const String success = '0000';
static const String unauthorized = '0002';
static const String kycRequired = 'KYC_REQUIRED';
}
/// API 响应模型
/// API 響應模型
class ApiResponse<T> {
final bool success;
final String? message;

View File

@@ -5,7 +5,7 @@ import '../storage/local_storage.dart';
import 'api_exception.dart';
import 'api_response.dart';
/// 网络配置常量
/// 網絡配置常量
class NetworkConfig {
static const String baseUrl = ApiEndpoints.baseUrl;
static const Duration connectTimeout = Duration(seconds: 15);
@@ -13,11 +13,11 @@ class NetworkConfig {
static const Duration sendTimeout = Duration(seconds: 15);
}
/// Dio 网络客户
/// Dio 網絡客戶
class DioClient {
late final Dio _dio;
/// 未授权回调token 过期时触发
/// 未授權回調token 過期時觸發
VoidCallback? onUnauthorized;
DioClient() {
@@ -43,7 +43,7 @@ class DioClient {
]);
}
/// GET
/// GET
Future<ApiResponse<T>> get<T>(
String path, {
Map<String, dynamic>? queryParameters,
@@ -57,7 +57,7 @@ class DioClient {
}
}
/// POST
/// POST
Future<ApiResponse<T>> post<T>(
String path, {
dynamic data,
@@ -71,7 +71,7 @@ class DioClient {
}
}
/// Multipart 文件上
/// Multipart 文件上
Future<ApiResponse<T>> upload<T>(
String path, {
required FormData formData,
@@ -96,21 +96,21 @@ class DioClient {
final data = response.data;
if (data is Map<String, dynamic>) {
final apiResponse = ApiResponse.fromJson(data, fromJson);
// 检测业务层未授权(后端返回 HTTP 200 + code "0002"
// 注意:不再自清除用户数据,避免
// 只有在 HTTP 401 才清除用户数据
// 檢測業務層未授權(後端返回 HTTP 200 + code "0002"
// 注意:不再自清除用戶數據,避免
// 只有在 HTTP 401 才清除用戶數據
if (apiResponse.isUnauthorized) {
debugPrint('业务层未授权响应: ${apiResponse.message}');
// 不再自动调用 onUnauthorized避免刷新时误
debugPrint('業務層未授權響應: ${apiResponse.message}');
// 不再自動調用 onUnauthorized避免刷新時誤
// onUnauthorized?.call();
}
return apiResponse;
}
return ApiResponse.fail('响应数据格式错误');
return ApiResponse.fail('響應數據格式錯誤');
}
ApiResponse<T> _handleError<T>(DioException e) {
// 详细错误日志
// 詳細錯誤日誌
debugPrint('=== Network Error ===');
debugPrint('Type: ${e.type}');
debugPrint('Message: ${e.message}');
@@ -122,7 +122,7 @@ class DioClient {
if (_isUnauthorized(e)) {
_clearUserData();
onUnauthorized?.call();
return ApiResponse.unauthorized('录已过期,重新登');
return ApiResponse.unauthorized('錄已過期,重新登');
}
final message = _getErrorMessage(e);
@@ -140,28 +140,28 @@ class DioClient {
String _getErrorMessage(DioException e) {
switch (e.type) {
case DioExceptionType.connectionTimeout:
return '接超时,请检查网络';
return '接超時,請檢查網絡';
case DioExceptionType.sendTimeout:
return '送超时,请重试';
return '送超時,請重試';
case DioExceptionType.receiveTimeout:
return '响应超时,请重试';
return '響應超時,請重試';
case DioExceptionType.connectionError:
return '网络连接失败,请检查网络设';
return '網絡連接失敗,請檢查網絡設';
case DioExceptionType.badResponse:
final statusCode = e.response?.statusCode;
if (statusCode == 500) {
return '务器内部错误';
return '務器內部錯誤';
} else if (statusCode == 502 || statusCode == 503) {
return '务暂时不可用';
return '務暫時不可用';
}
return '务器错误 ($statusCode)';
return '務器錯誤 ($statusCode)';
default:
return e.message ?? '网络请求失';
return e.message ?? '網絡請求失';
}
}
}
/// 日志拦截器
/// 日誌攔截器
class _LoggingInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
@@ -195,7 +195,7 @@ class _LoggingInterceptor extends Interceptor {
}
}
/// 认证拦截器
/// 認證攔截器
class _AuthInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {

View File

@@ -1,7 +1,7 @@
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
/// 本地存储服务
/// 本地存儲服務
class LocalStorage {
LocalStorage._();
@@ -15,7 +15,7 @@ class LocalStorage {
_prefs = await SharedPreferences.getInstance();
}
/// 获取实
/// 獲取實
static SharedPreferences get prefs {
if (_prefs == null) {
throw Exception('LocalStorage not initialized. Call init() first.');
@@ -30,7 +30,7 @@ class LocalStorage {
await prefs.setString(_tokenKey, token);
}
/// 取 Token
/// 取 Token
static String? getToken() {
return prefs.getString(_tokenKey);
}
@@ -40,17 +40,17 @@ class LocalStorage {
await prefs.remove(_tokenKey);
}
/// 是否已登
/// 是否已登
static bool get isLoggedIn => getToken() != null && getToken()!.isNotEmpty;
// ==================== 用信息管理 ====================
// ==================== 用信息管理 ====================
/// 保存用信息
/// 保存用信息
static Future<void> saveUserInfo(Map<String, dynamic> userInfo) async {
await prefs.setString(_userInfoKey, jsonEncode(userInfo));
}
/// 取用信息
/// 取用信息
static Map<String, dynamic>? getUserInfo() {
final str = prefs.getString(_userInfoKey);
if (str == null) return null;
@@ -61,7 +61,7 @@ class LocalStorage {
}
}
/// 移除用信息
/// 移除用信息
static Future<void> removeUserInfo() async {
await prefs.remove(_userInfoKey);
}
@@ -73,47 +73,47 @@ class LocalStorage {
await prefs.setString(key, value);
}
/// 取字符串
/// 取字符串
static String? getString(String key) {
return prefs.getString(key);
}
/// 保存布
/// 保存布
static Future<void> setBool(String key, bool value) async {
await prefs.setBool(key, value);
}
/// 取布
/// 取布
static bool? getBool(String key) {
return prefs.getBool(key);
}
/// 清除所有数据
/// 清除所有數據
static Future<void> clearAll() async {
await prefs.clear();
}
/// 清除用户数据(退出登录时调用)
/// 清除用戶數據(退出登錄時調用)
static Future<void> clearUserData() async {
await removeToken();
await removeUserInfo();
}
// ==================== 引导页状态 ====================
// ==================== 引導頁狀態 ====================
static const String _onboardingKey = 'onboarding_completed';
/// 查是否已完成引导页
/// 查是否已完成引導頁
static bool get isOnboardingCompleted {
return getBool(_onboardingKey) ?? false;
}
/// 标记引导页已完成
/// 標記引導頁已完成
static Future<void> setOnboardingCompleted() async {
await setBool(_onboardingKey, true);
}
/// 重置引导页状态(用于测试
/// 重置引導頁狀態(用於測試
static Future<void> resetOnboarding() async {
await prefs.remove(_onboardingKey);
}

View File

@@ -1,33 +1,33 @@
import 'package:flutter/material.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
/// Material Design 3 色系 - Pencil Design Theme
/// Material Design 3 色系 - Pencil Design Theme
///
/// 深色主: "黑金奇" (Black & Gold)
/// 深邃黑底 + 真金主色 + 翠绿盈利 → 金融奢
/// 深色主: "黑金奇" (Black & Gold)
/// 深邃黑底 + 真金主色 + 翠盈利 → 金融奢
/// 背景 #0B1120 | 主色 #D4AF37 | 盈利 #4ADE80
///
/// 色主: "白金殿堂" (White & Gold)
/// 纯净白底 + 深灰主色 + 琥珀强调 → 高端金融科技
/// 背景 #F8FAFC | 主色 #1F2937 | 强调 #F59E0B
/// 色主: "白金殿堂" (White & Gold)
/// 純淨白底 + 深灰主色 + 琥珀強調 → 高端金融科技
/// 背景 #F8FAFC | 主色 #1F2937 | 強調 #F59E0B
///
/// 设计原则
/// 設計原則
/// - Material Design 3 配色方案
/// - Glass Panel 毛玻璃效果
/// - Gold Glow 金色光效(原 Neon Glow
/// - 渐变 CTA: primary → primary_container (135度)
/// - 漸變 CTA: primary → primary_container (135度)
class AppColorScheme {
AppColorScheme._();
// ============================================
// 深色主 - "Slate Dark" (Material Design 3)
// 背景 #0B1120 | 主色 #1E3A8A | 强调 #D4AF37
// 深色主 - "Slate Dark" (Material Design 3)
// 背景 #0B1120 | 主色 #1E3A8A | 強調 #D4AF37
// ============================================
/// 背景基色 - Slate 深
/// 背景基色 - Slate 深
static const Color darkBackground = Color(0xFF0B1120);
/// Surface 次 (低到高) - Material Design 3 规范
/// Surface 次 (低到高) - Material Design 3 規範
static const Color darkSurfaceDim = Color(0xFF0B1120);
static const Color darkSurfaceLowest = Color(0xFF000000);
static const Color darkSurfaceLow = Color(0xFF0F172A);
@@ -38,7 +38,7 @@ class AppColorScheme {
static const Color darkSurfaceBright = Color(0xFF334155);
static const Color darkSurfaceVariant = Color(0xFF1E293B);
/// 兼容旧名称
/// 兼容舊名稱
static const Color darkSurfaceContainerLowest = darkSurfaceLowest;
static const Color darkSurfaceContainerLow = darkSurfaceLow;
static const Color darkSurfaceHigh = darkSurfaceContainerHigh;
@@ -59,7 +59,7 @@ class AppColorScheme {
static const Color darkOnPrimaryFixed = Color(0xFF1F2937);
static const Color darkOnPrimaryFixedVariant = Color(0xFF374151);
/// Secondary - 真金 #D4AF37 (黑金强调色)
/// Secondary - 真金 #D4AF37 (黑金強調色)
static const Color darkSecondary = Color(0xFFD4AF37);
static const Color darkSecondaryDim = Color(0xFFB8960E);
static const Color darkSecondaryContainer = Color(0xFFE8C84A);
@@ -81,7 +81,7 @@ class AppColorScheme {
static const Color darkOnTertiaryFixed = Color(0xFF052E16);
static const Color darkOnTertiaryFixedVariant = Color(0xFF14532D);
/// Error - Neon Red (色 - 错误/卖出)
/// Error - Neon Red (色 - 錯誤/賣出)
static const Color darkError = Color(0xFFff716c);
static const Color darkErrorDim = Color(0xFFd7383b);
static const Color darkErrorContainer = Color(0xFF9f0519);
@@ -99,14 +99,14 @@ class AppColorScheme {
static const Color darkSurfaceTint = Color(0xFFD4AF37);
// ============================================
// 色主 - "Slate Light" (Material Design 3)
// 背景 #F8FAFC | 主色 #1E40AF | 强调 #D4AF37
// 色主 - "Slate Light" (Material Design 3)
// 背景 #F8FAFC | 主色 #1E40AF | 強調 #D4AF37
// ============================================
/// 背景基色 - Slate 50
static const Color lightBackground = Color(0xFFF8FAFC);
/// Surface 次 (低到高)
/// Surface 次 (低到高)
static const Color lightSurfaceLowest = Color(0xFFFFFFFF);
static const Color lightSurfaceLow = Color(0xFFF1F5F9);
static const Color lightSurface = Color(0xFFF8FAFC);
@@ -134,41 +134,41 @@ class AppColorScheme {
static const Color lightOnSurfaceMuted = Color(0xFF94A3B8);
// ============================================
// Glass Panel 毛玻璃效果
// Glass Panel 毛玻璃效果
// ============================================
/// Glass Panel 背景色 - rgba(33, 37, 47, 0.4)
static const Color glassPanelBackground = Color(0x6621252F);
/// Glass Panel 框色 - rgba(69, 72, 79, 0.15)
/// Glass Panel 框色 - rgba(69, 72, 79, 0.15)
static const Color glassPanelBorder = Color(0x2645484f);
// ============================================
// Gold Glow 金色光效色(原 Neon Glow
// Gold Glow 金色光效色(原 Neon Glow
// ============================================
/// Primary Glow - 金色光 rgba(212, 175, 55, 0.15)
/// Primary Glow - 金色光 rgba(212, 175, 55, 0.15)
static const Color neonGlowPrimary = Color(0x26D4AF37);
/// Secondary Glow - 色光 rgba(30, 58, 138, 0.15)
/// Secondary Glow - 色光 rgba(30, 58, 138, 0.15)
static const Color neonGlowSecondary = Color(0x261E3A8A);
/// Tertiary Glow - 绿色光 rgba(175, 255, 209, 0.2)
/// Tertiary Glow - 色光 rgba(175, 255, 209, 0.2)
static const Color neonGlowTertiary = Color(0x33afffd1);
// ============================================
// 语义色 - 明暗通用
// 語義色 - 明暗通用
// ============================================
/// 涨/买入/成功 (深色主色,亮色模式下使用 getUpColor)
/// 漲/買入/成功 (深色主色,亮色模式下使用 getUpColor)
static const Color up = darkTertiary;
static const Color success = darkTertiary;
/// 跌/出/错误 - 使用 Material Design 3 error
/// 跌/出/錯誤 - 使用 Material Design 3 error
static const Color down = Color(0xFFff716c);
static const Color error = down;
/// 默/禁用/次要
/// 默/禁用/次要
static const Color muted = darkOnSurfaceVariant;
/// 警告
@@ -178,50 +178,50 @@ class AppColorScheme {
static const Color info = Color(0xFF2196F3);
// ============================================
// 主感知助函 - 已移至 AppThemeColors ThemeExtension
// 以下方法保留供 AppThemeColors 工厂内部使用
// 主感知助函 - 已移至 AppThemeColors ThemeExtension
// 以下方法保留供 AppThemeColors 工廠內部使用
// ============================================
static const Color buyButtonFill = Color(0xFF059669);
/// 交易按钮卖出色 - 深色填充,保白色文字可
/// 交易按鈕賣出色 - 深色填充,保白色文字可
static const Color sellButtonFill = Color(0xFFDC2626);
// ============================================
// 渐变预设
// 漸變預設
// ============================================
/// 深色 CTA 渐变 (primary → primary_container, 135度)
/// 深色 CTA 漸變 (primary → primary_container, 135度)
static const LinearGradient darkCtaGradient = LinearGradient(
colors: [darkPrimary, darkPrimaryContainer],
begin: Alignment(-0.7, -0.7),
end: Alignment(0.7, 0.7),
);
/// 色 CTA 渐变
/// 色 CTA 漸變
static const LinearGradient lightCtaGradient = LinearGradient(
colors: [lightPrimary, lightPrimaryContainer],
begin: Alignment(-0.7, -0.7),
end: Alignment(0.7, 0.7),
);
/// 兼容
/// 兼容
static const LinearGradient ctaGradient = darkCtaGradient;
/// 入按钮渐变
/// 入按鈕漸變
static const LinearGradient buyGradient = LinearGradient(
colors: [darkTertiary, darkTertiaryContainer],
begin: Alignment(-0.7, -0.7),
end: Alignment(0.7, 0.7),
);
/// 出按钮渐变
/// 出按鈕漸變
static const LinearGradient sellGradient = LinearGradient(
colors: [darkError, darkErrorDim],
begin: Alignment(-0.7, -0.7),
end: Alignment(0.7, 0.7),
);
/// 资产卡片渐变 - 金色
/// 資產卡片漸變 - 金色
static const LinearGradient assetCardGradient = LinearGradient(
colors: [darkPrimaryContainer, darkPrimary],
begin: Alignment.topLeft,
@@ -229,7 +229,7 @@ class AppColorScheme {
);
// ============================================
// Shadcn ColorScheme - 深色主
// Shadcn ColorScheme - 深色主
// ============================================
static ShadColorScheme get darkShad => ShadColorScheme(
@@ -256,7 +256,7 @@ class AppColorScheme {
);
// ============================================
// Shadcn ColorScheme - 色主
// Shadcn ColorScheme - 色主
// ============================================
static ShadColorScheme get lightShad => ShadColorScheme(
@@ -283,7 +283,7 @@ class AppColorScheme {
);
// ============================================
// Material ColorScheme - 深色主 (Material Design 3)
// Material ColorScheme - 深色主 (Material Design 3)
// ============================================
static ColorScheme get darkMaterial => ColorScheme.dark(
@@ -322,17 +322,17 @@ class AppColorScheme {
);
// ============================================
// Material ColorScheme - 色主
// Material ColorScheme - 色主
// ============================================
/// 色主 Error 色
/// 色主 Error 色
static const Color lightError = Color(0xFFDC2626);
static const Color lightOnError = Color(0xFFFFFFFF);
/// 色主 Outline 色 - Slate 500
/// 色主 Outline 色 - Slate 500
static const Color lightOutline = Color(0xFF64748B);
/// 色主 Surface 色 (展) - Slate 系列
/// 色主 Surface 色 (展) - Slate 系列
static const Color lightSurfaceBright = Color(0xFFFFFFFF);
static const Color lightSurfaceDim = Color(0xFFE2E8F0);
static const Color lightSurfaceVariant = Color(0xFFE2E8F0);
@@ -376,12 +376,12 @@ class AppColorScheme {
);
// ============================================
// 兼容性名 - 保留有外部引用的
// 兼容性名 - 保留有外部引用的
// (buyButtonFill, sellButtonFill, darkCtaGradient, lightCtaGradient,
// buyGradient, sellGradient 已在上方定,此处无需重)
// buyGradient, sellGradient 已在上方定,此處無需重)
// ============================================
/// 获取买入按钮渐变(主感知)- 用盈利展示
/// 獲取買入按鈕漸變(主感知)- 用盈利展示
}

View File

@@ -1,22 +1,22 @@
import 'package:flutter/material.dart';
/// 距系
/// 距系
///
/// 使用 4px 基础单位,保一致性
/// 使用 4px 基礎單位,保一致性
class AppSpacing {
AppSpacing._();
// ============================================
// 基础间距 (Base Spacing)
// 基礎間距 (Base Spacing)
// ============================================
/// 小 - 4px
/// 小 - 4px
static const double xs = 4.0;
/// 小 - 8px
static const double sm = 8.0;
/// 中 - 16px (默)
/// 中 - 16px (默)
static const double md = 16.0;
/// 大 - 24px
@@ -29,28 +29,28 @@ class AppSpacing {
static const double xxl = 48.0;
// ============================================
// 预设 EdgeInsets
// 預設 EdgeInsets
// ============================================
/// 页面内边
/// 頁面內邊
static const EdgeInsets pagePadding = EdgeInsets.all(md);
/// 卡片内边
/// 卡片內邊
static const EdgeInsets cardPadding = EdgeInsets.all(md);
/// 列表项内边
/// 列表項內邊
static const EdgeInsets listItemPadding = EdgeInsets.symmetric(
horizontal: md,
vertical: sm,
);
/// 按钮内边
/// 按鈕內邊
static const EdgeInsets buttonPadding = EdgeInsets.symmetric(
horizontal: lg,
vertical: 14,
);
/// 入框内边
/// 入框內邊
static const EdgeInsets inputPadding = EdgeInsets.symmetric(
horizontal: md,
vertical: 14,
@@ -60,98 +60,98 @@ class AppSpacing {
// 便捷方法
// ============================================
/// 取水平
/// 取水平
static SizedBox horizontal(double spacing) => SizedBox(width: spacing);
/// 取垂直
/// 取垂直
static SizedBox vertical(double spacing) => SizedBox(height: spacing);
}
/// 角系 - "The Kinetic Vault" & "The Ethereal Terminal" 规范
/// 角系 - "The Kinetic Vault" & "The Ethereal Terminal" 規範
///
/// 设计规则
/// - 按: xxl (24px / 1.5rem)
/// 設計規則
/// - 按: xxl (24px / 1.5rem)
/// - 卡片: xl (16px)
/// - 入框: md (8px)
/// - 标签: sm (4px)
/// - 入框: md (8px)
/// - 標籤: sm (4px)
class AppRadius {
AppRadius._();
// ============================================
// 基础圆角 (Base Radius)
// 基礎圓角 (Base Radius)
// ============================================
/// 小角 - 6px (标签、徽章) — 对齐 Pencil $radius-sm
/// 小角 - 6px (標籤、徽章) — 對齊 Pencil $radius-sm
static const double sm = 6.0;
/// 中角 - 10px (入框、标签页) — 对齐 Pencil $radius-md
/// 中角 - 10px (入框、標籤頁) — 對齊 Pencil $radius-md
static const double md = 10.0;
/// 大角 - 14px (卡片、按、列表) — 对齐 Pencil $radius-lg
/// 大角 - 14px (卡片、按、列表) — 對齊 Pencil $radius-lg
static const double lg = 14.0;
/// 特大角 - 20px (大卡片、窗) — 对齐 Pencil $radius-xl
/// 特大角 - 20px (大卡片、窗) — 對齊 Pencil $radius-xl
static const double xl = 20.0;
/// 超大角 - 24px (按、模框、底部抽)
/// 超大角 - 24px (按、模框、底部抽)
static const double xxl = 24.0;
/// 形 - 9999px (Pill buttons)
/// 形 - 9999px (Pill buttons)
static const double full = 9999.0;
// ============================================
// 预设 BorderRadius
// 預設 BorderRadius
// ============================================
/// 小
/// 小
static BorderRadius get radiusSm => BorderRadius.circular(sm);
/// 中
/// 中
static BorderRadius get radiusMd => BorderRadius.circular(md);
/// 大
/// 大
static BorderRadius get radiusLg => BorderRadius.circular(lg);
/// 特大
/// 特大
static BorderRadius get radiusXl => BorderRadius.circular(xl);
/// 超大
/// 超大
static BorderRadius get radiusXxl => BorderRadius.circular(xxl);
///
///
static BorderRadius get radiusFull => BorderRadius.circular(full);
}
/// 响应式断点
/// 響應式斷點
class AppBreakpoints {
AppBreakpoints._();
/// 手机竖
/// 手機豎
static const double mobile = 360;
/// 手机横屏/小平板
/// 手機橫屏/小平板
static const double tablet = 768;
/// 平板/桌面
static const double desktop = 1024;
/// 查是否为手机
/// 查是否為手機
static bool isMobile(BuildContext context) {
return MediaQuery.of(context).size.width < tablet;
}
/// 查是否平板
/// 查是否平板
static bool isTablet(BuildContext context) {
final width = MediaQuery.of(context).size.width;
return width >= tablet && width < desktop;
}
/// 查是否桌面
/// 查是否桌面
static bool isDesktop(BuildContext context) {
return MediaQuery.of(context).size.width >= desktop;
}
/// 根屏幕宽度获取响应式值
/// 根屏幕寬度獲取響應式值
static T responsive<T>(
BuildContext context, {
required T mobile,
@@ -168,14 +168,14 @@ class AppBreakpoints {
}
}
/// 摸目尺寸
/// 摸目尺寸
class AppTouchTarget {
AppTouchTarget._();
/// 最小摸目 - 44x44 (iOS HIG 标准)
/// 最小摸目 - 44x44 (iOS HIG 標準)
static const double minSize = 44.0;
/// 确保触摸目标足够
/// 確保觸摸目標足夠
static Widget ensureMinSize({
required Widget child,
double minSize = AppTouchTarget.minSize,

View File

@@ -4,12 +4,12 @@ import 'app_color_scheme.dart';
import 'app_spacing.dart';
import 'app_theme_extension.dart';
/// "The Kinetic Vault" & "The Ethereal Terminal" 主配置
/// "The Kinetic Vault" & "The Ethereal Terminal" 主配置
class AppTheme {
AppTheme._();
// ============================================
// 深色主 - "The Kinetic Vault"
// 深色主 - "The Kinetic Vault"
// ============================================
static ThemeData get darkTheme {
@@ -23,7 +23,7 @@ class AppTheme {
AppThemeColors.dark(),
],
// AppBar - 无边框规则
// AppBar - 無邊框規則
appBarTheme: AppBarTheme(
backgroundColor: AppColorScheme.darkBackground,
foregroundColor: AppColorScheme.darkOnSurface,
@@ -37,7 +37,7 @@ class AppTheme {
),
),
// 卡片 - 无边框,使用 surface
// 卡片 - 無邊框,使用 surface
cardTheme: CardThemeData(
color: AppColorScheme.darkSurface,
elevation: 0,
@@ -46,7 +46,7 @@ class AppTheme {
),
),
// 入框 - Ghost Border
// 入框 - Ghost Border
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: AppColorScheme.darkSurfaceLow,
@@ -73,7 +73,7 @@ class AppTheme {
),
),
// 按 - Pencil accent-primary (Gold)
// 按 - Pencil accent-primary (Gold)
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: AppColorScheme.darkSecondary,
@@ -97,13 +97,13 @@ class AppTheme {
),
),
// 分割线 - 使用次而非
// 分割 - 使用次而非
dividerTheme: DividerThemeData(
color: AppColorScheme.darkOutlineVariant.withValues(alpha: 0.1),
thickness: 1,
),
// 底部
// 底部
bottomNavigationBarTheme: BottomNavigationBarThemeData(
backgroundColor: AppColorScheme.darkSurface,
selectedItemColor: AppColorScheme.darkPrimary,
@@ -115,7 +115,7 @@ class AppTheme {
}
// ============================================
// 色主 - "The Ethereal Terminal"
// 色主 - "The Ethereal Terminal"
// ============================================
static ThemeData get lightTheme {
@@ -152,7 +152,7 @@ class AppTheme {
),
),
// 入框 - 底部线条风
// 入框 - 底部線條風
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: AppColorScheme.lightSurfaceLow,
@@ -181,7 +181,7 @@ class AppTheme {
),
),
// 按 - Pencil accent-primary (dark gray)
// 按 - Pencil accent-primary (dark gray)
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF1F2937),
@@ -205,13 +205,13 @@ class AppTheme {
),
),
// 分割线 - 不使用,用留白代替
// 分割 - 不使用,用留白代替
dividerTheme: DividerThemeData(
color: AppColorScheme.lightOutlineVariant.withValues(alpha: 0.2),
thickness: 1,
),
// 底部
// 底部
bottomNavigationBarTheme: BottomNavigationBarThemeData(
backgroundColor: AppColorScheme.lightSurfaceHighest,
selectedItemColor: AppColorScheme.lightPrimary,
@@ -223,19 +223,19 @@ class AppTheme {
}
}
/// Pencil 设计系统字体规范
/// Pencil 設計系統字體規範
///
/// 对齐 Pencil 量: Inter 字, 明的字/字重层级
/// 28px (总资产) → 24px (精选价格) → 22px (页面标题) → 16px (区块标题)
/// → 14px (卡片标题/价格) → 13px (正文) → 12px (标签/副标题) → 11px (小文字)
/// 對齊 Pencil 量: Inter 字, 明的字/字重層級
/// 28px (總資產) → 24px (精選價格) → 22px (頁面標題) → 16px (區塊標題)
/// → 14px (卡片標題/價格) → 13px (正文) → 12px (標籤/副標題) → 11px (小文字)
class AppTextStyles {
AppTextStyles._();
// ============================================
// Display - 核心
// Display - 核心
// ============================================
/// D1 - 总资产/余额 (28px w700) — Pencil $hero-value
/// D1 - 總資產/餘額 (28px w700) — Pencil $hero-value
static TextStyle displayLarge(BuildContext context) => GoogleFonts.inter(
fontSize: 28,
fontWeight: FontWeight.w700,
@@ -244,7 +244,7 @@ class AppTextStyles {
letterSpacing: -0.5,
);
/// D2 - 精选价格 (24px w700) — Pencil 行情卡片
/// D2 - 精選價格 (24px w700) — Pencil 行情卡片
static TextStyle displayMedium(BuildContext context) => GoogleFonts.inter(
fontSize: 24,
fontWeight: FontWeight.w700,
@@ -252,7 +252,7 @@ class AppTextStyles {
height: 1.15,
);
/// D3 - 页面标题 (22px w700) — Pencil 面大标题
/// D3 - 頁面標題 (22px w700) — Pencil 面大標題
static TextStyle displaySmall(BuildContext context) => GoogleFonts.inter(
fontSize: 22,
fontWeight: FontWeight.w700,
@@ -261,10 +261,10 @@ class AppTextStyles {
);
// ============================================
// Headline - 标题
// Headline - 標題
// ============================================
/// 区块/导航标题 (16px w600) — Pencil $section-title
/// 區塊/導航標題 (16px w600) — Pencil $section-title
static TextStyle headlineLarge(BuildContext context) => GoogleFonts.inter(
fontSize: 16,
fontWeight: FontWeight.w600,
@@ -272,7 +272,7 @@ class AppTextStyles {
height: 1.3,
);
/// 卡片标题/价格/标签页 (14px w600) — Pencil $card-title
/// 卡片標題/價格/標籤頁 (14px w600) — Pencil $card-title
static TextStyle headlineMedium(BuildContext context) => GoogleFonts.inter(
fontSize: 14,
fontWeight: FontWeight.w600,
@@ -280,7 +280,7 @@ class AppTextStyles {
height: 1.35,
);
/// 副标题/持仓价值 (13px w500)
/// 副標題/持倉價值 (13px w500)
static TextStyle headlineSmall(BuildContext context) => GoogleFonts.inter(
fontSize: 13,
fontWeight: FontWeight.w500,
@@ -300,7 +300,7 @@ class AppTextStyles {
height: 1.45,
);
/// 次要正文/副标题 (12px w400) — Pencil $subtitle
/// 次要正文/副標題 (12px w400) — Pencil $subtitle
static TextStyle bodyMedium(BuildContext context) => GoogleFonts.inter(
fontSize: 12,
fontWeight: FontWeight.w400,
@@ -308,7 +308,7 @@ class AppTextStyles {
height: 1.45,
);
/// 助文字/币种全名 (11px w400) — Pencil $small-text
/// 助文字/幣種全名 (11px w400) — Pencil $small-text
static TextStyle bodySmall(BuildContext context) => GoogleFonts.inter(
fontSize: 11,
fontWeight: FontWeight.w400,
@@ -317,10 +317,10 @@ class AppTextStyles {
);
// ============================================
// Label - 标签
// Label - 標籤
// ============================================
/// 按钮/标签页标签 (12px w500) — Pencil $tab-label
/// 按鈕/標籤頁標籤 (12px w500) — Pencil $tab-label
static TextStyle labelLarge(BuildContext context) => GoogleFonts.inter(
fontSize: 12,
fontWeight: FontWeight.w500,
@@ -328,7 +328,7 @@ class AppTextStyles {
height: 1.35,
);
/// 跌幅标签 (11px w500) — Pencil $change-badge
/// 跌幅標籤 (11px w500) — Pencil $change-badge
static TextStyle labelMedium(BuildContext context) => GoogleFonts.inter(
fontSize: 11,
fontWeight: FontWeight.w500,
@@ -336,7 +336,7 @@ class AppTextStyles {
height: 1.35,
);
/// 跌幅标签-粗 (11px w600) — Pencil $change-badge-bold
/// 跌幅標籤-粗 (11px w600) — Pencil $change-badge-bold
static TextStyle labelSmall(BuildContext context) => GoogleFonts.inter(
fontSize: 11,
fontWeight: FontWeight.w600,
@@ -345,10 +345,10 @@ class AppTextStyles {
);
// ============================================
// 字/金 - Inter (等特性)
// 字/金 - Inter (等特性)
// ============================================
/// 大号数字 (28px w700) - 总资产、余额
/// 大號數字 (28px w700) - 總資產、餘額
static TextStyle numberLarge(BuildContext context) => GoogleFonts.inter(
fontSize: 28,
fontWeight: FontWeight.w700,
@@ -358,7 +358,7 @@ class AppTextStyles {
fontFeatures: const [FontFeature.tabularFigures()],
);
/// 中号数字 (14px w600) - 格、金
/// 中號數字 (14px w600) - 格、金
static TextStyle numberMedium(BuildContext context) => GoogleFonts.inter(
fontSize: 14,
fontWeight: FontWeight.w600,
@@ -367,7 +367,7 @@ class AppTextStyles {
fontFeatures: const [FontFeature.tabularFigures()],
);
/// 小号数字 (12px w500) - 跌幅、
/// 小號數字 (12px w500) - 跌幅、
static TextStyle numberSmall(BuildContext context) => GoogleFonts.inter(
fontSize: 12,
fontWeight: FontWeight.w500,
@@ -377,7 +377,7 @@ class AppTextStyles {
);
}
/// 动画时长
/// 動畫時長
class AnimationDurations {
AnimationDurations._();

View File

@@ -1,48 +1,48 @@
import 'package:flutter/material.dart';
import 'app_color_scheme.dart';
/// 自定义主题扩展 — 注册到 ThemeData.extensions 中
/// 自定義主題擴展 — 註冊到 ThemeData.extensions 中
///
/// 存放 ColorScheme 标准槽位法覆盖的语义色:
/// - 卡片背景语义dark/light 使用不同 surface 层级
/// - 跌金融色(含透明度的背景色)
/// - 渐变预设
/// 存放 ColorScheme 標準槽位法覆蓋的語義色:
/// - 卡片背景語義dark/light 使用不同 surface 層級
/// - 跌金融色(含透明度的背景色)
/// - 漸變預設
/// - 光效透明度等
@immutable
class AppThemeColors extends ThemeExtension<AppThemeColors> {
// ---- 卡片背景语义色 ----
/// 标准卡片背景dark: surfaceContainer, light: surfaceContainerHigh
// ---- 卡片背景語義色 ----
/// 標準卡片背景dark: surfaceContainer, light: surfaceContainerHigh
final Color surfaceCard;
/// 高亮卡片背景dark: surfaceContainerHighest, light: surfaceContainerHigh
final Color surfaceCardHigh;
// ---- 文字语义 ----
/// 弱化/助文字
// ---- 文字語義 ----
/// 弱化/助文字
final Color onSurfaceMuted;
// ---- 跌金融色 ----
/// /盈利/
// ---- 跌金融色 ----
/// /盈利/
final Color up;
final Color upBackground;
/// 跌/亏损/卖
/// 跌/虧損/賣
final Color down;
final Color downBackground;
// ---- 强调色 ----
/// 主强调dark: primary 金色, light: primary 深灰)
// ---- 強調色 ----
/// 主強調dark: primary 金色, light: primary 深灰)
final Color accentPrimary;
// ---- 光效 ----
/// 光透明度dark: 0.15, light: 0.08
/// 光透明度dark: 0.15, light: 0.08
final double glowOpacity;
// ---- 框 ----
/// Ghost BorderoutlineVariant 透明度)
// ---- 框 ----
/// Ghost BorderoutlineVariant 透明度)
final Color ghostBorder;
// ---- 渐变预设 ----
// ---- 漸變預設 ----
final LinearGradient ctaGradient;
final LinearGradient buyGradient;
final LinearGradient sellGradient;
@@ -67,7 +67,7 @@ class AppThemeColors extends ThemeExtension<AppThemeColors> {
required this.emeraldGradient,
});
/// 深色主题工厂
/// 深色主題工廠
factory AppThemeColors.dark() => AppThemeColors(
surfaceCard: AppColorScheme.darkSurfaceContainer,
surfaceCardHigh: AppColorScheme.darkSurfaceContainerHighest,
@@ -90,7 +90,7 @@ class AppThemeColors extends ThemeExtension<AppThemeColors> {
),
);
/// 色主题工厂
/// 色主題工廠
factory AppThemeColors.light() => AppThemeColors(
surfaceCard: AppColorScheme.lightSurfaceLowest,
surfaceCardHigh: AppColorScheme.lightSurfaceContainer,
@@ -198,23 +198,23 @@ class AppThemeColors extends ThemeExtension<AppThemeColors> {
static double lerpDouble(double a, double b, double t) => a + (b - a) * t;
}
/// BuildContext 主快捷
/// BuildContext 主快捷
///
/// 用法:
/// context.colors.primary → Theme.of(context).colorScheme.primary
/// context.appColors.up → AppThemeColors 中的
/// context.appColors.up → AppThemeColors 中的
/// context.isDark → 是否深色模式
extension AppThemeContext on BuildContext {
/// Material ColorScheme 快捷访问
/// Material ColorScheme 快捷訪問
ColorScheme get colors => Theme.of(this).colorScheme;
/// 自定义语义色快捷访问
/// 自定義語義色快捷訪問
AppThemeColors get appColors =>
Theme.of(this).extension<AppThemeColors>()!;
/// 是否深色模式
bool get isDark => Theme.of(this).brightness == Brightness.dark;
/// TextTheme 快捷访问
/// TextTheme 快捷訪問
TextTheme get textStyles => Theme.of(this).textTheme;
}

View File

@@ -1,13 +1,13 @@
import 'package:flutter/material.dart';
import 'package:bot_toast/bot_toast.dart';
/// Toast 工具 - 提供一的 toast 提示功能
/// Toast 工具 - 提供一的 toast 提示功能
///
/// 使用 bot_toast 实现,确保 toast 示在所有窗之上
/// 使用 bot_toast 實現,確保 toast 示在所有窗之上
class ToastUtils {
ToastUtils._();
/// 示普通提示
/// 示普通提示
static void show(String message, {Duration? duration}) {
BotToast.showCustomText(
toastBuilder: (_) => _buildToastWidget(message),
@@ -18,7 +18,7 @@ class ToastUtils {
);
}
/// 示成功提示
/// 示成功提示
static void showSuccess(String message, {Duration? duration}) {
BotToast.showCustomText(
toastBuilder: (_) => _buildToastWidget(
@@ -32,7 +32,7 @@ class ToastUtils {
);
}
/// 显示错误提示
/// 顯示錯誤提示
static void showError(String message, {Duration? duration}) {
BotToast.showCustomText(
toastBuilder: (_) => _buildToastWidget(
@@ -46,7 +46,7 @@ class ToastUtils {
);
}
/// 示警告提示
/// 示警告提示
static void showWarning(String message, {Duration? duration}) {
BotToast.showCustomText(
toastBuilder: (_) => _buildToastWidget(
@@ -60,7 +60,7 @@ class ToastUtils {
);
}
/// 建 toast widget
/// 建 toast widget
static Widget _buildToastWidget(
String message, {
Color? backgroundColor,