Flutter v0.1 draft ok
This commit is contained in:
parent
79f8f4f4ee
commit
2915c4ccc5
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -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';
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
@ -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)');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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';
|
||||
}
|
||||
|
|
@ -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 = '存储操作失败']);
|
||||
}
|
||||
|
|
@ -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),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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+$'), '');
|
||||
}
|
||||
}
|
||||
|
|
@ -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)}';
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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';
|
||||
}
|
||||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
@ -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),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建助记词网格 (4行3列)
|
||||
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),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 格式化地址显示 (显示前6位和后4位)
|
||||
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),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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), // 深棕色文字
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 格式化地址显示 (显示前6位和后4位)
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
});
|
||||
|
|
@ -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),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
|
@ -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';
|
||||
}
|
||||
|
|
@ -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';
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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/
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue