Flutter v0.1 draft ok

This commit is contained in:
hailin 2025-11-25 20:09:50 -08:00
parent 79f8f4f4ee
commit 2915c4ccc5
62 changed files with 8007 additions and 189 deletions

View File

@ -1,2 +1,2 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
org.gradle.jvmargs=-Xmx1536m -XX:MaxMetaspaceSize=384m -XX:ReservedCodeCacheSize=256m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true

View File

@ -0,0 +1,4 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="40" height="40" rx="8" fill="#D4AF37" fill-opacity="0.1"/>
<path d="M13.1944 26.8056V13.1944C13.1944 13.1944 13.1944 13.555 13.1944 14.276C13.1944 14.9971 13.1944 15.9329 13.1944 17.0833V22.9167C13.1944 24.0671 13.1944 25.0029 13.1944 25.724C13.1944 26.445 13.1944 26.8056 13.1944 26.8056ZM13.1944 28.75C12.6597 28.75 12.202 28.5596 11.8212 28.1788C11.4404 27.798 11.25 27.3403 11.25 26.8056V13.1944C11.25 12.6597 11.4404 12.202 11.8212 11.8212C12.202 11.4404 12.6597 11.25 13.1944 11.25H26.8056C27.3403 11.25 27.798 11.4404 28.1788 11.8212C28.5596 12.202 28.75 12.6597 28.75 13.1944V15.625H26.8056V13.1944H13.1944V26.8056H26.8056V24.375H28.75V26.8056C28.75 27.3403 28.5596 27.798 28.1788 28.1788C27.798 28.5596 27.3403 28.75 26.8056 28.75H13.1944ZM20.9722 24.8611C20.4375 24.8611 19.9797 24.6707 19.599 24.2899C19.2182 23.9091 19.0278 23.4514 19.0278 22.9167V17.0833C19.0278 16.5486 19.2182 16.0909 19.599 15.7101C19.9797 15.3293 20.4375 15.1389 20.9722 15.1389H27.7778C28.3125 15.1389 28.7703 15.3293 29.151 15.7101C29.5318 16.0909 29.7222 16.5486 29.7222 17.0833V22.9167C29.7222 23.4514 29.5318 23.9091 29.151 24.2899C28.7703 24.6707 28.3125 24.8611 27.7778 24.8611H20.9722ZM27.7778 22.9167V17.0833H20.9722V22.9167H27.7778ZM23.8889 21.4583C24.294 21.4583 24.6383 21.3166 24.9219 21.033C25.2054 20.7494 25.3472 20.4051 25.3472 20C25.3472 19.5949 25.2054 19.2506 24.9219 18.967C24.6383 18.6834 24.294 18.5417 23.8889 18.5417C23.4838 18.5417 23.1395 18.6834 22.8559 18.967C22.5723 19.2506 22.4306 19.5949 22.4306 20C22.4306 20.4051 22.5723 20.7494 22.8559 21.033C23.1395 21.3166 23.4838 21.4583 23.8889 21.4583Z" fill="#D4AF37"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,4 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="40" height="40" rx="8" fill="#D4AF37" fill-opacity="0.1"/>
<path d="M15.1389 21.9445C14.6042 21.9445 14.1464 21.7541 13.7656 21.3733C13.3848 20.9925 13.1944 20.5348 13.1944 20C13.1944 19.4653 13.3848 19.0076 13.7656 18.6268C14.1464 18.246 14.6042 18.0556 15.1389 18.0556C15.6736 18.0556 16.1314 18.246 16.5121 18.6268C16.8929 19.0076 17.0833 19.4653 17.0833 20C17.0833 20.5348 16.8929 20.9925 16.5121 21.3733C16.1314 21.7541 15.6736 21.9445 15.1389 21.9445ZM15.1389 25.8334C13.5185 25.8334 12.1412 25.2662 11.0069 24.132C9.87267 22.9977 9.30554 21.6204 9.30554 20C9.30554 18.3797 9.87267 17.0024 11.0069 15.8681C12.1412 14.7338 13.5185 14.1667 15.1389 14.1667C16.2245 14.1667 17.2089 14.4341 18.092 14.9688C18.9751 15.5035 19.6759 16.2084 20.1944 17.0834H28.75L31.6667 20L27.2917 24.375L25.3472 22.9167L23.4028 24.375L21.3368 22.9167H20.1944C19.6759 23.7917 18.9751 24.4966 18.092 25.0313C17.2089 25.566 16.2245 25.8334 15.1389 25.8334ZM15.1389 23.8889C16.0463 23.8889 16.8443 23.6135 17.533 23.0625C18.2216 22.5116 18.6794 21.8149 18.9062 20.9723H21.9444L23.3542 21.9688L25.3472 20.4862L27.0729 21.823L28.8958 20L27.9236 19.0278H18.9062C18.6794 18.1852 18.2216 17.4885 17.533 16.9375C16.8443 16.3866 16.0463 16.1112 15.1389 16.1112C14.0694 16.1112 13.1539 16.4919 12.3923 17.2535C11.6308 18.0151 11.25 18.9306 11.25 20C11.25 21.0695 11.6308 21.985 12.3923 22.7466C13.1539 23.5081 14.0694 23.8889 15.1389 23.8889Z" fill="#D4AF37"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,4 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="48" height="48" rx="8" fill="#D4AF37" fill-opacity="0.2"/>
<path d="M18.1667 31.7778L19.1389 27.8889H15.25L15.7361 25.9445H19.625L20.5972 22.0556H16.7083L17.1944 20.1112H21.0833L22.0556 16.2223H24L23.0278 20.1112H26.9167L27.8889 16.2223H29.8333L28.8611 20.1112H32.75L32.2639 22.0556H28.375L27.4028 25.9445H31.2917L30.8056 27.8889H26.9167L25.9444 31.7778H24L24.9722 27.8889H21.0833L20.1111 31.7778H18.1667ZM21.5694 25.9445H25.4583L26.4306 22.0556H22.5417L21.5694 25.9445Z" fill="#D4AF37"/>
</svg>

After

Width:  |  Height:  |  Size: 605 B

View File

@ -0,0 +1,28 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'core/theme/app_theme.dart';
import 'routes/app_router.dart';
class App extends ConsumerWidget {
const App({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final router = ref.watch(appRouterProvider);
return ScreenUtilInit(
designSize: const Size(375, 812),
minTextAdapt: true,
splitScreenMode: true,
builder: (context, child) {
return MaterialApp.router(
title: 'RWA榴莲女皇',
debugShowCheckedModeBanner: false,
theme: AppTheme.light,
routerConfig: router,
);
},
);
}
}

View File

@ -0,0 +1,50 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'core/storage/local_storage.dart';
import 'core/di/injection_container.dart';
import 'core/utils/logger.dart';
Future<void> bootstrap(FutureOr<Widget> Function() builder) async {
// Ensure Flutter bindings are initialized
WidgetsFlutterBinding.ensureInitialized();
// Set preferred orientations
await SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
// Set system UI overlay style
SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.dark,
systemNavigationBarColor: Colors.white,
systemNavigationBarIconBrightness: Brightness.dark,
),
);
// Initialize Hive
await Hive.initFlutter();
// Initialize LocalStorage
final localStorage = await LocalStorage.init();
// Create provider container with initialized dependencies
final container = createProviderContainer(localStorage);
// Run app with error handling
FlutterError.onError = (details) {
AppLogger.e('Flutter Error', details.exception, details.stack);
};
runApp(
UncontrolledProviderScope(
container: container,
child: await builder(),
),
);
}

View File

@ -0,0 +1,65 @@
class ApiEndpoints {
ApiEndpoints._();
// Base URL
static const String baseUrl = 'https://api.rwadurian.com';
static const String baseUrlDev = 'https://api-dev.rwadurian.com';
// Auth
static const String auth = '/auth';
static const String login = '$auth/login';
static const String register = '$auth/register';
static const String refreshToken = '$auth/refresh';
static const String logout = '$auth/logout';
// User
static const String user = '/user';
static const String profile = '$user/profile';
static const String updateProfile = '$user/profile/update';
static const String updateAvatar = '$user/avatar';
// Wallet
static const String wallet = '/wallet';
static const String createWallet = '$wallet/create';
static const String importWallet = '$wallet/import';
static const String balance = '$wallet/balance';
// Mining
static const String mining = '/mining';
static const String miningStatus = '$mining/status';
static const String startMining = '$mining/start';
static const String stopMining = '$mining/stop';
static const String claimReward = '$mining/claim';
static const String hashPower = '$mining/hash-power';
// Ranking
static const String ranking = '/ranking';
static const String dailyRanking = '$ranking/daily';
static const String weeklyRanking = '$ranking/weekly';
static const String monthlyRanking = '$ranking/monthly';
// Trading
static const String trading = '/trading';
static const String exchange = '$trading/exchange';
static const String settlement = '$trading/settlement';
static const String transactions = '$trading/transactions';
// Deposit
static const String deposit = '/deposit';
static const String depositAddress = '$deposit/address';
static const String confirmDeposit = '$deposit/confirm';
static const String depositRecords = '$deposit/records';
// Planting
static const String planting = '/planting';
static const String plantingPrice = '$planting/price';
static const String submitPlanting = '$planting/submit';
static const String provinces = '$planting/provinces';
static const String cities = '$planting/cities';
// Community
static const String community = '/community';
static const String referralList = '$community/referrals';
static const String earnings = '$community/earnings';
static const String claimEarnings = '$community/claim';
}

View File

@ -0,0 +1,30 @@
class AppConstants {
AppConstants._();
// App Info
static const String appName = 'RWA榴莲女皇';
static const String appVersion = '1.0.0';
// Animation Durations
static const Duration animationFast = Duration(milliseconds: 200);
static const Duration animationNormal = Duration(milliseconds: 300);
static const Duration animationSlow = Duration(milliseconds: 500);
// Splash Screen
static const Duration splashDuration = Duration(seconds: 2);
// Pagination
static const int pageSize = 20;
// Timeout
static const Duration connectionTimeout = Duration(seconds: 30);
static const Duration receiveTimeout = Duration(seconds: 30);
// Mining
static const Duration miningCycleDuration = Duration(hours: 24);
// Validation
static const int mnemonicWordCount = 12;
static const int passwordMinLength = 6;
static const int passwordMaxLength = 20;
}

View File

@ -0,0 +1,21 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../storage/secure_storage.dart';
import '../storage/local_storage.dart';
// Storage Providers
final secureStorageProvider = Provider<SecureStorage>((ref) {
return SecureStorage();
});
final localStorageProvider = Provider<LocalStorage>((ref) {
throw UnimplementedError('LocalStorage must be initialized before use');
});
// Override provider with initialized instance
ProviderContainer createProviderContainer(LocalStorage localStorage) {
return ProviderContainer(
overrides: [
localStorageProvider.overrideWithValue(localStorage),
],
);
}

View File

@ -0,0 +1,72 @@
import 'package:dio/dio.dart';
import 'package:logger/logger.dart';
import 'exceptions.dart';
import 'failures.dart';
class ErrorHandler {
static final Logger _logger = Logger();
static Failure handleException(dynamic exception) {
_logger.e('Error occurred', error: exception);
if (exception is ServerException) {
return ServerFailure(exception.message);
} else if (exception is NetworkException) {
return NetworkFailure(exception.message);
} else if (exception is CacheException) {
return CacheFailure(exception.message);
} else if (exception is AuthException) {
return AuthFailure(exception.message);
} else if (exception is WalletException) {
return WalletFailure(exception.message);
} else if (exception is BlockchainException) {
return BlockchainFailure(exception.message);
} else if (exception is ValidationException) {
return ValidationFailure(exception.message);
} else if (exception is StorageException) {
return StorageFailure(exception.message);
} else if (exception is DioException) {
return _handleDioError(exception);
} else {
return ServerFailure(exception.toString());
}
}
static Failure _handleDioError(DioException error) {
switch (error.type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.sendTimeout:
case DioExceptionType.receiveTimeout:
return const NetworkFailure('连接超时,请检查网络');
case DioExceptionType.connectionError:
return const NetworkFailure('网络连接失败');
case DioExceptionType.badResponse:
return _handleBadResponse(error.response?.statusCode);
case DioExceptionType.cancel:
return const ServerFailure('请求已取消');
default:
return const ServerFailure('未知错误');
}
}
static Failure _handleBadResponse(int? statusCode) {
switch (statusCode) {
case 400:
return const ServerFailure('请求参数错误');
case 401:
return const AuthFailure('登录已过期,请重新登录');
case 403:
return const AuthFailure('没有权限访问');
case 404:
return const ServerFailure('请求的资源不存在');
case 500:
return const ServerFailure('服务器内部错误');
case 502:
return const ServerFailure('服务器网关错误');
case 503:
return const ServerFailure('服务暂不可用');
default:
return ServerFailure('服务器错误 ($statusCode)');
}
}
}

View File

@ -0,0 +1,75 @@
class ServerException implements Exception {
final String message;
final int? statusCode;
const ServerException({
this.message = '服务器错误',
this.statusCode,
});
@override
String toString() => 'ServerException: $message (status: $statusCode)';
}
class NetworkException implements Exception {
final String message;
const NetworkException([this.message = '网络连接失败']);
@override
String toString() => 'NetworkException: $message';
}
class CacheException implements Exception {
final String message;
const CacheException([this.message = '缓存错误']);
@override
String toString() => 'CacheException: $message';
}
class AuthException implements Exception {
final String message;
const AuthException([this.message = '认证失败']);
@override
String toString() => 'AuthException: $message';
}
class WalletException implements Exception {
final String message;
const WalletException([this.message = '钱包操作失败']);
@override
String toString() => 'WalletException: $message';
}
class BlockchainException implements Exception {
final String message;
const BlockchainException([this.message = '区块链交互失败']);
@override
String toString() => 'BlockchainException: $message';
}
class ValidationException implements Exception {
final String message;
const ValidationException([this.message = '验证失败']);
@override
String toString() => 'ValidationException: $message';
}
class StorageException implements Exception {
final String message;
const StorageException([this.message = '存储操作失败']);
@override
String toString() => 'StorageException: $message';
}

View File

@ -0,0 +1,41 @@
import 'package:equatable/equatable.dart';
abstract class Failure extends Equatable {
final String message;
const Failure(this.message);
@override
List<Object> get props => [message];
}
class ServerFailure extends Failure {
const ServerFailure([super.message = '服务器错误']);
}
class NetworkFailure extends Failure {
const NetworkFailure([super.message = '网络连接失败']);
}
class CacheFailure extends Failure {
const CacheFailure([super.message = '缓存错误']);
}
class AuthFailure extends Failure {
const AuthFailure([super.message = '认证失败']);
}
class WalletFailure extends Failure {
const WalletFailure([super.message = '钱包操作失败']);
}
class BlockchainFailure extends Failure {
const BlockchainFailure([super.message = '区块链交互失败']);
}
class ValidationFailure extends Failure {
const ValidationFailure([super.message = '验证失败']);
}
class StorageFailure extends Failure {
const StorageFailure([super.message = '存储操作失败']);
}

View File

@ -0,0 +1,52 @@
import 'package:flutter/material.dart';
extension ContextExtensions on BuildContext {
// Theme
ThemeData get theme => Theme.of(this);
TextTheme get textTheme => Theme.of(this).textTheme;
ColorScheme get colorScheme => Theme.of(this).colorScheme;
// MediaQuery
MediaQueryData get mediaQuery => MediaQuery.of(this);
Size get screenSize => MediaQuery.of(this).size;
double get screenWidth => MediaQuery.of(this).size.width;
double get screenHeight => MediaQuery.of(this).size.height;
EdgeInsets get padding => MediaQuery.of(this).padding;
EdgeInsets get viewInsets => MediaQuery.of(this).viewInsets;
// Navigation
void pop<T>([T? result]) => Navigator.of(this).pop(result);
Future<T?> push<T>(Widget page) => Navigator.of(this).push<T>(
MaterialPageRoute(builder: (_) => page),
);
// SnackBar
void showSnackBar(String message, {Duration? duration}) {
ScaffoldMessenger.of(this).showSnackBar(
SnackBar(
content: Text(message),
duration: duration ?? const Duration(seconds: 2),
),
);
}
void showErrorSnackBar(String message) {
ScaffoldMessenger.of(this).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: Colors.red,
duration: const Duration(seconds: 3),
),
);
}
void showSuccessSnackBar(String message) {
ScaffoldMessenger.of(this).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: Colors.green,
duration: const Duration(seconds: 2),
),
);
}
}

View File

@ -0,0 +1,30 @@
import 'package:intl/intl.dart';
extension NumExtensions on num {
String get formatted {
return NumberFormat('#,##0.##').format(this);
}
String get formattedCurrency {
return NumberFormat('#,##0.00').format(this);
}
String get formattedPercent {
return NumberFormat('0.##%').format(this / 100);
}
String get compact {
if (this >= 1000000000) {
return '${(this / 1000000000).toStringAsFixed(1)}B';
} else if (this >= 1000000) {
return '${(this / 1000000).toStringAsFixed(1)}M';
} else if (this >= 1000) {
return '${(this / 1000).toStringAsFixed(1)}K';
}
return toString();
}
String toFixedNoTrailing(int fractionDigits) {
return toStringAsFixed(fractionDigits).replaceAll(RegExp(r'\.?0+$'), '');
}
}

View File

@ -0,0 +1,35 @@
extension StringExtensions on String {
// Validation
bool get isValidEmail {
return RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(this);
}
bool get isValidPhone {
return RegExp(r'^1[3-9]\d{9}$').hasMatch(this);
}
bool get isValidWalletAddress {
return RegExp(r'^0x[a-fA-F0-9]{40}$').hasMatch(this);
}
// Formatting
String get capitalize {
if (isEmpty) return this;
return '${this[0].toUpperCase()}${substring(1)}';
}
String truncate(int maxLength, {String suffix = '...'}) {
if (length <= maxLength) return this;
return '${substring(0, maxLength - suffix.length)}$suffix';
}
String get maskMiddle {
if (length <= 8) return this;
return '${substring(0, 4)}****${substring(length - 4)}';
}
String get maskWalletAddress {
if (length <= 10) return this;
return '${substring(0, 6)}...${substring(length - 4)}';
}
}

View File

@ -0,0 +1,119 @@
import 'package:shared_preferences/shared_preferences.dart';
import '../errors/exceptions.dart';
class LocalStorage {
final SharedPreferences _prefs;
LocalStorage(this._prefs);
static Future<LocalStorage> init() async {
final prefs = await SharedPreferences.getInstance();
return LocalStorage(prefs);
}
// String
Future<bool> setString(String key, String value) async {
try {
return await _prefs.setString(key, value);
} catch (e) {
throw StorageException('写入本地存储失败: $e');
}
}
String? getString(String key) {
try {
return _prefs.getString(key);
} catch (e) {
throw StorageException('读取本地存储失败: $e');
}
}
// Int
Future<bool> setInt(String key, int value) async {
try {
return await _prefs.setInt(key, value);
} catch (e) {
throw StorageException('写入本地存储失败: $e');
}
}
int? getInt(String key) {
try {
return _prefs.getInt(key);
} catch (e) {
throw StorageException('读取本地存储失败: $e');
}
}
// Double
Future<bool> setDouble(String key, double value) async {
try {
return await _prefs.setDouble(key, value);
} catch (e) {
throw StorageException('写入本地存储失败: $e');
}
}
double? getDouble(String key) {
try {
return _prefs.getDouble(key);
} catch (e) {
throw StorageException('读取本地存储失败: $e');
}
}
// Bool
Future<bool> setBool(String key, bool value) async {
try {
return await _prefs.setBool(key, value);
} catch (e) {
throw StorageException('写入本地存储失败: $e');
}
}
bool? getBool(String key) {
try {
return _prefs.getBool(key);
} catch (e) {
throw StorageException('读取本地存储失败: $e');
}
}
// StringList
Future<bool> setStringList(String key, List<String> value) async {
try {
return await _prefs.setStringList(key, value);
} catch (e) {
throw StorageException('写入本地存储失败: $e');
}
}
List<String>? getStringList(String key) {
try {
return _prefs.getStringList(key);
} catch (e) {
throw StorageException('读取本地存储失败: $e');
}
}
// Remove & Clear
Future<bool> remove(String key) async {
try {
return await _prefs.remove(key);
} catch (e) {
throw StorageException('删除本地存储失败: $e');
}
}
Future<bool> clear() async {
try {
return await _prefs.clear();
} catch (e) {
throw StorageException('清空本地存储失败: $e');
}
}
bool containsKey(String key) {
return _prefs.containsKey(key);
}
}

View File

@ -0,0 +1,61 @@
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import '../errors/exceptions.dart';
class SecureStorage {
final FlutterSecureStorage _storage;
SecureStorage({FlutterSecureStorage? storage})
: _storage = storage ??
const FlutterSecureStorage(
aOptions: AndroidOptions(encryptedSharedPreferences: true),
iOptions: IOSOptions(accessibility: KeychainAccessibility.first_unlock),
);
Future<void> write({required String key, required String value}) async {
try {
await _storage.write(key: key, value: value);
} catch (e) {
throw StorageException('写入安全存储失败: $e');
}
}
Future<String?> read({required String key}) async {
try {
return await _storage.read(key: key);
} catch (e) {
throw StorageException('读取安全存储失败: $e');
}
}
Future<void> delete({required String key}) async {
try {
await _storage.delete(key: key);
} catch (e) {
throw StorageException('删除安全存储失败: $e');
}
}
Future<void> deleteAll() async {
try {
await _storage.deleteAll();
} catch (e) {
throw StorageException('清空安全存储失败: $e');
}
}
Future<bool> containsKey({required String key}) async {
try {
return await _storage.containsKey(key: key);
} catch (e) {
throw StorageException('检查安全存储失败: $e');
}
}
Future<Map<String, String>> readAll() async {
try {
return await _storage.readAll();
} catch (e) {
throw StorageException('读取所有安全存储失败: $e');
}
}
}

View File

@ -0,0 +1,27 @@
class StorageKeys {
StorageKeys._();
// Auth
static const String walletAddress = 'wallet_address';
static const String privateKey = 'private_key';
static const String mnemonic = 'mnemonic';
static const String isWalletCreated = 'is_wallet_created';
static const String isMnemonicBackedUp = 'is_mnemonic_backed_up';
// User
static const String userId = 'user_id';
static const String userProfile = 'user_profile';
static const String accessToken = 'access_token';
static const String refreshToken = 'refresh_token';
// Settings
static const String locale = 'locale';
static const String themeMode = 'theme_mode';
static const String isFirstLaunch = 'is_first_launch';
static const String biometricEnabled = 'biometric_enabled';
// Cache
static const String lastSyncTime = 'last_sync_time';
static const String cachedRankingData = 'cached_ranking_data';
static const String cachedMiningStatus = 'cached_mining_status';
}

View File

@ -0,0 +1,49 @@
import 'package:flutter/material.dart';
class AppColors {
AppColors._();
// -
static const Color primary = Color(0xFFD4A84B);
static const Color primaryLight = Color(0xFFF5E6C8);
static const Color primaryDark = Color(0xFFB8923F);
//
static const Color background = Color(0xFFFAF8F5);
static const Color cardBackground = Color(0xFFFFFDF8);
static const Color surface = Color(0xFFFFFFFF);
//
static const Color textPrimary = Color(0xFF333333);
static const Color textSecondary = Color(0xFF666666);
static const Color textHint = Color(0xFF999999);
static const Color textOnPrimary = Color(0xFFFFFFFF);
//
static const Color success = Color(0xFF52C41A);
static const Color warning = Color(0xFFFFAA00);
static const Color error = Color(0xFFFF4D4F);
static const Color info = Color(0xFF1890FF);
//
static const Color border = Color(0xFFE8E8E8);
static const Color divider = Color(0xFFF0F0F0);
//
static const Color crownGold = Color(0xFFFFD700);
static const Color crownSilver = Color(0xFFC0C0C0);
static const Color crownBronze = Color(0xFFCD7F32);
//
static const LinearGradient primaryGradient = LinearGradient(
colors: [primary, primaryDark],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
static const LinearGradient goldGradient = LinearGradient(
colors: [Color(0xFFFFD700), Color(0xFFD4A84B)],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
);
}

View File

@ -0,0 +1,50 @@
class AppDimensions {
AppDimensions._();
//
static const double spacingXs = 4.0;
static const double spacingSm = 8.0;
static const double spacingMd = 16.0;
static const double spacingLg = 24.0;
static const double spacingXl = 32.0;
static const double spacingXxl = 48.0;
//
static const double radiusXs = 4.0;
static const double radiusSm = 8.0;
static const double radiusMd = 12.0;
static const double radiusLg = 16.0;
static const double radiusXl = 24.0;
static const double radiusFull = 999.0;
//
static const double iconXs = 16.0;
static const double iconSm = 20.0;
static const double iconMd = 24.0;
static const double iconLg = 32.0;
static const double iconXl = 48.0;
//
static const double buttonHeightSm = 36.0;
static const double buttonHeightMd = 44.0;
static const double buttonHeightLg = 52.0;
//
static const double inputHeight = 48.0;
//
static const double avatarSm = 32.0;
static const double avatarMd = 48.0;
static const double avatarLg = 64.0;
static const double avatarXl = 96.0;
//
static const double cardPadding = 16.0;
static const double cardRadius = 12.0;
//
static const double bottomNavHeight = 56.0;
// AppBar
static const double appBarHeight = 56.0;
}

View File

@ -0,0 +1,36 @@
import 'package:flutter/material.dart';
import 'app_colors.dart';
class AppGradients {
AppGradients._();
static const LinearGradient primary = LinearGradient(
colors: [AppColors.primary, AppColors.primaryDark],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
static const LinearGradient gold = LinearGradient(
colors: [Color(0xFFFFD700), Color(0xFFD4A84B)],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
);
static const LinearGradient silver = LinearGradient(
colors: [Color(0xFFE8E8E8), Color(0xFFC0C0C0)],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
);
static const LinearGradient bronze = LinearGradient(
colors: [Color(0xFFE0A070), Color(0xFFCD7F32)],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
);
static const LinearGradient background = LinearGradient(
colors: [Color(0xFFFAF8F5), Color(0xFFF5F3F0)],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
);
}

View File

@ -0,0 +1,37 @@
import 'package:flutter/material.dart';
class AppShadows {
AppShadows._();
static List<BoxShadow> get small => [
BoxShadow(
color: Colors.black.withOpacity(0.04),
blurRadius: 4,
offset: const Offset(0, 2),
),
];
static List<BoxShadow> get medium => [
BoxShadow(
color: Colors.black.withOpacity(0.08),
blurRadius: 8,
offset: const Offset(0, 4),
),
];
static List<BoxShadow> get large => [
BoxShadow(
color: Colors.black.withOpacity(0.12),
blurRadius: 16,
offset: const Offset(0, 8),
),
];
static List<BoxShadow> get card => [
BoxShadow(
color: Colors.black.withOpacity(0.06),
blurRadius: 12,
offset: const Offset(0, 4),
),
];
}

View File

@ -0,0 +1,118 @@
import 'package:flutter/material.dart';
import 'app_colors.dart';
class AppTextStyles {
AppTextStyles._();
//
static const TextStyle headline1 = TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: AppColors.textPrimary,
height: 1.3,
);
static const TextStyle headline2 = TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: AppColors.textPrimary,
height: 1.3,
);
static const TextStyle headline3 = TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: AppColors.textPrimary,
height: 1.3,
);
static const TextStyle headline4 = TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: AppColors.textPrimary,
height: 1.3,
);
//
static const TextStyle bodyLarge = TextStyle(
fontSize: 16,
fontWeight: FontWeight.normal,
color: AppColors.textPrimary,
height: 1.5,
);
static const TextStyle bodyMedium = TextStyle(
fontSize: 14,
fontWeight: FontWeight.normal,
color: AppColors.textPrimary,
height: 1.5,
);
static const TextStyle bodySmall = TextStyle(
fontSize: 12,
fontWeight: FontWeight.normal,
color: AppColors.textSecondary,
height: 1.5,
);
//
static const TextStyle bodyWhite = TextStyle(
fontSize: 14,
fontWeight: FontWeight.normal,
color: AppColors.textOnPrimary,
height: 1.5,
);
static const TextStyle headlineWhite = TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: AppColors.textOnPrimary,
height: 1.3,
);
//
static const TextStyle button = TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.textOnPrimary,
height: 1.2,
);
static const TextStyle buttonSmall = TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppColors.textOnPrimary,
height: 1.2,
);
//
static const TextStyle hint = TextStyle(
fontSize: 14,
fontWeight: FontWeight.normal,
color: AppColors.textHint,
height: 1.5,
);
//
static const TextStyle label = TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: AppColors.textSecondary,
height: 1.2,
);
//
static const TextStyle numberLarge = TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: AppColors.primary,
height: 1.2,
);
static const TextStyle numberMedium = TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: AppColors.primary,
height: 1.2,
);
}

View File

@ -0,0 +1,104 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'app_colors.dart';
import 'app_text_styles.dart';
import 'app_dimensions.dart';
class AppTheme {
AppTheme._();
static ThemeData get light {
return ThemeData(
useMaterial3: true,
brightness: Brightness.light,
primaryColor: AppColors.primary,
scaffoldBackgroundColor: AppColors.background,
colorScheme: const ColorScheme.light(
primary: AppColors.primary,
secondary: AppColors.primaryLight,
surface: AppColors.surface,
error: AppColors.error,
),
appBarTheme: const AppBarTheme(
elevation: 0,
centerTitle: true,
backgroundColor: AppColors.surface,
foregroundColor: AppColors.textPrimary,
systemOverlayStyle: SystemUiOverlayStyle.dark,
titleTextStyle: AppTextStyles.headline4,
),
cardTheme: CardThemeData(
color: AppColors.cardBackground,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppDimensions.cardRadius),
),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primary,
foregroundColor: AppColors.textOnPrimary,
minimumSize: const Size(double.infinity, AppDimensions.buttonHeightLg),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppDimensions.radiusMd),
),
textStyle: AppTextStyles.button,
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
foregroundColor: AppColors.primary,
minimumSize: const Size(double.infinity, AppDimensions.buttonHeightLg),
side: const BorderSide(color: AppColors.primary),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppDimensions.radiusMd),
),
textStyle: AppTextStyles.button.copyWith(color: AppColors.primary),
),
),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
foregroundColor: AppColors.primary,
textStyle: AppTextStyles.buttonSmall,
),
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: AppColors.surface,
contentPadding: const EdgeInsets.symmetric(
horizontal: AppDimensions.spacingMd,
vertical: AppDimensions.spacingSm,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppDimensions.radiusMd),
borderSide: const BorderSide(color: AppColors.border),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppDimensions.radiusMd),
borderSide: const BorderSide(color: AppColors.border),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppDimensions.radiusMd),
borderSide: const BorderSide(color: AppColors.primary, width: 2),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppDimensions.radiusMd),
borderSide: const BorderSide(color: AppColors.error),
),
hintStyle: AppTextStyles.hint,
),
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
backgroundColor: AppColors.surface,
selectedItemColor: AppColors.primary,
unselectedItemColor: AppColors.textHint,
type: BottomNavigationBarType.fixed,
elevation: 8,
),
dividerTheme: const DividerThemeData(
color: AppColors.divider,
thickness: 1,
space: 1,
),
);
}
}

View File

@ -0,0 +1,10 @@
import 'package:dartz/dartz.dart';
import '../errors/failures.dart';
abstract class UseCase<Type, Params> {
Future<Either<Failure, Type>> call(Params params);
}
class NoParams {
const NoParams();
}

View File

@ -0,0 +1,30 @@
import 'package:logger/logger.dart';
class AppLogger {
static final Logger _logger = Logger(
printer: PrettyPrinter(
methodCount: 2,
errorMethodCount: 8,
lineLength: 120,
colors: true,
printEmojis: true,
dateTimeFormat: DateTimeFormat.onlyTimeAndSinceStart,
),
);
static void d(String message, [dynamic error, StackTrace? stackTrace]) {
_logger.d(message, error: error, stackTrace: stackTrace);
}
static void i(String message, [dynamic error, StackTrace? stackTrace]) {
_logger.i(message, error: error, stackTrace: stackTrace);
}
static void w(String message, [dynamic error, StackTrace? stackTrace]) {
_logger.w(message, error: error, stackTrace: stackTrace);
}
static void e(String message, [dynamic error, StackTrace? stackTrace]) {
_logger.e(message, error: error, stackTrace: stackTrace);
}
}

View File

@ -0,0 +1,555 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../../../routes/route_paths.dart';
import '../../../../routes/app_router.dart';
/// -
/// 使
class BackupMnemonicPage extends ConsumerStatefulWidget {
///
final List<String> mnemonicWords;
/// KAVA
final String kavaAddress;
/// DST
final String dstAddress;
/// BSC
final String bscAddress;
///
final String serialNumber;
const BackupMnemonicPage({
super.key,
required this.mnemonicWords,
required this.kavaAddress,
required this.dstAddress,
required this.bscAddress,
required this.serialNumber,
});
@override
ConsumerState<BackupMnemonicPage> createState() => _BackupMnemonicPageState();
}
class _BackupMnemonicPageState extends ConsumerState<BackupMnemonicPage> {
//
bool _isHidden = false;
///
void _copyAllMnemonic() {
final mnemonicText = widget.mnemonicWords.join(' ');
Clipboard.setData(ClipboardData(text: mnemonicText));
_showCopySuccess('助记词已复制到剪贴板');
}
///
void _copyAddress(String address, String label) {
Clipboard.setData(ClipboardData(text: address));
_showCopySuccess('$label 已复制');
}
///
void _showCopySuccess(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: const Color(0xFFD4AF37),
duration: const Duration(seconds: 2),
),
);
}
/// /
void _toggleVisibility() {
setState(() {
_isHidden = !_isHidden;
});
}
///
void _confirmBackup() {
//
context.push(
RoutePaths.verifyMnemonic,
extra: VerifyMnemonicParams(
mnemonicWords: widget.mnemonicWords,
kavaAddress: widget.kavaAddress,
dstAddress: widget.dstAddress,
bscAddress: widget.bscAddress,
serialNumber: widget.serialNumber,
),
);
}
///
void _goBack() {
context.pop();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
width: double.infinity,
height: double.infinity,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFFFFF5E6),
Color(0xFFFFE4B5),
],
),
),
child: SafeArea(
child: Column(
children: [
//
_buildAppBar(),
//
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
children: [
//
_buildMnemonicCard(),
const SizedBox(height: 24),
//
_buildWarningCard(),
const SizedBox(height: 24),
//
_buildAddressCard(),
],
),
),
),
//
_buildBottomButtons(),
],
),
),
),
);
}
///
Widget _buildAppBar() {
return Container(
color: const Color(0xFFFFF5E6),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
child: Row(
children: [
//
GestureDetector(
onTap: _goBack,
child: Container(
width: 48,
height: 48,
alignment: Alignment.center,
child: const Icon(
Icons.arrow_back,
color: Color(0xFF5D4037),
size: 24,
),
),
),
//
const Expanded(
child: Text(
'备份你的助记词',
style: TextStyle(
fontSize: 18,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.25,
letterSpacing: -0.27,
color: Color(0xFF5D4037),
),
textAlign: TextAlign.center,
),
),
//
const SizedBox(width: 48, height: 48),
],
),
);
}
///
Widget _buildMnemonicCard() {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0x80FFFFFF),
borderRadius: BorderRadius.circular(12),
boxShadow: const [
BoxShadow(
color: Color(0x0D000000),
blurRadius: 2,
offset: Offset(0, 1),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'您的助记词',
style: TextStyle(
fontSize: 18,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.25,
letterSpacing: -0.27,
color: Color(0xFF5D4037),
),
),
Row(
children: [
// /
GestureDetector(
onTap: _toggleVisibility,
child: Icon(
_isHidden ? Icons.visibility : Icons.visibility_off,
color: const Color(0xFF5D4037),
size: 24,
),
),
const SizedBox(width: 16),
//
GestureDetector(
onTap: () {
// TODO:
},
child: const Icon(
Icons.download,
color: Color(0xFF5D4037),
size: 24,
),
),
],
),
],
),
const SizedBox(height: 16),
//
_buildMnemonicGrid(),
const SizedBox(height: 16),
//
_buildCopyAllButton(),
],
),
);
}
/// (43)
Widget _buildMnemonicGrid() {
return Column(
children: [
for (int row = 0; row < 4; row++)
Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: Row(
children: [
for (int col = 0; col < 3; col++)
Expanded(
child: _buildMnemonicWord(
row * 3 + col + 1,
widget.mnemonicWords[row * 3 + col],
),
),
],
),
),
],
);
}
///
Widget _buildMnemonicWord(int index, String word) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'$index.',
style: const TextStyle(
fontSize: 14,
fontFamily: 'Inter',
height: 1.43,
color: Color(0x805D4037),
),
),
const SizedBox(width: 8),
Text(
_isHidden ? '****' : word,
style: const TextStyle(
fontSize: 16,
fontFamily: 'Inter',
height: 1.5,
color: Color(0xFF5D4037),
),
),
],
);
}
///
Widget _buildCopyAllButton() {
return GestureDetector(
onTap: _copyAllMnemonic,
child: Container(
width: double.infinity,
height: 40,
decoration: BoxDecoration(
color: const Color(0x1A8B5A2B),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: const Color(0x338B5A2B),
width: 1,
),
),
child: const Center(
child: Text(
'复制全部',
style: TextStyle(
fontSize: 16,
fontFamily: 'Inter',
fontWeight: FontWeight.w500,
height: 1.5,
color: Color(0xFF8B5A2B),
),
),
),
),
);
}
///
Widget _buildWarningCard() {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: const Color(0x1A7F1D1D),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: const Color(0x337F1D1D),
width: 1,
),
),
child: const Text(
'请妥善保管您的助记词,丢失将无法恢复账号。',
style: TextStyle(
fontSize: 14,
fontFamily: 'Inter',
height: 1.5,
color: Color(0xFF991B1B),
),
textAlign: TextAlign.center,
),
);
}
///
Widget _buildAddressCard() {
return Container(
width: double.infinity,
decoration: BoxDecoration(
color: const Color(0x80FFFFFF),
borderRadius: BorderRadius.circular(12),
boxShadow: const [
BoxShadow(
color: Color(0x0D000000),
blurRadius: 2,
offset: Offset(0, 1),
),
],
),
child: Column(
children: [
_buildAddressItem(
icon: Icons.account_balance_wallet,
label: 'KAVA 地址',
address: widget.kavaAddress,
showBorder: true,
),
_buildAddressItem(
icon: Icons.account_balance_wallet,
label: 'DST 地址',
address: widget.dstAddress,
showBorder: true,
),
_buildAddressItem(
icon: Icons.account_balance_wallet,
label: 'BSC 地址',
address: widget.bscAddress,
showBorder: true,
),
_buildAddressItem(
icon: Icons.confirmation_number,
label: '序列号',
address: widget.serialNumber,
showBorder: false,
),
],
),
);
}
///
Widget _buildAddressItem({
required IconData icon,
required String label,
required String address,
required bool showBorder,
}) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
border: showBorder
? const Border(
bottom: BorderSide(
color: Color(0x1A5D4037),
width: 1,
),
)
: null,
),
child: Row(
children: [
//
Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: const Color(0x33D4AF37),
borderRadius: BorderRadius.circular(8),
),
child: Icon(
icon,
color: const Color(0xFFD4AF37),
size: 24,
),
),
const SizedBox(width: 16),
//
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: const TextStyle(
fontSize: 16,
fontFamily: 'Inter',
fontWeight: FontWeight.w500,
height: 1.5,
color: Color(0xFF5D4037),
),
),
Text(
_formatAddress(address),
style: const TextStyle(
fontSize: 14,
fontFamily: 'Inter',
height: 1.5,
color: Color(0x995D4037),
),
),
],
),
),
//
GestureDetector(
onTap: () => _copyAddress(address, label),
child: const Row(
children: [
Icon(
Icons.copy,
color: Color(0xCC8B5A2B),
size: 16,
),
SizedBox(width: 5),
Text(
'复制',
style: TextStyle(
fontSize: 14,
fontFamily: 'Inter',
fontWeight: FontWeight.w500,
height: 1.5,
color: Color(0xCC8B5A2B),
),
),
],
),
),
],
),
);
}
/// (64)
String _formatAddress(String address) {
if (address.length <= 10) return address;
return '${address.substring(0, 6)}...${address.substring(address.length - 4)}';
}
///
Widget _buildBottomButtons() {
return Container(
color: const Color(0xFFFFE4B5),
padding: const EdgeInsets.all(16),
child: Column(
children: [
//
GestureDetector(
onTap: _confirmBackup,
child: Container(
width: double.infinity,
height: 48,
decoration: BoxDecoration(
color: const Color(0xFFD4AF37),
borderRadius: BorderRadius.circular(12),
),
child: const Center(
child: Text(
'我已备份助记词',
style: TextStyle(
fontSize: 16,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.5,
color: Colors.white,
),
),
),
),
),
const SizedBox(height: 16),
//
GestureDetector(
onTap: _goBack,
child: const Text(
'返回上一步',
style: TextStyle(
fontSize: 14,
fontFamily: 'Inter',
fontWeight: FontWeight.w500,
height: 1.5,
color: Color(0xCC8B5A2B),
),
),
),
],
),
);
}
}

View File

@ -0,0 +1,352 @@
import 'package:flutter/material.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../../../routes/route_paths.dart';
import '../../../../routes/app_router.dart';
/// -
///
class OnboardingPage extends ConsumerStatefulWidget {
const OnboardingPage({super.key});
@override
ConsumerState<OnboardingPage> createState() => _OnboardingPageState();
}
class _OnboardingPageState extends ConsumerState<OnboardingPage> {
//
bool _isAgreed = false;
//
bool _isCreating = false;
///
Future<void> _createWallet() async {
if (!_isAgreed) {
_showAgreementTip();
return;
}
setState(() => _isCreating = true);
try {
// TODO: (bip39)
//
await Future.delayed(const Duration(seconds: 2));
// (12)
final mockMnemonicWords = [
'apple', 'banana', 'cherry', 'date',
'elder', 'fig', 'grape', 'honey',
'kiwi', 'lemon', 'mango', 'nectar',
];
//
const mockKavaAddress = '0x1234567890abcdef1234567890abcdef12345678';
const mockDstAddress = '0xABCDEF1234567890abcdef1234567890ABCDEFGH';
const mockBscAddress = '0x9876543210zyxwvu9876543210zyxwvu98765432';
const mockSerialNumber = '12345678';
if (!mounted) return;
//
context.push(
RoutePaths.backupMnemonic,
extra: BackupMnemonicParams(
mnemonicWords: mockMnemonicWords,
kavaAddress: mockKavaAddress,
dstAddress: mockDstAddress,
bscAddress: mockBscAddress,
serialNumber: mockSerialNumber,
),
);
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('创建钱包失败: $e')),
);
} finally {
if (mounted) {
setState(() => _isCreating = false);
}
}
}
///
void _showAgreementTip() {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('请先阅读并同意用户协议和隐私政策'),
backgroundColor: Color(0xFFD4AF37),
),
);
}
///
void _showUserAgreement() {
// TODO:
debugPrint('显示用户协议');
}
///
void _showPrivacyPolicy() {
// TODO:
debugPrint('显示隐私政策');
}
///
void _importMnemonic() {
// TODO:
debugPrint('导入助记词');
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
width: double.infinity,
height: double.infinity,
// -
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFFFFF5E6), //
Color(0xFFFFE4B5), //
],
),
),
child: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 15),
child: Column(
children: [
// Logo
_buildHeader(),
const SizedBox(height: 48),
//
_buildDescription(),
const Spacer(),
//
_buildActionSection(),
const SizedBox(height: 9),
],
),
),
),
),
);
}
/// Logo
Widget _buildHeader() {
return Column(
children: [
const SizedBox(height: 32),
// Logo
Container(
width: 100,
height: 80,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: const Center(
child: Text(
'ap',
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.w600,
color: Color(0xFF2E7D32), // 绿
),
),
),
),
const SizedBox(height: 24),
//
const Text(
'榴莲女皇',
style: TextStyle(
fontSize: 32,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.25,
color: Color(0xFF5D4037), //
),
),
],
);
}
///
Widget _buildDescription() {
return Column(
children: [
//
const Text(
'创建账户',
style: TextStyle(
fontSize: 22,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.25,
letterSpacing: -0.33,
color: Color(0xFF5D4037),
),
),
const SizedBox(height: 16),
//
const Padding(
padding: EdgeInsets.symmetric(horizontal: 20),
child: Text(
'我们将为你创建三个链的钱包地址KAVA / DST / BSC同时生成唯一序列号用于推荐与权益。',
style: TextStyle(
fontSize: 16,
fontFamily: 'Inter',
height: 1.5,
color: Color(0xFF5D4037),
),
textAlign: TextAlign.center,
),
),
],
);
}
///
Widget _buildActionSection() {
return Column(
children: [
//
_buildCreateButton(),
const SizedBox(height: 20),
//
_buildAgreementCheckbox(),
const SizedBox(height: 24),
//
_buildImportLink(),
],
);
}
///
Widget _buildCreateButton() {
return GestureDetector(
onTap: _isCreating ? null : _createWallet,
child: Opacity(
opacity: _isAgreed ? 1.0 : 0.5,
child: Container(
width: double.infinity,
height: 48,
decoration: BoxDecoration(
color: const Color(0xFFD4AF37), //
borderRadius: BorderRadius.circular(8),
),
child: Center(
child: _isCreating
? const SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
)
: const Text(
'生成钱包(创建账户)',
style: TextStyle(
fontSize: 16,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.5,
letterSpacing: 0.24,
color: Colors.white,
),
),
),
),
),
);
}
///
Widget _buildAgreementCheckbox() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//
SizedBox(
width: 20,
height: 20,
child: Checkbox(
value: _isAgreed,
onChanged: (value) {
setState(() {
_isAgreed = value ?? false;
});
},
activeColor: const Color(0xFFD4AF37),
side: const BorderSide(color: Color(0xFF5D4037)),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
),
),
),
const SizedBox(width: 8),
//
Flexible(
child: RichText(
text: TextSpan(
style: const TextStyle(
fontSize: 14,
fontFamily: 'Inter',
fontWeight: FontWeight.w500,
height: 1.43,
color: Color(0xFF5D4037),
),
children: [
const TextSpan(text: '我已阅读并同意 '),
TextSpan(
text: '《用户协议》',
style: const TextStyle(color: Color(0xFFD4AF37)),
recognizer: TapGestureRecognizer()..onTap = _showUserAgreement,
),
TextSpan(
text: '《隐私政策》',
style: const TextStyle(color: Color(0xFFD4AF37)),
recognizer: TapGestureRecognizer()..onTap = _showPrivacyPolicy,
),
],
),
),
),
],
);
}
///
Widget _buildImportLink() {
return GestureDetector(
onTap: _importMnemonic,
child: const Text(
'已有账号? 导入助记词',
style: TextStyle(
fontSize: 14,
fontFamily: 'Inter',
fontWeight: FontWeight.w500,
height: 1.43,
color: Color(0xFFD4AF37),
),
),
);
}
}

View File

@ -0,0 +1,144 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../../../core/constants/app_constants.dart';
import '../../../../routes/route_paths.dart';
import '../providers/auth_provider.dart';
/// -
/// Logo
class SplashPage extends ConsumerStatefulWidget {
const SplashPage({super.key});
@override
ConsumerState<SplashPage> createState() => _SplashPageState();
}
class _SplashPageState extends ConsumerState<SplashPage> {
@override
void initState() {
super.initState();
_initializeApp();
}
///
Future<void> _initializeApp() async {
//
await Future.delayed(AppConstants.splashDuration);
//
await ref.read(authProvider.notifier).checkAuthStatus();
if (!mounted) return;
final authState = ref.read(authProvider);
if (authState.isWalletCreated) {
//
context.go(RoutePaths.ranking);
} else {
//
context.go(RoutePaths.onboarding);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
width: double.infinity,
height: double.infinity,
// -
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFFFFF5E6), //
Color(0xFFFFE4B5), //
],
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Logo
_buildLogo(),
const SizedBox(height: 24),
//
_buildAppTitle(),
],
),
),
);
}
/// Logo
///
Widget _buildLogo() {
return Container(
width: 128,
height: 152,
decoration: BoxDecoration(
color: const Color(0xFF2D2A26), //
borderRadius: BorderRadius.circular(8),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
//
Container(
width: 64,
height: 48,
decoration: BoxDecoration(
color: const Color(0xFFD4A84B), //
borderRadius: BorderRadius.circular(8),
),
child: const Icon(
Icons.workspace_premium,
size: 32,
color: Color(0xFF2D2A26),
),
),
const SizedBox(height: 16),
// DURIAN
const Text(
'DURIAN',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
letterSpacing: 2,
color: Color(0xFFD4A84B),
),
),
const SizedBox(height: 2),
// QUEEN
const Text(
'QUEEN',
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w400,
letterSpacing: 1.5,
color: Color(0xFFD4A84B),
),
),
],
),
);
}
///
Widget _buildAppTitle() {
return const Text(
'榴莲女皇',
style: TextStyle(
fontSize: 32,
fontFamily: 'Noto Sans SC',
fontWeight: FontWeight.w700,
height: 1.25,
letterSpacing: 1.6,
color: Color(0xFF4A3F35), //
),
);
}
}

View File

@ -0,0 +1,477 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../../../routes/route_paths.dart';
import '../../../../routes/app_router.dart';
import '../providers/auth_provider.dart';
/// -
///
class VerifyMnemonicPage extends ConsumerStatefulWidget {
///
final List<String> mnemonicWords;
/// KAVA
final String kavaAddress;
/// DST
final String dstAddress;
/// BSC
final String bscAddress;
///
final String serialNumber;
const VerifyMnemonicPage({
super.key,
required this.mnemonicWords,
required this.kavaAddress,
required this.dstAddress,
required this.bscAddress,
required this.serialNumber,
});
@override
ConsumerState<VerifyMnemonicPage> createState() => _VerifyMnemonicPageState();
}
class _VerifyMnemonicPageState extends ConsumerState<VerifyMnemonicPage> {
// "我已备份助记词并理解风险"
bool _isChecked = false;
// (1-12)
late int _verifyWordIndex;
//
late List<String> _wordOptions;
//
String? _selectedWord;
//
late String _correctWord;
//
bool _isCreating = false;
@override
void initState() {
super.initState();
_initializeVerification();
}
///
void _initializeVerification() {
final random = Random();
// (1-12)
_verifyWordIndex = random.nextInt(12) + 1;
//
_correctWord = widget.mnemonicWords[_verifyWordIndex - 1];
//
_wordOptions = _generateOptions();
}
///
List<String> _generateOptions() {
final random = Random();
final options = <String>[_correctWord];
// BIP39
final decoyWords = [
'apple', 'banana', 'cherry', 'date', 'elder', 'fig', 'grape', 'honey',
'kiwi', 'lemon', 'mango', 'nectar', 'orange', 'peach', 'queen', 'rose',
'sugar', 'tiger', 'uncle', 'virus', 'water', 'yellow', 'zebra', 'zoo',
'jungle', 'victory', 'wizard', 'anchor', 'brain', 'cloud',
];
//
final availableDecoys = decoyWords
.where((word) => !widget.mnemonicWords.contains(word) && word != _correctWord)
.toList();
// 5
availableDecoys.shuffle(random);
options.addAll(availableDecoys.take(5));
//
options.shuffle(random);
return options;
}
///
void _selectWord(String word) {
setState(() {
_selectedWord = word;
});
}
///
bool get _canSubmit {
// 1
// 2
return _isChecked || _selectedWord == _correctWord;
}
///
Future<void> _confirmAndCreate() async {
if (!_canSubmit) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('请勾选确认或选择正确的助记词单词'),
backgroundColor: Color(0xFF8B5A2B),
),
);
return;
}
//
if (_selectedWord != null && _selectedWord != _correctWord) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('选择的单词不正确,请重试'),
backgroundColor: Color(0xFF991B1B),
),
);
return;
}
setState(() => _isCreating = true);
try {
//
await ref.read(authProvider.notifier).saveWallet(
widget.kavaAddress,
widget.mnemonicWords.join(' '),
);
if (!mounted) return;
//
context.go(
RoutePaths.walletCreated,
extra: WalletCreatedParams(
kavaAddress: widget.kavaAddress,
dstAddress: widget.dstAddress,
bscAddress: widget.bscAddress,
serialNumber: widget.serialNumber,
),
);
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('创建账号失败: $e')),
);
} finally {
if (mounted) {
setState(() => _isCreating = false);
}
}
}
///
void _goBack() {
context.pop();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: Column(
children: [
//
_buildAppBar(),
//
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 16),
//
_buildDescription(),
const SizedBox(height: 24),
//
_buildCheckbox(),
const SizedBox(height: 24),
// 线
_buildDivider(),
const SizedBox(height: 24),
//
_buildWordSelection(),
],
),
),
),
//
_buildBottomButtons(),
],
),
),
);
}
///
Widget _buildAppBar() {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
child: Row(
children: [
//
GestureDetector(
onTap: _goBack,
child: Container(
width: 40,
height: 40,
alignment: Alignment.center,
child: const Icon(
Icons.arrow_back,
color: Color(0xFF5D4037),
size: 24,
),
),
),
//
const Expanded(
child: Text(
'确认备份',
style: TextStyle(
fontSize: 18,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.56,
letterSpacing: -0.45,
color: Color(0xFF5D4037),
),
textAlign: TextAlign.center,
),
),
//
const SizedBox(width: 40),
],
),
);
}
///
Widget _buildDescription() {
return const Text(
'为保证助记词已被您正确保存,请通过以下方式确认:',
style: TextStyle(
fontSize: 16,
fontFamily: 'Inter',
height: 1.5,
color: Color(0xFF5D4037),
),
);
}
///
Widget _buildCheckbox() {
return GestureDetector(
onTap: () {
setState(() {
_isChecked = !_isChecked;
});
},
child: Row(
children: [
Container(
width: 24,
height: 24,
decoration: BoxDecoration(
color: _isChecked ? const Color(0xFFD4AF37) : Colors.transparent,
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: _isChecked ? const Color(0xFFD4AF37) : const Color(0xFF8B5A2B),
width: 2,
),
),
child: _isChecked
? const Icon(
Icons.check,
color: Colors.white,
size: 16,
)
: null,
),
const SizedBox(width: 12),
const Text(
'我已备份助记词并理解风险',
style: TextStyle(
fontSize: 16,
fontFamily: 'Inter',
fontWeight: FontWeight.w500,
height: 1.5,
color: Color(0xFF5D4037),
),
),
],
),
);
}
/// 线
Widget _buildDivider() {
return Row(
children: [
Expanded(
child: Container(
height: 1,
color: const Color(0x338B5A2B),
),
),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 12),
child: Text(
'',
style: TextStyle(
fontSize: 14,
fontFamily: 'Inter',
height: 1.43,
color: Color(0x998B5A2B),
),
),
),
Expanded(
child: Container(
height: 1,
color: const Color(0x338B5A2B),
),
),
],
);
}
///
Widget _buildWordSelection() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'请选择你助记词中的第 $_verifyWordIndex 个单词',
style: const TextStyle(
fontSize: 16,
fontFamily: 'Inter',
height: 1.5,
color: Color(0xFF5D4037),
),
),
const SizedBox(height: 16),
//
Wrap(
spacing: 12,
runSpacing: 12,
children: _wordOptions.map((word) => _buildWordOption(word)).toList(),
),
],
);
}
///
Widget _buildWordOption(String word) {
final isSelected = _selectedWord == word;
final isCorrect = word == _correctWord;
return GestureDetector(
onTap: () => _selectWord(word),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
decoration: BoxDecoration(
color: isSelected
? const Color(0x33D4AF37)
: const Color(0x1A8B5A2B),
borderRadius: BorderRadius.circular(8),
border: isSelected
? Border.all(color: const Color(0xFFD4AF37), width: 1)
: null,
boxShadow: isSelected
? [
const BoxShadow(
color: Color(0xCCD4AF37),
blurRadius: 0,
spreadRadius: 1,
),
]
: null,
),
child: Text(
word,
style: TextStyle(
fontSize: 14,
fontFamily: 'Inter',
fontWeight: FontWeight.w500,
height: 1.5,
color: isSelected ? const Color(0xFFD4AF37) : const Color(0xFF5D4037),
),
),
),
);
}
///
Widget _buildBottomButtons() {
return Container(
color: const Color(0xFFFFE4B5),
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
child: Column(
children: [
//
GestureDetector(
onTap: _isCreating ? null : _confirmAndCreate,
child: Container(
width: double.infinity,
height: 48,
decoration: BoxDecoration(
color: _canSubmit
? const Color(0xFFD4AF37)
: const Color(0x668B5A2B),
borderRadius: BorderRadius.circular(12),
),
child: Center(
child: _isCreating
? const SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
)
: Text(
'确认并创建账号',
style: TextStyle(
fontSize: 16,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.5,
color: _canSubmit
? Colors.white
: const Color(0xB3FFFFFF),
),
),
),
),
),
const SizedBox(height: 8),
//
GestureDetector(
onTap: _goBack,
child: Container(
width: double.infinity,
height: 48,
alignment: Alignment.center,
child: const Text(
'返回查看助记词',
style: TextStyle(
fontSize: 16,
fontFamily: 'Inter',
fontWeight: FontWeight.w500,
height: 1.5,
color: Color(0xFFD4AF37),
),
),
),
),
],
),
);
}
}

View File

@ -0,0 +1,360 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../../../routes/route_paths.dart';
/// -
///
class WalletCreatedPage extends ConsumerWidget {
/// KAVA
final String kavaAddress;
/// DST
final String dstAddress;
/// BSC
final String bscAddress;
///
final String serialNumber;
const WalletCreatedPage({
super.key,
required this.kavaAddress,
required this.dstAddress,
required this.bscAddress,
required this.serialNumber,
});
///
void _copyToClipboard(BuildContext context, String text, String label) {
Clipboard.setData(ClipboardData(text: text));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('$label 已复制'),
backgroundColor: const Color(0xFFD4AF37),
duration: const Duration(seconds: 2),
),
);
}
///
void _enterWallet(BuildContext context) {
context.go(RoutePaths.ranking);
}
///
void _shareToFriends(BuildContext context) {
// TODO:
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('分享功能即将推出'),
backgroundColor: Color(0xFF8B5A2B),
duration: Duration(seconds: 2),
),
);
}
/// (64)
String _formatAddress(String address) {
if (address.length <= 10) return address;
return '${address.substring(0, 6)}...${address.substring(address.length - 4)}';
}
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
backgroundColor: Colors.white,
body: Container(
width: double.infinity,
height: double.infinity,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFFFFF5E6),
Color(0xFFFFE4B5),
],
),
),
child: SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
child: Column(
children: [
const SizedBox(height: 32),
//
_buildSuccessIcon(),
const SizedBox(height: 24),
//
_buildTitle(),
const SizedBox(height: 32),
//
_buildWalletInfoCard(context),
const SizedBox(height: 32),
//
_buildActionButtons(context),
const SizedBox(height: 16),
//
_buildWarningText(),
],
),
),
),
),
);
}
///
Widget _buildSuccessIcon() {
return Container(
width: 120,
height: 120,
decoration: const BoxDecoration(
color: Color(0x33D4AF37),
shape: BoxShape.circle,
),
child: const Center(
child: Icon(
Icons.check_circle_outline,
size: 64,
color: Color(0xFFD4AF37),
),
),
);
}
///
Widget _buildTitle() {
return Column(
children: const [
Text(
'账号创建成功',
style: TextStyle(
fontSize: 30,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.25,
letterSpacing: -0.75,
color: Color(0xFF5D4037),
),
),
SizedBox(height: 8),
Text(
'已为您创建钱包与序列号',
style: TextStyle(
fontSize: 16,
fontFamily: 'Inter',
height: 1.5,
color: Color(0xFF8B5A2B),
),
),
],
);
}
///
Widget _buildWalletInfoCard(BuildContext context) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0x4DFFFFFF),
borderRadius: BorderRadius.circular(12),
boxShadow: const [
BoxShadow(
color: Color(0x33D4AF37),
blurRadius: 0,
spreadRadius: 1,
offset: Offset(0, 0),
),
],
),
child: Column(
children: [
//
_buildInfoItem(
context: context,
icon: Icons.key,
label: '序列号',
value: serialNumber,
showDivider: true,
),
// KAVA
_buildInfoItem(
context: context,
icon: Icons.account_balance_wallet,
label: 'KAVA',
value: kavaAddress,
showDivider: true,
),
// DST
_buildInfoItem(
context: context,
icon: Icons.account_balance_wallet,
label: 'DST',
value: dstAddress,
showDivider: true,
),
// BSC
_buildInfoItem(
context: context,
icon: Icons.account_balance_wallet,
label: 'BSC',
value: bscAddress,
showDivider: false,
),
],
),
);
}
///
Widget _buildInfoItem({
required BuildContext context,
required IconData icon,
required String label,
required String value,
required bool showDivider,
}) {
return Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Row(
children: [
//
Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: const Color(0x33D4AF37),
borderRadius: BorderRadius.circular(8),
),
child: Icon(
icon,
color: const Color(0xFFD4AF37),
size: 24,
),
),
const SizedBox(width: 16),
//
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'$label:',
style: const TextStyle(
fontSize: 14,
fontFamily: 'Inter',
height: 1.5,
color: Color(0x998B5A2B),
),
),
Text(
label == '序列号' ? value : _formatAddress(value),
style: const TextStyle(
fontSize: 16,
fontFamily: 'Inter',
height: 1.5,
color: Color(0xFF5D4037),
),
),
],
),
),
//
GestureDetector(
onTap: () => _copyToClipboard(context, value, label),
child: Container(
padding: const EdgeInsets.all(8),
child: const Icon(
Icons.copy,
color: Color(0xCC8B5A2B),
size: 20,
),
),
),
],
),
),
if (showDivider)
Container(
height: 1,
color: const Color(0x1A8B5A2B),
),
],
);
}
///
Widget _buildActionButtons(BuildContext context) {
return Column(
children: [
//
GestureDetector(
onTap: () => _enterWallet(context),
child: Container(
width: double.infinity,
height: 53,
decoration: BoxDecoration(
color: const Color(0xFFD4AF37),
borderRadius: BorderRadius.circular(12),
),
child: const Center(
child: Text(
'进入我的钱包',
style: TextStyle(
fontSize: 16,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.5,
color: Colors.white,
),
),
),
),
),
const SizedBox(height: 16),
//
GestureDetector(
onTap: () => _shareToFriends(context),
child: Container(
width: double.infinity,
height: 53,
decoration: BoxDecoration(
color: const Color(0xFF8B5A2B),
borderRadius: BorderRadius.circular(12),
),
child: const Center(
child: Text(
'分享给好友',
style: TextStyle(
fontSize: 16,
fontFamily: 'Inter',
fontWeight: FontWeight.w500,
height: 1.5,
color: Colors.white,
),
),
),
),
),
],
);
}
///
Widget _buildWarningText() {
return const Text(
'助记词仅显示一次,请妥善保管。',
style: TextStyle(
fontSize: 14,
fontFamily: 'Inter',
height: 1.5,
color: Color(0xCC8B5A2B),
),
textAlign: TextAlign.center,
);
}
}

View File

@ -0,0 +1,93 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../core/storage/secure_storage.dart';
import '../../../../core/storage/storage_keys.dart';
import '../../../../core/di/injection_container.dart';
enum AuthStatus {
initial,
checking,
authenticated,
unauthenticated,
}
class AuthState {
final AuthStatus status;
final String? walletAddress;
final bool isWalletCreated;
final String? errorMessage;
const AuthState({
this.status = AuthStatus.initial,
this.walletAddress,
this.isWalletCreated = false,
this.errorMessage,
});
AuthState copyWith({
AuthStatus? status,
String? walletAddress,
bool? isWalletCreated,
String? errorMessage,
}) {
return AuthState(
status: status ?? this.status,
walletAddress: walletAddress ?? this.walletAddress,
isWalletCreated: isWalletCreated ?? this.isWalletCreated,
errorMessage: errorMessage,
);
}
}
class AuthNotifier extends StateNotifier<AuthState> {
final SecureStorage _secureStorage;
AuthNotifier(this._secureStorage) : super(const AuthState());
Future<void> checkAuthStatus() async {
state = state.copyWith(status: AuthStatus.checking);
try {
final walletAddress = await _secureStorage.read(key: StorageKeys.walletAddress);
final isWalletCreated = walletAddress != null && walletAddress.isNotEmpty;
if (isWalletCreated) {
state = state.copyWith(
status: AuthStatus.authenticated,
walletAddress: walletAddress,
isWalletCreated: true,
);
} else {
state = state.copyWith(
status: AuthStatus.unauthenticated,
isWalletCreated: false,
);
}
} catch (e) {
state = state.copyWith(
status: AuthStatus.unauthenticated,
errorMessage: e.toString(),
);
}
}
Future<void> saveWallet(String walletAddress, String privateKey) async {
await _secureStorage.write(key: StorageKeys.walletAddress, value: walletAddress);
await _secureStorage.write(key: StorageKeys.privateKey, value: privateKey);
state = state.copyWith(
status: AuthStatus.authenticated,
walletAddress: walletAddress,
isWalletCreated: true,
);
}
Future<void> logout() async {
await _secureStorage.deleteAll();
state = const AuthState(status: AuthStatus.unauthenticated);
}
}
final authProvider = StateNotifierProvider<AuthNotifier, AuthState>((ref) {
final secureStorage = ref.watch(secureStorageProvider);
return AuthNotifier(secureStorage);
});

View File

@ -0,0 +1,50 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../../../core/theme/app_colors.dart';
import '../../../../core/theme/app_dimensions.dart';
import '../../../../routes/route_paths.dart';
import '../widgets/bottom_nav_bar.dart';
class HomeShellPage extends ConsumerWidget {
final Widget child;
const HomeShellPage({super.key, required this.child});
int _getCurrentIndex(BuildContext context) {
final location = GoRouterState.of(context).uri.path;
if (location.startsWith(RoutePaths.ranking)) return 0;
if (location.startsWith(RoutePaths.mining)) return 1;
if (location.startsWith(RoutePaths.trading)) return 2;
if (location.startsWith(RoutePaths.profile)) return 3;
return 0;
}
void _onTabTapped(BuildContext context, int index) {
switch (index) {
case 0:
context.go(RoutePaths.ranking);
break;
case 1:
context.go(RoutePaths.mining);
break;
case 2:
context.go(RoutePaths.trading);
break;
case 3:
context.go(RoutePaths.profile);
break;
}
}
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
body: child,
bottomNavigationBar: BottomNavBar(
currentIndex: _getCurrentIndex(context),
onTap: (index) => _onTabTapped(context, index),
),
);
}
}

View File

@ -0,0 +1,20 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
enum NavTab {
ranking,
mining,
trading,
profile,
}
class NavigationNotifier extends StateNotifier<NavTab> {
NavigationNotifier() : super(NavTab.ranking);
void setTab(NavTab tab) {
state = tab;
}
}
final navigationProvider = StateNotifierProvider<NavigationNotifier, NavTab>((ref) {
return NavigationNotifier();
});

View File

@ -0,0 +1,99 @@
import 'package:flutter/material.dart';
///
/// Tab
class BottomNavBar extends StatelessWidget {
final int currentIndex;
final Function(int) onTap;
const BottomNavBar({
super.key,
required this.currentIndex,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return Container(
height: 65,
decoration: const BoxDecoration(
color: Color(0xFFFFF5E6),
border: Border(
top: BorderSide(
width: 1,
color: Color(0x338B5A2B),
),
),
),
child: SafeArea(
top: false,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildNavItem(
index: 0,
icon: Icons.leaderboard_outlined,
activeIcon: Icons.leaderboard,
label: '龙虎榜',
),
_buildNavItem(
index: 1,
icon: Icons.memory_outlined,
activeIcon: Icons.memory,
label: '矿机',
),
_buildNavItem(
index: 2,
icon: Icons.swap_horiz_outlined,
activeIcon: Icons.swap_horiz,
label: '交易',
),
_buildNavItem(
index: 3,
icon: Icons.person_outline,
activeIcon: Icons.person,
label: '',
),
],
),
),
);
}
///
Widget _buildNavItem({
required int index,
required IconData icon,
required IconData activeIcon,
required String label,
}) {
final isSelected = currentIndex == index;
return Expanded(
child: GestureDetector(
onTap: () => onTap(index),
behavior: HitTestBehavior.opaque,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
isSelected ? activeIcon : icon,
size: 24,
color: isSelected ? const Color(0xFFD4AF37) : const Color(0xFF8B5A2B),
),
const SizedBox(height: 2),
Text(
label,
style: TextStyle(
fontSize: 12,
fontFamily: 'Inter',
fontWeight: FontWeight.w500,
height: 1.33,
color: isSelected ? const Color(0xFFD4AF37) : const Color(0xFF8B5A2B),
),
),
],
),
),
);
}
}

View File

@ -0,0 +1,347 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
///
enum MiningStatus {
pending, //
mining, //
paused, //
}
/// -
///
class MiningPage extends ConsumerStatefulWidget {
const MiningPage({super.key});
@override
ConsumerState<MiningPage> createState() => _MiningPageState();
}
class _MiningPageState extends ConsumerState<MiningPage> {
//
MiningStatus _miningStatus = MiningStatus.pending;
//
final String _serialNumber = '12345';
final String _community = '星空社区';
final String _province = '广东';
final String _city = '深圳';
///
void _showHelpInfo() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('矿机说明'),
content: const Text('矿机是您参与挖矿的核心工具。\n\n'
'开启挖矿后,您将开始获得收益。\n\n'
'收益与您的算力和团队规模相关。'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('知道了'),
),
],
),
);
}
///
void _toggleMining() {
setState(() {
if (_miningStatus == MiningStatus.pending) {
_miningStatus = MiningStatus.mining;
} else if (_miningStatus == MiningStatus.mining) {
_miningStatus = MiningStatus.paused;
} else {
_miningStatus = MiningStatus.mining;
}
});
}
///
String _getStatusText() {
switch (_miningStatus) {
case MiningStatus.pending:
return '挖矿待开启';
case MiningStatus.mining:
return '挖矿中';
case MiningStatus.paused:
return '挖矿已暂停';
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Container(
width: double.infinity,
height: double.infinity,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFFFFF5E6),
Color(0xFFFFE4B5),
],
),
),
child: SafeArea(
child: Column(
children: [
//
_buildAppBar(),
//
_buildUserInfo(),
const SizedBox(height: 24),
//
Expanded(
child: _buildMiningStatusArea(),
),
],
),
),
),
);
}
///
Widget _buildAppBar() {
return Container(
height: 56,
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: [
//
const SizedBox(width: 48),
//
const Expanded(
child: Text(
'矿机',
style: TextStyle(
fontSize: 18,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.25,
letterSpacing: -0.27,
color: Color(0xFF5D4037),
),
textAlign: TextAlign.center,
),
),
//
GestureDetector(
onTap: _showHelpInfo,
child: Container(
width: 48,
height: 48,
alignment: Alignment.center,
child: Container(
width: 24,
height: 24,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: const Color(0xFF8B5A2B),
width: 2,
),
),
child: const Center(
child: Text(
'i',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Color(0xFF8B5A2B),
),
),
),
),
),
),
],
),
);
}
///
Widget _buildUserInfo() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: [
//
Container(
width: 80,
height: 80,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(40),
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Color(0xFFFF6B6B),
Color(0xFFFFE66D),
Color(0xFF4ECDC4),
],
),
),
child: const Center(
child: Icon(
Icons.person,
size: 40,
color: Colors.white,
),
),
),
const SizedBox(width: 16),
//
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'序列号$_serialNumber',
style: const TextStyle(
fontSize: 20,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.25,
letterSpacing: -0.3,
color: Color(0xFF5D4037),
),
),
const SizedBox(height: 4),
Text(
'社区: $_community / 省市: $_province · $_city',
style: const TextStyle(
fontSize: 14,
fontFamily: 'Inter',
height: 1.5,
color: Color(0xFF8B5A2B),
),
),
],
),
),
],
),
);
}
///
Widget _buildMiningStatusArea() {
return Padding(
padding: const EdgeInsets.all(16),
child: Container(
width: double.infinity,
decoration: BoxDecoration(
color: const Color(0x66FFFFFF),
borderRadius: BorderRadius.circular(12),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
//
GestureDetector(
onTap: _toggleMining,
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: _miningStatus == MiningStatus.mining
? const Color(0xFFD4AF37)
: Colors.transparent,
border: Border.all(
color: _miningStatus == MiningStatus.mining
? const Color(0xFFD4AF37)
: const Color(0xFFD4AF37).withOpacity(0.5),
width: 3,
),
),
child: Icon(
Icons.power_settings_new,
size: 48,
color: _miningStatus == MiningStatus.mining
? Colors.white
: const Color(0xFFD4AF37).withOpacity(0.5),
),
),
),
const SizedBox(height: 16),
//
Text(
_getStatusText(),
style: const TextStyle(
fontSize: 18,
fontFamily: 'Inter',
fontWeight: FontWeight.w500,
height: 1.56,
color: Color(0xFF8B5A2B),
),
),
//
if (_miningStatus == MiningStatus.mining) ...[
const SizedBox(height: 24),
_buildMiningStats(),
],
],
),
),
);
}
///
Widget _buildMiningStats() {
return Container(
padding: const EdgeInsets.all(16),
margin: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
color: const Color(0x33D4AF37),
borderRadius: BorderRadius.circular(8),
),
child: Column(
children: [
_buildStatRow('个人算力', '100 H/s'),
const SizedBox(height: 8),
_buildStatRow('团队算力', '1,000 H/s'),
const SizedBox(height: 8),
_buildStatRow('今日收益', '0.00 DST'),
const SizedBox(height: 8),
_buildStatRow('累计收益', '0.00 DST'),
],
),
);
}
///
Widget _buildStatRow(String label, String value) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
style: const TextStyle(
fontSize: 14,
fontFamily: 'Inter',
height: 1.5,
color: Color(0xFF8B5A2B),
),
),
Text(
value,
style: const TextStyle(
fontSize: 14,
fontFamily: 'Inter',
fontWeight: FontWeight.w600,
height: 1.5,
color: Color(0xFF5D4037),
),
),
],
);
}
}

View File

@ -0,0 +1,524 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
/// -
///
class EditProfilePage extends ConsumerStatefulWidget {
const EditProfilePage({super.key});
@override
ConsumerState<EditProfilePage> createState() => _EditProfilePageState();
}
class _EditProfilePageState extends ConsumerState<EditProfilePage> {
//
final TextEditingController _nicknameController = TextEditingController();
//
String? _avatarPath;
//
bool _isSaving = false;
@override
void initState() {
super.initState();
//
_nicknameController.text = '社区园丁';
}
@override
void dispose() {
_nicknameController.dispose();
super.dispose();
}
///
void _goBack() {
context.pop();
}
///
void _showAvatarPicker() {
showModalBottomSheet(
context: context,
backgroundColor: Colors.transparent,
builder: (context) => _buildAvatarPickerSheet(),
);
}
///
void _takePhoto() {
Navigator.pop(context);
// TODO:
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('拍照功能开发中'),
backgroundColor: Color(0xFFD4AF37),
),
);
}
///
void _pickFromGallery() {
Navigator.pop(context);
// TODO:
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('相册选择功能开发中'),
backgroundColor: Color(0xFFD4AF37),
),
);
}
///
void _deleteAvatar() {
Navigator.pop(context);
setState(() {
_avatarPath = null;
});
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('头像已删除'),
backgroundColor: Color(0xFFD4AF37),
),
);
}
///
void _cancelPicker() {
Navigator.pop(context);
}
///
Future<void> _saveProfile() async {
if (_nicknameController.text.trim().isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('昵称不能为空'),
backgroundColor: Colors.red,
),
);
return;
}
setState(() {
_isSaving = true;
});
try {
// TODO: API保存资料
await Future.delayed(const Duration(seconds: 1));
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('保存成功'),
backgroundColor: Color(0xFFD4AF37),
),
);
context.pop();
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('保存失败: $e'),
backgroundColor: Colors.red,
),
);
} finally {
if (mounted) {
setState(() {
_isSaving = false;
});
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Container(
width: double.infinity,
height: double.infinity,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFFFFF5E6),
Color(0xFFFFE4B5),
],
),
),
child: SafeArea(
child: Column(
children: [
//
_buildAppBar(),
//
Expanded(
child: SingleChildScrollView(
child: Column(
children: [
const SizedBox(height: 40),
//
_buildAvatarSection(),
const SizedBox(height: 40),
//
_buildNicknameSection(),
],
),
),
),
//
_buildSaveButton(),
],
),
),
),
);
}
///
Widget _buildAppBar() {
return Container(
height: 64,
padding: const EdgeInsets.only(top: 16, left: 16, right: 16),
child: Row(
children: [
//
GestureDetector(
onTap: _goBack,
child: Container(
width: 48,
height: 48,
alignment: Alignment.center,
child: const Icon(
Icons.arrow_back,
size: 24,
color: Color(0xFF5D4037),
),
),
),
//
Expanded(
child: Text(
'编辑资料',
style: const TextStyle(
fontSize: 18,
fontFamily: 'Inter',
fontWeight: FontWeight.w500,
height: 1.25,
letterSpacing: -0.27,
color: Color(0xFF5D4037),
),
textAlign: TextAlign.center,
),
),
//
const SizedBox(width: 48),
],
),
);
}
///
Widget _buildAvatarSection() {
return GestureDetector(
onTap: _showAvatarPicker,
child: Column(
children: [
//
Container(
width: 128,
height: 128,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: const Color(0xFFFFF5E6),
border: Border.all(
color: const Color(0x33D4AF37),
width: 2,
),
),
child: ClipOval(
child: _avatarPath != null
? Image.asset(
_avatarPath!,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return const Icon(
Icons.person,
size: 64,
color: Color(0xFF8B5A2B),
);
},
)
: Image.asset(
'assets/images/Image-Border@2x.png',
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return const Icon(
Icons.person,
size: 64,
color: Color(0xFF8B5A2B),
);
},
),
),
),
const SizedBox(height: 16),
//
const Text(
'修改头像',
style: TextStyle(
fontSize: 16,
fontFamily: 'Inter',
fontWeight: FontWeight.w500,
height: 1.5,
color: Color(0xFF8B5A2B),
),
),
],
),
);
}
///
Widget _buildNicknameSection() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//
const Text(
'昵称',
style: TextStyle(
fontSize: 16,
fontFamily: 'Inter',
fontWeight: FontWeight.w500,
height: 1.5,
color: Color(0xFF5D4037),
),
),
const SizedBox(height: 7),
//
Container(
width: double.infinity,
height: 54,
decoration: BoxDecoration(
color: const Color(0x80FFFFFF),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: const Color(0x80FFFFFF),
width: 1,
),
boxShadow: const [
BoxShadow(
color: Color(0x0D000000),
blurRadius: 2,
offset: Offset(0, 1),
),
],
),
child: TextField(
controller: _nicknameController,
style: const TextStyle(
fontSize: 16,
fontFamily: 'Inter',
height: 1.19,
color: Color(0xFF5D4037),
),
decoration: const InputDecoration(
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 15),
border: InputBorder.none,
hintText: '请输入昵称',
hintStyle: TextStyle(
fontSize: 16,
fontFamily: 'Inter',
height: 1.19,
color: Color(0x995D4037),
),
),
),
),
],
),
);
}
///
Widget _buildSaveButton() {
return Container(
padding: const EdgeInsets.all(16),
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFFFFE4B5),
Color(0x00FFE4B5),
],
),
),
child: GestureDetector(
onTap: _isSaving ? null : _saveProfile,
child: Container(
width: double.infinity,
height: 56,
decoration: BoxDecoration(
color: const Color(0xFFD4AF37),
borderRadius: BorderRadius.circular(12),
boxShadow: const [
BoxShadow(
color: Color(0x4DD4AF37),
blurRadius: 14,
offset: Offset(0, 4),
),
],
),
child: Center(
child: _isSaving
? const SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
)
: const Text(
'保存',
style: TextStyle(
fontSize: 16,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.5,
letterSpacing: 0.24,
color: Colors.white,
),
),
),
),
),
);
}
///
Widget _buildAvatarPickerSheet() {
return Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
//
Container(
width: 40,
height: 4,
margin: const EdgeInsets.only(top: 8),
decoration: BoxDecoration(
color: const Color(0x33000000),
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(height: 16),
//
_buildPickerOption(
title: '拍照',
onTap: _takePhoto,
showDivider: true,
),
//
_buildPickerOption(
title: '从相册选择',
onTap: _pickFromGallery,
showDivider: true,
),
//
_buildPickerOption(
title: '删除头像',
onTap: _deleteAvatar,
showDivider: false,
),
const SizedBox(height: 8),
//
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: GestureDetector(
onTap: _cancelPicker,
child: Container(
width: double.infinity,
height: 57,
decoration: BoxDecoration(
color: const Color(0x33D4AF37),
borderRadius: BorderRadius.circular(12),
),
child: const Center(
child: Text(
'取消',
style: TextStyle(
fontSize: 16,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.5,
color: Color(0xFF8B5A2B),
),
),
),
),
),
),
const SizedBox(height: 16),
],
),
);
}
///
Widget _buildPickerOption({
required String title,
required VoidCallback onTap,
required bool showDivider,
}) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
children: [
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 16),
child: Text(
title,
style: const TextStyle(
fontSize: 16,
fontFamily: 'Inter',
height: 1.5,
color: Color(0xFF5D4037),
),
textAlign: TextAlign.center,
),
),
if (showDivider)
Container(
height: 1,
color: const Color(0x1A000000),
),
],
),
),
);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,455 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
///
enum RankingType { daily, weekly, monthly }
///
enum FilterType { all, previousDay, previousWeek, previousMonth }
///
class RankingItem {
final int rank;
final String name;
final String province;
final String city;
final int teamPlantingAmount;
final String? avatarUrl;
RankingItem({
required this.rank,
required this.name,
required this.province,
required this.city,
required this.teamPlantingAmount,
this.avatarUrl,
});
}
/// -
///
class RankingPage extends ConsumerStatefulWidget {
const RankingPage({super.key});
@override
ConsumerState<RankingPage> createState() => _RankingPageState();
}
class _RankingPageState extends ConsumerState<RankingPage> {
//
RankingType _selectedRankingType = RankingType.daily;
//
FilterType _selectedFilterType = FilterType.all;
//
final List<RankingItem> _mockRankingData = [
RankingItem(
rank: 1,
name: '环保先锋',
province: '广东',
city: '深圳',
teamPlantingAmount: 1234567,
),
RankingItem(
rank: 2,
name: '绿色卫士',
province: '广东',
city: '深圳',
teamPlantingAmount: 1123456,
),
RankingItem(
rank: 3,
name: '低碳达人',
province: '广东',
city: '深圳',
teamPlantingAmount: 987654,
),
RankingItem(
rank: 4,
name: '节能小能手',
province: '广东',
city: '深圳',
teamPlantingAmount: 876543,
),
RankingItem(
rank: 5,
name: '分类高手',
province: '广东',
city: '深圳',
teamPlantingAmount: 765432,
),
];
///
void _selectRankingType(RankingType type) {
setState(() {
_selectedRankingType = type;
});
}
///
void _selectFilterType(FilterType type) {
setState(() {
_selectedFilterType = type;
});
}
///
String _formatNumber(int number) {
return number.toString().replaceAllMapped(
RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'),
(Match m) => '${m[1]},',
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Column(
children: [
// Tab栏
_buildHeader(),
//
_buildFilterBar(),
//
Expanded(
child: _buildRankingList(),
),
],
),
);
}
/// Tab栏
Widget _buildHeader() {
return Container(
color: const Color(0xCCFFF5E6),
child: SafeArea(
bottom: false,
child: Column(
children: [
//
Container(
height: 56,
padding: const EdgeInsets.symmetric(horizontal: 16),
child: const Center(
child: Text(
'龙虎榜',
style: TextStyle(
fontSize: 18,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.25,
letterSpacing: -0.27,
color: Color(0xFF5D4037),
),
),
),
),
// Tab栏
_buildTabBar(),
],
),
),
);
}
/// Tab栏
Widget _buildTabBar() {
return Container(
height: 45,
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(
width: 1,
color: Color(0x338B5A2B),
),
),
),
child: Row(
children: [
//
Expanded(
child: _buildTabItem(
title: '日榜',
isSelected: _selectedRankingType == RankingType.daily,
onTap: () => _selectRankingType(RankingType.daily),
),
),
//
Expanded(
child: _buildTabItem(
title: '周榜',
isSelected: _selectedRankingType == RankingType.weekly,
onTap: () => _selectRankingType(RankingType.weekly),
),
),
//
Expanded(
child: _buildTabItem(
title: '月榜',
isSelected: _selectedRankingType == RankingType.monthly,
onTap: () => _selectRankingType(RankingType.monthly),
),
),
],
),
);
}
/// Tab项
Widget _buildTabItem({
required String title,
required bool isSelected,
required VoidCallback onTap,
}) {
return GestureDetector(
onTap: onTap,
child: Container(
height: 44,
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 3,
color: isSelected ? const Color(0xFFD4AF37) : Colors.transparent,
),
),
),
child: Center(
child: Text(
title,
style: TextStyle(
fontSize: 14,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.5,
letterSpacing: 0.21,
color: isSelected ? const Color(0xFFD4AF37) : const Color(0xFF8B5A2B),
),
),
),
),
);
}
///
Widget _buildFilterBar() {
return Container(
height: 56,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
_buildFilterChip(
title: '全部',
isSelected: _selectedFilterType == FilterType.all,
onTap: () => _selectFilterType(FilterType.all),
),
const SizedBox(width: 12),
_buildFilterChip(
title: '上一日榜',
isSelected: _selectedFilterType == FilterType.previousDay,
onTap: () => _selectFilterType(FilterType.previousDay),
),
const SizedBox(width: 12),
_buildFilterChip(
title: '上一周榜',
isSelected: _selectedFilterType == FilterType.previousWeek,
onTap: () => _selectFilterType(FilterType.previousWeek),
),
const SizedBox(width: 12),
_buildFilterChip(
title: '上一月榜',
isSelected: _selectedFilterType == FilterType.previousMonth,
onTap: () => _selectFilterType(FilterType.previousMonth),
),
],
),
),
);
}
///
Widget _buildFilterChip({
required String title,
required bool isSelected,
required VoidCallback onTap,
}) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
decoration: BoxDecoration(
color: isSelected ? const Color(0xFFD4AF37) : const Color(0x1A8B5A2B),
borderRadius: BorderRadius.circular(9999),
),
child: Text(
title,
style: TextStyle(
fontSize: 14,
fontFamily: 'Inter',
fontWeight: FontWeight.w500,
height: 1.5,
color: isSelected ? Colors.white : const Color(0xFF8B5A2B),
),
),
),
);
}
///
Widget _buildRankingList() {
return ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
itemCount: _mockRankingData.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: _buildRankingItem(_mockRankingData[index]),
);
},
);
}
///
Widget _buildRankingItem(RankingItem item) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0x80FFFFFF),
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
//
SizedBox(
width: 32,
child: _buildRankBadge(item.rank),
),
const SizedBox(width: 16),
//
_buildAvatar(item),
const SizedBox(width: 16),
//
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.name,
style: const TextStyle(
fontSize: 16,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.5,
color: Color(0xFF5D4037),
),
),
Text(
'省市: ${item.province} · ${item.city}',
style: const TextStyle(
fontSize: 14,
fontFamily: 'Inter',
height: 1.5,
color: Color(0xFF8B5A2B),
),
),
],
),
),
//
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
_formatNumber(item.teamPlantingAmount),
style: const TextStyle(
fontSize: 16,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.5,
color: Color(0xFFD4AF37),
),
),
const Text(
'团队认种量',
style: TextStyle(
fontSize: 12,
fontFamily: 'Inter',
height: 1.5,
color: Color(0xFF8B5A2B),
),
),
],
),
],
),
);
}
///
Widget _buildRankBadge(int rank) {
// 使
if (rank <= 3) {
Color crownColor;
switch (rank) {
case 1:
crownColor = const Color(0xFFFFD700); //
break;
case 2:
crownColor = const Color(0xFFC0C0C0); //
break;
case 3:
crownColor = const Color(0xFFCD7F32); //
break;
default:
crownColor = const Color(0xFF8B5A2B);
}
return Icon(
Icons.workspace_premium,
size: 32,
color: crownColor,
);
}
//
return Text(
rank.toString(),
style: const TextStyle(
fontSize: 20,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.4,
color: Color(0xFF8B5A2B),
),
textAlign: TextAlign.center,
);
}
///
Widget _buildAvatar(RankingItem item) {
return Container(
width: 56,
height: 56,
decoration: BoxDecoration(
color: const Color(0x33D4AF37),
borderRadius: BorderRadius.circular(28),
),
child: item.avatarUrl != null
? ClipRRect(
borderRadius: BorderRadius.circular(28),
child: Image.network(
item.avatarUrl!,
fit: BoxFit.cover,
),
)
: const Icon(
Icons.person,
size: 32,
color: Color(0xFF8B5A2B),
),
);
}
}

View File

@ -0,0 +1,386 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
///
enum SettlementCurrency { bnb, og, usdt, dst }
/// -
/// DST转USDT
class TradingPage extends ConsumerStatefulWidget {
const TradingPage({super.key});
@override
ConsumerState<TradingPage> createState() => _TradingPageState();
}
class _TradingPageState extends ConsumerState<TradingPage> {
//
SettlementCurrency _selectedCurrency = SettlementCurrency.usdt;
//
final double _settleableAmount = 1234.56;
final double _dstBalance = 5678.90;
///
void _selectCurrency(SettlementCurrency currency) {
setState(() {
_selectedCurrency = currency;
});
}
///
void _onSettlement() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('确认结算'),
content: Text(
'确定将 ${_formatNumber(_settleableAmount)} USDT 结算为 ${_getCurrencyName(_selectedCurrency)} 吗?',
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
TextButton(
onPressed: () {
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('结算成功'),
backgroundColor: Color(0xFFD4AF37),
),
);
},
child: const Text('确认'),
),
],
),
);
}
/// DST转换为USDT
void _onSellDst() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('卖出 DST'),
content: Text(
'确定将 ${_formatNumber(_dstBalance)} DST 转换为 USDT 吗?',
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
TextButton(
onPressed: () {
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('转换成功'),
backgroundColor: Color(0xFFD4AF37),
),
);
},
child: const Text('确认'),
),
],
),
);
}
///
String _formatNumber(double number) {
final parts = number.toStringAsFixed(2).split('.');
final intPart = parts[0].replaceAllMapped(
RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'),
(Match m) => '${m[1]},',
);
return '$intPart.${parts[1]}';
}
///
String _getCurrencyName(SettlementCurrency currency) {
switch (currency) {
case SettlementCurrency.bnb:
return 'BNB';
case SettlementCurrency.og:
return 'OG';
case SettlementCurrency.usdt:
return 'USDT';
case SettlementCurrency.dst:
return 'DST';
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Container(
width: double.infinity,
height: double.infinity,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFFFFF5E6),
Color(0xFFFFE4B5),
],
),
),
child: SafeArea(
child: SingleChildScrollView(
child: Column(
children: [
//
_buildAppBar(),
const SizedBox(height: 16),
//
_buildSettleableCard(),
const SizedBox(height: 24),
//
_buildSettlementButton(),
const SizedBox(height: 16),
//
_buildCurrencySelector(),
const SizedBox(height: 24),
// 线
_buildDivider(),
const SizedBox(height: 24),
// DST按钮
_buildSellDstButton(),
const SizedBox(height: 8),
// DST余额显示
_buildDstBalance(),
],
),
),
),
),
);
}
///
Widget _buildAppBar() {
return Container(
height: 56,
padding: const EdgeInsets.symmetric(horizontal: 16),
child: const Center(
child: Text(
'交易',
style: TextStyle(
fontSize: 18,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.25,
letterSpacing: -0.27,
color: Color(0xFF5D4037),
),
),
),
);
}
///
Widget _buildSettleableCard() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: const Color(0x1A8B5A2B),
borderRadius: BorderRadius.circular(12),
),
child: Column(
children: [
const Text(
'可结算收益',
style: TextStyle(
fontSize: 14,
fontFamily: 'Inter',
height: 1.5,
color: Color(0xCC8B5A2B),
),
),
const SizedBox(height: 8),
Text(
'${_formatNumber(_settleableAmount)} USDT',
style: const TextStyle(
fontSize: 32,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.25,
letterSpacing: -0.8,
color: Color(0xFF5D4037),
),
),
],
),
),
);
}
///
Widget _buildSettlementButton() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: GestureDetector(
onTap: _onSettlement,
child: Container(
width: double.infinity,
height: 56,
decoration: BoxDecoration(
color: const Color(0xFFD4AF37),
borderRadius: BorderRadius.circular(12),
),
child: const Center(
child: Text(
'一键结算',
style: TextStyle(
fontSize: 16,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.5,
letterSpacing: 0.24,
color: Colors.white,
),
),
),
),
),
);
}
///
Widget _buildCurrencySelector() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
children: [
const Text(
'结算的币种',
style: TextStyle(
fontSize: 14,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.5,
letterSpacing: 0.21,
color: Color(0xCC8B5A2B),
),
),
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: const Color(0x1A8B5A2B),
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
_buildCurrencyChip(SettlementCurrency.bnb, 'BNB'),
const SizedBox(width: 8),
_buildCurrencyChip(SettlementCurrency.og, 'OG'),
const SizedBox(width: 8),
_buildCurrencyChip(SettlementCurrency.usdt, 'USDT'),
const SizedBox(width: 8),
_buildCurrencyChip(SettlementCurrency.dst, 'DST'),
],
),
),
],
),
);
}
///
Widget _buildCurrencyChip(SettlementCurrency currency, String label) {
final isSelected = _selectedCurrency == currency;
return Expanded(
child: GestureDetector(
onTap: () => _selectCurrency(currency),
child: Container(
height: 40,
decoration: BoxDecoration(
color: isSelected ? const Color(0xFFD4AF37) : Colors.transparent,
borderRadius: BorderRadius.circular(8),
),
child: Center(
child: Text(
label,
style: TextStyle(
fontSize: 14,
fontFamily: 'Inter',
fontWeight: isSelected ? FontWeight.w700 : FontWeight.w600,
height: 1.43,
color: isSelected ? Colors.white : const Color(0xCC5D4037),
),
),
),
),
),
);
}
/// 线
Widget _buildDivider() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Container(
height: 1,
color: const Color(0x1A8B5A2B),
),
);
}
/// DST按钮
Widget _buildSellDstButton() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: GestureDetector(
onTap: _onSellDst,
child: Container(
width: double.infinity,
height: 56,
decoration: BoxDecoration(
color: const Color(0x338B5A2B),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: const Color(0x808B5A2B),
width: 1,
),
),
child: const Center(
child: Text(
'卖出 DST 转换为 USDT',
style: TextStyle(
fontSize: 16,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.5,
letterSpacing: 0.24,
color: Color(0xFF8B5A2B),
),
),
),
),
),
);
}
/// DST余额显示
Widget _buildDstBalance() {
return Text(
'DST 余额: ${_formatNumber(_dstBalance)}',
style: const TextStyle(
fontSize: 14,
fontFamily: 'Inter',
height: 1.5,
color: Color(0x995D4037),
),
);
}
}

View File

@ -1,122 +1,6 @@
import 'package:flutter/material.dart';
import 'bootstrap.dart';
import 'app.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// TRY THIS: Try running your application with "flutter run". You'll see
// the application has a purple toolbar. Then, without quitting the app,
// try changing the seedColor in the colorScheme below to Colors.green
// and then invoke "hot reload" (save your changes or press the "hot
// reload" button in a Flutter-supported IDE, or press "r" if you used
// the command line to start the app).
//
// Notice that the counter didn't reset back to zero; the application
// state is not lost during the reload. To reset the state, use hot
// restart instead.
//
// This works for code too, not just values: Most code changes can be
// tested with just a hot reload.
colorScheme: .fromSeed(seedColor: Colors.deepPurple),
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
}
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// TRY THIS: Try changing the color here to a specific color (to
// Colors.amber, perhaps?) and trigger a hot reload to see the AppBar
// change color while the other colors stay the same.
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column(
// Column is also a layout widget. It takes a list of children and
// arranges them vertically. By default, it sizes itself to fit its
// children horizontally, and tries to be as tall as its parent.
//
// Column has various properties to control how it sizes itself and
// how it positions its children. Here we use mainAxisAlignment to
// center the children vertically; the main axis here is the vertical
// axis because Columns are vertical (the cross axis would be
// horizontal).
//
// TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint"
// action in the IDE, or press "p" in the console), to see the
// wireframe for each widget.
mainAxisAlignment: .center,
children: [
const Text('You have pushed the button this many times:'),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
bootstrap(() => const App());
}

View File

@ -0,0 +1,175 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../features/auth/presentation/pages/splash_page.dart';
import '../features/auth/presentation/pages/onboarding_page.dart';
import '../features/auth/presentation/pages/backup_mnemonic_page.dart';
import '../features/auth/presentation/pages/verify_mnemonic_page.dart';
import '../features/auth/presentation/pages/wallet_created_page.dart';
import '../features/home/presentation/pages/home_shell_page.dart';
import '../features/ranking/presentation/pages/ranking_page.dart';
import '../features/mining/presentation/pages/mining_page.dart';
import '../features/trading/presentation/pages/trading_page.dart';
import '../features/profile/presentation/pages/profile_page.dart';
import '../features/profile/presentation/pages/edit_profile_page.dart';
import 'route_paths.dart';
import 'route_names.dart';
final _rootNavigatorKey = GlobalKey<NavigatorState>();
final _shellNavigatorKey = GlobalKey<NavigatorState>();
///
class BackupMnemonicParams {
final List<String> mnemonicWords;
final String kavaAddress;
final String dstAddress;
final String bscAddress;
final String serialNumber;
BackupMnemonicParams({
required this.mnemonicWords,
required this.kavaAddress,
required this.dstAddress,
required this.bscAddress,
required this.serialNumber,
});
}
/// BackupMnemonicParams
typedef VerifyMnemonicParams = BackupMnemonicParams;
///
class WalletCreatedParams {
final String kavaAddress;
final String dstAddress;
final String bscAddress;
final String serialNumber;
WalletCreatedParams({
required this.kavaAddress,
required this.dstAddress,
required this.bscAddress,
required this.serialNumber,
});
}
final appRouterProvider = Provider<GoRouter>((ref) {
return GoRouter(
navigatorKey: _rootNavigatorKey,
initialLocation: RoutePaths.splash,
debugLogDiagnostics: true,
routes: [
// Splash Screen
GoRoute(
path: RoutePaths.splash,
name: RouteNames.splash,
builder: (context, state) => const SplashPage(),
),
// Onboarding / Create Account
GoRoute(
path: RoutePaths.onboarding,
name: RouteNames.onboarding,
builder: (context, state) => const OnboardingPage(),
),
// Backup Mnemonic
GoRoute(
path: RoutePaths.backupMnemonic,
name: RouteNames.backupMnemonic,
builder: (context, state) {
final params = state.extra as BackupMnemonicParams;
return BackupMnemonicPage(
mnemonicWords: params.mnemonicWords,
kavaAddress: params.kavaAddress,
dstAddress: params.dstAddress,
bscAddress: params.bscAddress,
serialNumber: params.serialNumber,
);
},
),
// Verify Mnemonic ()
GoRoute(
path: RoutePaths.verifyMnemonic,
name: RouteNames.verifyMnemonic,
builder: (context, state) {
final params = state.extra as VerifyMnemonicParams;
return VerifyMnemonicPage(
mnemonicWords: params.mnemonicWords,
kavaAddress: params.kavaAddress,
dstAddress: params.dstAddress,
bscAddress: params.bscAddress,
serialNumber: params.serialNumber,
);
},
),
// Wallet Created ()
GoRoute(
path: RoutePaths.walletCreated,
name: RouteNames.walletCreated,
builder: (context, state) {
final params = state.extra as WalletCreatedParams;
return WalletCreatedPage(
kavaAddress: params.kavaAddress,
dstAddress: params.dstAddress,
bscAddress: params.bscAddress,
serialNumber: params.serialNumber,
);
},
),
// Edit Profile ()
GoRoute(
path: RoutePaths.editProfile,
name: RouteNames.editProfile,
builder: (context, state) => const EditProfilePage(),
),
// Main Shell with Bottom Navigation
ShellRoute(
navigatorKey: _shellNavigatorKey,
builder: (context, state, child) => HomeShellPage(child: child),
routes: [
// Ranking Tab
GoRoute(
path: RoutePaths.ranking,
name: RouteNames.ranking,
pageBuilder: (context, state) => const NoTransitionPage(
child: RankingPage(),
),
),
// Mining Tab
GoRoute(
path: RoutePaths.mining,
name: RouteNames.mining,
pageBuilder: (context, state) => const NoTransitionPage(
child: MiningPage(),
),
),
// Trading Tab
GoRoute(
path: RoutePaths.trading,
name: RouteNames.trading,
pageBuilder: (context, state) => const NoTransitionPage(
child: TradingPage(),
),
),
// Profile Tab
GoRoute(
path: RoutePaths.profile,
name: RouteNames.profile,
pageBuilder: (context, state) => const NoTransitionPage(
child: ProfilePage(),
),
),
],
),
],
);
});

View File

@ -0,0 +1,30 @@
class RouteNames {
RouteNames._();
// Auth
static const splash = 'splash';
static const onboarding = 'onboarding';
static const createWallet = 'create-wallet';
static const backupMnemonic = 'backup-mnemonic';
static const verifyMnemonic = 'verify-mnemonic';
static const walletCreated = 'wallet-created';
static const importWallet = 'import-wallet';
// Main tabs
static const ranking = 'ranking';
static const mining = 'mining';
static const trading = 'trading';
static const profile = 'profile';
// Sub pages
static const editProfile = 'edit-profile';
static const referralList = 'referral-list';
static const earningsDetail = 'earnings-detail';
static const deposit = 'deposit';
static const plantingQuantity = 'planting-quantity';
static const plantingLocation = 'planting-location';
static const googleAuth = 'google-auth';
static const changePassword = 'change-password';
static const bindEmail = 'bind-email';
static const transactionHistory = 'transaction-history';
}

View File

@ -0,0 +1,30 @@
class RoutePaths {
RoutePaths._();
// Auth
static const splash = '/';
static const onboarding = '/onboarding';
static const createWallet = '/auth/create';
static const backupMnemonic = '/auth/backup-mnemonic';
static const verifyMnemonic = '/auth/verify-mnemonic';
static const walletCreated = '/auth/wallet-created';
static const importWallet = '/auth/import';
// Main tabs
static const ranking = '/ranking';
static const mining = '/mining';
static const trading = '/trading';
static const profile = '/profile';
// Sub pages
static const editProfile = '/profile/edit';
static const referralList = '/profile/referrals';
static const earningsDetail = '/profile/earnings';
static const deposit = '/deposit';
static const plantingQuantity = '/planting/quantity';
static const plantingLocation = '/planting/location';
static const googleAuth = '/security/google-auth';
static const changePassword = '/security/password';
static const bindEmail = '/security/email';
static const transactionHistory = '/trading/history';
}

View File

@ -6,6 +6,18 @@
#include "generated_plugin_registrant.h"
#include <file_selector_linux/file_selector_plugin.h>
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
}

View File

@ -3,6 +3,9 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
file_selector_linux
flutter_secure_storage_linux
url_launcher_linux
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST

View File

@ -5,6 +5,24 @@
import FlutterMacOS
import Foundation
import connectivity_plus
import file_selector_macos
import flutter_secure_storage_macos
import local_auth_darwin
import path_provider_foundation
import share_plus
import shared_preferences_foundation
import sqflite_darwin
import url_launcher_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
LocalAuthPlugin.register(with: registry.registrar(forPlugin: "LocalAuthPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
}

File diff suppressed because it is too large Load Diff

View File

@ -1,89 +1,91 @@
name: rwa_android_app
description: "A new Flutter project."
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
description: RWA榴莲女皇移动应用
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: ^3.10.1
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
# 状态管理
flutter_riverpod: ^2.5.1
riverpod_annotation: ^2.3.5
# 路由
go_router: ^14.2.0
# 网络
dio: ^5.4.3+1
connectivity_plus: ^6.0.3
# 区块链
web3dart: ^2.7.3
bip39: ^1.0.6
ed25519_hd_key: ^2.3.0
hex: ^0.2.0
# 本地存储
shared_preferences: ^2.2.3
flutter_secure_storage: ^9.0.0
hive: ^2.2.3
hive_flutter: ^1.1.0
# UI组件
flutter_svg: ^2.0.10+1
cached_network_image: ^3.3.1
shimmer: ^3.0.0
lottie: ^3.1.0
qr_flutter: ^4.1.0
flutter_screenutil: ^5.9.0
# 工具
intl: ^0.20.2
logger: ^2.2.0
equatable: ^2.0.5
dartz: ^0.10.1
uuid: ^4.3.3
image_picker: ^1.0.7
permission_handler: ^11.3.1
url_launcher: ^6.2.6
share_plus: ^8.0.3
# 生物识别
local_auth: ^2.2.0
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8
dev_dependencies:
flutter_test:
sdk: flutter
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^6.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# 代码生成
build_runner: ^2.4.9
riverpod_generator: ^2.4.0
freezed: ^2.5.2
freezed_annotation: ^2.4.1
json_serializable: ^6.7.1
hive_generator: ^2.0.1
# 测试
mocktail: ^1.0.3
# The following section is specific to Flutter packages.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/to/asset-from-package
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/to/font-from-package
assets:
- assets/images/
- assets/images/logo/
- assets/images/backgrounds/
- assets/images/avatars/
- assets/images/illustrations/
- assets/icons/
- assets/icons/nav/
- assets/icons/tokens/
- assets/icons/actions/
- assets/lottie/

View File

@ -6,6 +6,27 @@
#include "generated_plugin_registrant.h"
#include <connectivity_plus/connectivity_plus_windows_plugin.h>
#include <file_selector_windows/file_selector_windows.h>
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
#include <local_auth_windows/local_auth_plugin.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <share_plus/share_plus_windows_plugin_c_api.h>
#include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
FileSelectorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FileSelectorWindows"));
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
LocalAuthPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("LocalAuthPlugin"));
PermissionHandlerWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
SharePlusWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
}

View File

@ -3,6 +3,13 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
connectivity_plus
file_selector_windows
flutter_secure_storage_windows
local_auth_windows
permission_handler_windows
share_plus
url_launcher_windows
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST