diff --git a/frontend/mobile-app/android/gradle.properties b/frontend/mobile-app/android/gradle.properties index fbee1d8c..c587f726 100644 --- a/frontend/mobile-app/android/gradle.properties +++ b/frontend/mobile-app/android/gradle.properties @@ -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 diff --git a/frontend/mobile-app/assets/fonts/.gitkeep b/frontend/mobile-app/assets/fonts/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/frontend/mobile-app/assets/icons/.gitkeep b/frontend/mobile-app/assets/icons/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/frontend/mobile-app/assets/icons/actions/.gitkeep b/frontend/mobile-app/assets/icons/actions/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/frontend/mobile-app/assets/icons/nav/.gitkeep b/frontend/mobile-app/assets/icons/nav/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/frontend/mobile-app/assets/icons/tokens/.gitkeep b/frontend/mobile-app/assets/icons/tokens/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/frontend/mobile-app/assets/images/.gitkeep b/frontend/mobile-app/assets/images/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/frontend/mobile-app/assets/images/Overlay-19.svg b/frontend/mobile-app/assets/images/Overlay-19.svg new file mode 100644 index 00000000..d79816f6 --- /dev/null +++ b/frontend/mobile-app/assets/images/Overlay-19.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/mobile-app/assets/images/Overlay.svg b/frontend/mobile-app/assets/images/Overlay.svg new file mode 100644 index 00000000..d0a9e6ba --- /dev/null +++ b/frontend/mobile-app/assets/images/Overlay.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/mobile-app/assets/images/Overlay5.svg b/frontend/mobile-app/assets/images/Overlay5.svg new file mode 100644 index 00000000..e6991bda --- /dev/null +++ b/frontend/mobile-app/assets/images/Overlay5.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/mobile-app/assets/images/avatars/.gitkeep b/frontend/mobile-app/assets/images/avatars/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/frontend/mobile-app/assets/images/backgrounds/.gitkeep b/frontend/mobile-app/assets/images/backgrounds/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/frontend/mobile-app/assets/images/illustrations/.gitkeep b/frontend/mobile-app/assets/images/illustrations/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/frontend/mobile-app/assets/images/logo/.gitkeep b/frontend/mobile-app/assets/images/logo/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/frontend/mobile-app/assets/lottie/.gitkeep b/frontend/mobile-app/assets/lottie/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/frontend/mobile-app/lib/app.dart b/frontend/mobile-app/lib/app.dart new file mode 100644 index 00000000..e64ed6d2 --- /dev/null +++ b/frontend/mobile-app/lib/app.dart @@ -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, + ); + }, + ); + } +} diff --git a/frontend/mobile-app/lib/bootstrap.dart b/frontend/mobile-app/lib/bootstrap.dart new file mode 100644 index 00000000..30987b37 --- /dev/null +++ b/frontend/mobile-app/lib/bootstrap.dart @@ -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 bootstrap(FutureOr 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(), + ), + ); +} diff --git a/frontend/mobile-app/lib/core/constants/api_endpoints.dart b/frontend/mobile-app/lib/core/constants/api_endpoints.dart new file mode 100644 index 00000000..fbd16674 --- /dev/null +++ b/frontend/mobile-app/lib/core/constants/api_endpoints.dart @@ -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'; +} diff --git a/frontend/mobile-app/lib/core/constants/app_constants.dart b/frontend/mobile-app/lib/core/constants/app_constants.dart new file mode 100644 index 00000000..5ab26f20 --- /dev/null +++ b/frontend/mobile-app/lib/core/constants/app_constants.dart @@ -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; +} diff --git a/frontend/mobile-app/lib/core/di/injection_container.dart b/frontend/mobile-app/lib/core/di/injection_container.dart new file mode 100644 index 00000000..25762f77 --- /dev/null +++ b/frontend/mobile-app/lib/core/di/injection_container.dart @@ -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((ref) { + return SecureStorage(); +}); + +final localStorageProvider = Provider((ref) { + throw UnimplementedError('LocalStorage must be initialized before use'); +}); + +// Override provider with initialized instance +ProviderContainer createProviderContainer(LocalStorage localStorage) { + return ProviderContainer( + overrides: [ + localStorageProvider.overrideWithValue(localStorage), + ], + ); +} diff --git a/frontend/mobile-app/lib/core/errors/error_handler.dart b/frontend/mobile-app/lib/core/errors/error_handler.dart new file mode 100644 index 00000000..5cc8f6b0 --- /dev/null +++ b/frontend/mobile-app/lib/core/errors/error_handler.dart @@ -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)'); + } + } +} diff --git a/frontend/mobile-app/lib/core/errors/exceptions.dart b/frontend/mobile-app/lib/core/errors/exceptions.dart new file mode 100644 index 00000000..30f3705f --- /dev/null +++ b/frontend/mobile-app/lib/core/errors/exceptions.dart @@ -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'; +} diff --git a/frontend/mobile-app/lib/core/errors/failures.dart b/frontend/mobile-app/lib/core/errors/failures.dart new file mode 100644 index 00000000..e746eaf2 --- /dev/null +++ b/frontend/mobile-app/lib/core/errors/failures.dart @@ -0,0 +1,41 @@ +import 'package:equatable/equatable.dart'; + +abstract class Failure extends Equatable { + final String message; + const Failure(this.message); + + @override + List 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 = '存储操作失败']); +} diff --git a/frontend/mobile-app/lib/core/extensions/context_extensions.dart b/frontend/mobile-app/lib/core/extensions/context_extensions.dart new file mode 100644 index 00000000..d311ceec --- /dev/null +++ b/frontend/mobile-app/lib/core/extensions/context_extensions.dart @@ -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? result]) => Navigator.of(this).pop(result); + Future push(Widget page) => Navigator.of(this).push( + 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), + ), + ); + } +} diff --git a/frontend/mobile-app/lib/core/extensions/num_extensions.dart b/frontend/mobile-app/lib/core/extensions/num_extensions.dart new file mode 100644 index 00000000..7cec96fc --- /dev/null +++ b/frontend/mobile-app/lib/core/extensions/num_extensions.dart @@ -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+$'), ''); + } +} diff --git a/frontend/mobile-app/lib/core/extensions/string_extensions.dart b/frontend/mobile-app/lib/core/extensions/string_extensions.dart new file mode 100644 index 00000000..6f877bb3 --- /dev/null +++ b/frontend/mobile-app/lib/core/extensions/string_extensions.dart @@ -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)}'; + } +} diff --git a/frontend/mobile-app/lib/core/storage/local_storage.dart b/frontend/mobile-app/lib/core/storage/local_storage.dart new file mode 100644 index 00000000..778c9612 --- /dev/null +++ b/frontend/mobile-app/lib/core/storage/local_storage.dart @@ -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 init() async { + final prefs = await SharedPreferences.getInstance(); + return LocalStorage(prefs); + } + + // String + Future 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 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 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 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 setStringList(String key, List value) async { + try { + return await _prefs.setStringList(key, value); + } catch (e) { + throw StorageException('写入本地存储失败: $e'); + } + } + + List? getStringList(String key) { + try { + return _prefs.getStringList(key); + } catch (e) { + throw StorageException('读取本地存储失败: $e'); + } + } + + // Remove & Clear + Future remove(String key) async { + try { + return await _prefs.remove(key); + } catch (e) { + throw StorageException('删除本地存储失败: $e'); + } + } + + Future clear() async { + try { + return await _prefs.clear(); + } catch (e) { + throw StorageException('清空本地存储失败: $e'); + } + } + + bool containsKey(String key) { + return _prefs.containsKey(key); + } +} diff --git a/frontend/mobile-app/lib/core/storage/secure_storage.dart b/frontend/mobile-app/lib/core/storage/secure_storage.dart new file mode 100644 index 00000000..ceeee8f9 --- /dev/null +++ b/frontend/mobile-app/lib/core/storage/secure_storage.dart @@ -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 write({required String key, required String value}) async { + try { + await _storage.write(key: key, value: value); + } catch (e) { + throw StorageException('写入安全存储失败: $e'); + } + } + + Future read({required String key}) async { + try { + return await _storage.read(key: key); + } catch (e) { + throw StorageException('读取安全存储失败: $e'); + } + } + + Future delete({required String key}) async { + try { + await _storage.delete(key: key); + } catch (e) { + throw StorageException('删除安全存储失败: $e'); + } + } + + Future deleteAll() async { + try { + await _storage.deleteAll(); + } catch (e) { + throw StorageException('清空安全存储失败: $e'); + } + } + + Future containsKey({required String key}) async { + try { + return await _storage.containsKey(key: key); + } catch (e) { + throw StorageException('检查安全存储失败: $e'); + } + } + + Future> readAll() async { + try { + return await _storage.readAll(); + } catch (e) { + throw StorageException('读取所有安全存储失败: $e'); + } + } +} diff --git a/frontend/mobile-app/lib/core/storage/storage_keys.dart b/frontend/mobile-app/lib/core/storage/storage_keys.dart new file mode 100644 index 00000000..fef63b24 --- /dev/null +++ b/frontend/mobile-app/lib/core/storage/storage_keys.dart @@ -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'; +} diff --git a/frontend/mobile-app/lib/core/theme/app_colors.dart b/frontend/mobile-app/lib/core/theme/app_colors.dart new file mode 100644 index 00000000..0f240ff7 --- /dev/null +++ b/frontend/mobile-app/lib/core/theme/app_colors.dart @@ -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, + ); +} diff --git a/frontend/mobile-app/lib/core/theme/app_dimensions.dart b/frontend/mobile-app/lib/core/theme/app_dimensions.dart new file mode 100644 index 00000000..982bc8c1 --- /dev/null +++ b/frontend/mobile-app/lib/core/theme/app_dimensions.dart @@ -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; +} diff --git a/frontend/mobile-app/lib/core/theme/app_gradients.dart b/frontend/mobile-app/lib/core/theme/app_gradients.dart new file mode 100644 index 00000000..a360653c --- /dev/null +++ b/frontend/mobile-app/lib/core/theme/app_gradients.dart @@ -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, + ); +} diff --git a/frontend/mobile-app/lib/core/theme/app_shadows.dart b/frontend/mobile-app/lib/core/theme/app_shadows.dart new file mode 100644 index 00000000..512b3f67 --- /dev/null +++ b/frontend/mobile-app/lib/core/theme/app_shadows.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; + +class AppShadows { + AppShadows._(); + + static List get small => [ + BoxShadow( + color: Colors.black.withOpacity(0.04), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ]; + + static List get medium => [ + BoxShadow( + color: Colors.black.withOpacity(0.08), + blurRadius: 8, + offset: const Offset(0, 4), + ), + ]; + + static List get large => [ + BoxShadow( + color: Colors.black.withOpacity(0.12), + blurRadius: 16, + offset: const Offset(0, 8), + ), + ]; + + static List get card => [ + BoxShadow( + color: Colors.black.withOpacity(0.06), + blurRadius: 12, + offset: const Offset(0, 4), + ), + ]; +} diff --git a/frontend/mobile-app/lib/core/theme/app_text_styles.dart b/frontend/mobile-app/lib/core/theme/app_text_styles.dart new file mode 100644 index 00000000..019d3d07 --- /dev/null +++ b/frontend/mobile-app/lib/core/theme/app_text_styles.dart @@ -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, + ); +} diff --git a/frontend/mobile-app/lib/core/theme/app_theme.dart b/frontend/mobile-app/lib/core/theme/app_theme.dart new file mode 100644 index 00000000..190c57ad --- /dev/null +++ b/frontend/mobile-app/lib/core/theme/app_theme.dart @@ -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, + ), + ); + } +} diff --git a/frontend/mobile-app/lib/core/usecases/usecase.dart b/frontend/mobile-app/lib/core/usecases/usecase.dart new file mode 100644 index 00000000..a09e48f6 --- /dev/null +++ b/frontend/mobile-app/lib/core/usecases/usecase.dart @@ -0,0 +1,10 @@ +import 'package:dartz/dartz.dart'; +import '../errors/failures.dart'; + +abstract class UseCase { + Future> call(Params params); +} + +class NoParams { + const NoParams(); +} diff --git a/frontend/mobile-app/lib/core/utils/logger.dart b/frontend/mobile-app/lib/core/utils/logger.dart new file mode 100644 index 00000000..dd5915b6 --- /dev/null +++ b/frontend/mobile-app/lib/core/utils/logger.dart @@ -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); + } +} diff --git a/frontend/mobile-app/lib/features/auth/presentation/pages/backup_mnemonic_page.dart b/frontend/mobile-app/lib/features/auth/presentation/pages/backup_mnemonic_page.dart new file mode 100644 index 00000000..756244a6 --- /dev/null +++ b/frontend/mobile-app/lib/features/auth/presentation/pages/backup_mnemonic_page.dart @@ -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 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 createState() => _BackupMnemonicPageState(); +} + +class _BackupMnemonicPageState extends ConsumerState { + // 是否隐藏助记词 + 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), + ), + ), + ), + ], + ), + ); + } +} diff --git a/frontend/mobile-app/lib/features/auth/presentation/pages/onboarding_page.dart b/frontend/mobile-app/lib/features/auth/presentation/pages/onboarding_page.dart new file mode 100644 index 00000000..474abdc0 --- /dev/null +++ b/frontend/mobile-app/lib/features/auth/presentation/pages/onboarding_page.dart @@ -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 createState() => _OnboardingPageState(); +} + +class _OnboardingPageState extends ConsumerState { + // 用户协议勾选状态 + bool _isAgreed = false; + // 创建钱包加载状态 + bool _isCreating = false; + + /// 创建钱包并跳转到备份助记词页面 + Future _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(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), + ), + ), + ); + } +} diff --git a/frontend/mobile-app/lib/features/auth/presentation/pages/splash_page.dart b/frontend/mobile-app/lib/features/auth/presentation/pages/splash_page.dart new file mode 100644 index 00000000..d5c93c99 --- /dev/null +++ b/frontend/mobile-app/lib/features/auth/presentation/pages/splash_page.dart @@ -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 createState() => _SplashPageState(); +} + +class _SplashPageState extends ConsumerState { + @override + void initState() { + super.initState(); + _initializeApp(); + } + + /// 初始化应用并检查认证状态 + Future _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), // 深棕色文字 + ), + ); + } +} diff --git a/frontend/mobile-app/lib/features/auth/presentation/pages/verify_mnemonic_page.dart b/frontend/mobile-app/lib/features/auth/presentation/pages/verify_mnemonic_page.dart new file mode 100644 index 00000000..c68372c3 --- /dev/null +++ b/frontend/mobile-app/lib/features/auth/presentation/pages/verify_mnemonic_page.dart @@ -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 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 createState() => _VerifyMnemonicPageState(); +} + +class _VerifyMnemonicPageState extends ConsumerState { + // 勾选"我已备份助记词并理解风险" + bool _isChecked = false; + // 需要验证的单词索引 (随机生成,1-12) + late int _verifyWordIndex; + // 选项列表 + late List _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 _generateOptions() { + final random = Random(); + final options = [_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 _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(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), + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/frontend/mobile-app/lib/features/auth/presentation/pages/wallet_created_page.dart b/frontend/mobile-app/lib/features/auth/presentation/pages/wallet_created_page.dart new file mode 100644 index 00000000..a0241dcb --- /dev/null +++ b/frontend/mobile-app/lib/features/auth/presentation/pages/wallet_created_page.dart @@ -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, + ); + } +} diff --git a/frontend/mobile-app/lib/features/auth/presentation/providers/auth_provider.dart b/frontend/mobile-app/lib/features/auth/presentation/providers/auth_provider.dart new file mode 100644 index 00000000..c9fba9be --- /dev/null +++ b/frontend/mobile-app/lib/features/auth/presentation/providers/auth_provider.dart @@ -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 { + final SecureStorage _secureStorage; + + AuthNotifier(this._secureStorage) : super(const AuthState()); + + Future 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 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 logout() async { + await _secureStorage.deleteAll(); + state = const AuthState(status: AuthStatus.unauthenticated); + } +} + +final authProvider = StateNotifierProvider((ref) { + final secureStorage = ref.watch(secureStorageProvider); + return AuthNotifier(secureStorage); +}); diff --git a/frontend/mobile-app/lib/features/home/presentation/pages/home_shell_page.dart b/frontend/mobile-app/lib/features/home/presentation/pages/home_shell_page.dart new file mode 100644 index 00000000..13043cd5 --- /dev/null +++ b/frontend/mobile-app/lib/features/home/presentation/pages/home_shell_page.dart @@ -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), + ), + ); + } +} diff --git a/frontend/mobile-app/lib/features/home/presentation/providers/navigation_provider.dart b/frontend/mobile-app/lib/features/home/presentation/providers/navigation_provider.dart new file mode 100644 index 00000000..bba4bd55 --- /dev/null +++ b/frontend/mobile-app/lib/features/home/presentation/providers/navigation_provider.dart @@ -0,0 +1,20 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +enum NavTab { + ranking, + mining, + trading, + profile, +} + +class NavigationNotifier extends StateNotifier { + NavigationNotifier() : super(NavTab.ranking); + + void setTab(NavTab tab) { + state = tab; + } +} + +final navigationProvider = StateNotifierProvider((ref) { + return NavigationNotifier(); +}); diff --git a/frontend/mobile-app/lib/features/home/presentation/widgets/bottom_nav_bar.dart b/frontend/mobile-app/lib/features/home/presentation/widgets/bottom_nav_bar.dart new file mode 100644 index 00000000..003f2521 --- /dev/null +++ b/frontend/mobile-app/lib/features/home/presentation/widgets/bottom_nav_bar.dart @@ -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), + ), + ), + ], + ), + ), + ); + } +} diff --git a/frontend/mobile-app/lib/features/mining/presentation/pages/mining_page.dart b/frontend/mobile-app/lib/features/mining/presentation/pages/mining_page.dart new file mode 100644 index 00000000..2741037a --- /dev/null +++ b/frontend/mobile-app/lib/features/mining/presentation/pages/mining_page.dart @@ -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 createState() => _MiningPageState(); +} + +class _MiningPageState extends ConsumerState { + // 当前挖矿状态 + 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), + ), + ), + ], + ); + } +} diff --git a/frontend/mobile-app/lib/features/profile/presentation/pages/edit_profile_page.dart b/frontend/mobile-app/lib/features/profile/presentation/pages/edit_profile_page.dart new file mode 100644 index 00000000..29d1ddb9 --- /dev/null +++ b/frontend/mobile-app/lib/features/profile/presentation/pages/edit_profile_page.dart @@ -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 createState() => _EditProfilePageState(); +} + +class _EditProfilePageState extends ConsumerState { + // 昵称控制器 + 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 _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(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), + ), + ], + ), + ), + ); + } +} diff --git a/frontend/mobile-app/lib/features/profile/presentation/pages/profile_page.dart b/frontend/mobile-app/lib/features/profile/presentation/pages/profile_page.dart new file mode 100644 index 00000000..8ee10cb5 --- /dev/null +++ b/frontend/mobile-app/lib/features/profile/presentation/pages/profile_page.dart @@ -0,0 +1,1268 @@ +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 'dart:async'; + +/// 个人中心页面 - 显示用户信息、社区数据、收益和设置 +/// 包含用户资料、推荐信息、社区考核、收益领取等功能 +class ProfilePage extends ConsumerStatefulWidget { + const ProfilePage({super.key}); + + @override + ConsumerState createState() => _ProfilePageState(); +} + +class _ProfilePageState extends ConsumerState { + // 模拟用户数据 + final String _nickname = '用户昵称'; + final String _serialNumber = '12345678'; + final String _referrerSerial = '87654321'; + final String _community = '星空社区'; + final String _parentCommunity = '银河社区'; + final String _childCommunity = '星辰社区'; + final String _cityCompany = '深圳公司'; + final String _provinceCompany = '广东公司'; + final String _province = '广东'; + + // 团队数据 + final int _teamUsers = 1204; + final int _teamPlanting = 8930; + + // 直推数据 + final List> _referrals = [ + {'serial': '87654321', 'personal': 15, 'team': 120}, + {'serial': '87654322', 'personal': 10, 'team': 85}, + {'serial': '87654323', 'personal': 25, 'team': 250}, + ]; + + // 社区考核数据 + final int _communityLevel = 3; + final int _currentPlanting = 12; + final int _requiredPlanting = 50; + + // 收益数据 + final double _pendingUsdt = 320.75; + final double _pendingPower = 50.00; + final double _settleableUsdt = 1500.00; + final double _settledUsdt = 8500.50; + final double _expiredUsdt = 250.00; + final double _expiredPower = 15.00; + + // 倒计时 + Timer? _timer; + int _remainingSeconds = 66942; // 18:35:42 + + @override + void initState() { + super.initState(); + _startCountdown(); + } + + @override + void dispose() { + _timer?.cancel(); + super.dispose(); + } + + /// 开始倒计时 + void _startCountdown() { + _timer = Timer.periodic(const Duration(seconds: 1), (timer) { + if (_remainingSeconds > 0) { + setState(() { + _remainingSeconds--; + }); + } + }); + } + + /// 格式化倒计时 + String _formatCountdown() { + final hours = (_remainingSeconds ~/ 3600).toString().padLeft(2, '0'); + final minutes = ((_remainingSeconds % 3600) ~/ 60).toString().padLeft(2, '0'); + final seconds = (_remainingSeconds % 60).toString().padLeft(2, '0'); + return '$hours : $minutes : $seconds'; + } + + /// 复制序列号 + void _copySerialNumber() { + Clipboard.setData(ClipboardData(text: _serialNumber)); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('序列号已复制'), + backgroundColor: Color(0xFFD4AF37), + ), + ); + } + + /// 复制分享链接 + void _copyShareLink() { + Clipboard.setData(ClipboardData(text: 'https://rwa.app/invite/$_serialNumber')); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('分享链接已复制'), + backgroundColor: Color(0xFFD4AF37), + ), + ); + } + + /// 领取全部收益 + void _claimAllEarnings() { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('确认领取'), + content: Text( + '确定领取全部收益吗?\nUSDT: ${_formatNumber(_pendingUsdt)}\n算力: ${_formatNumber(_pendingPower)}', + ), + 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('确认'), + ), + ], + ), + ); + } + + /// 结算 + void _onSettlement() { + context.go(RoutePaths.trading); + } + + /// 充值USDT + void _onDeposit() { + context.push(RoutePaths.deposit); + } + + /// 进入交易 + void _goToTrading() { + context.go(RoutePaths.trading); + } + + /// 进入认种 + void _goToPlanting() { + context.push(RoutePaths.plantingQuantity); + } + + /// 谷歌验证器 + void _goToGoogleAuth() { + context.push(RoutePaths.googleAuth); + } + + /// 修改密码 + void _goToChangePassword() { + context.push(RoutePaths.changePassword); + } + + /// 绑定邮箱 + void _goToBindEmail() { + context.push(RoutePaths.bindEmail); + } + + /// 编辑资料 + void _goToEditProfile() { + context.push(RoutePaths.editProfile); + } + + /// 格式化数字 + 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 _formatInt(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: 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: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 16), + // 用户头像和基本信息 + _buildUserHeader(), + const SizedBox(height: 16), + // 推荐人信息卡片 + _buildReferralInfoCard(), + const SizedBox(height: 16), + // 社区/省份标签 + _buildCommunityLabel(), + const SizedBox(height: 8), + // 认种按钮 + _buildPlantingButton(), + const SizedBox(height: 16), + // 主要内容卡片 + _buildMainContentCard(), + const SizedBox(height: 24), + ], + ), + ), + ), + ), + ), + ); + } + + /// 构建用户头像和基本信息 + Widget _buildUserHeader() { + return Row( + children: [ + // 头像(点击跳转到编辑资料页面) + GestureDetector( + onTap: _goToEditProfile, + child: Container( + width: 80, + height: 80, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(40), + color: const Color(0xFFFFF5E6), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(40), + child: Image.asset( + 'assets/images/Button@2x.png', + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return const Icon( + Icons.person, + size: 40, + color: Color(0xFF8B5A2B), + ); + }, + ), + ), + ), + ), + const SizedBox(width: 16), + // 用户信息 + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + _nickname, + style: const TextStyle( + fontSize: 20, + fontFamily: 'Inter', + fontWeight: FontWeight.w700, + height: 1.25, + letterSpacing: -0.3, + color: Color(0xFF5D4037), + ), + ), + const SizedBox(height: 3), + Row( + children: [ + Text( + '序列号: $_serialNumber', + style: const TextStyle( + fontSize: 14, + fontFamily: 'Inter', + height: 1.5, + color: Color(0xCC5D4037), + ), + ), + const SizedBox(width: 8), + GestureDetector( + onTap: _copySerialNumber, + child: const Icon( + Icons.copy, + size: 16, + color: Color(0xCC5D4037), + ), + ), + ], + ), + ], + ), + ), + ], + ); + } + + /// 构建推荐人信息卡片 + Widget _buildReferralInfoCard() { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: const Color(0x80FFFFFF), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: const Color(0x33D4AF37), + width: 1, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + '推荐人的序列号:', + style: const TextStyle( + fontSize: 14, + fontFamily: 'Inter', + height: 1.5, + color: Color(0xCC5D4037), + ), + ), + ), + Expanded( + child: Text( + '下级社区:', + style: const TextStyle( + fontSize: 14, + fontFamily: 'Inter', + height: 1.5, + color: Color(0xCC5D4037), + ), + ), + ), + ], + ), + const SizedBox(height: 4), + Row( + children: [ + Expanded( + child: Text( + '所属社区:', + style: const TextStyle( + fontSize: 14, + fontFamily: 'Inter', + height: 1.5, + color: Color(0xCC5D4037), + ), + ), + ), + Expanded( + child: Text( + '授权市公司:', + style: const TextStyle( + fontSize: 14, + fontFamily: 'Inter', + height: 1.5, + color: Color(0xCC5D4037), + ), + ), + ), + ], + ), + const SizedBox(height: 4), + Row( + children: [ + Expanded( + child: Text( + '上级社区:', + style: const TextStyle( + fontSize: 14, + fontFamily: 'Inter', + height: 1.5, + color: Color(0xCC5D4037), + ), + ), + ), + Expanded( + child: Text( + '授权省公司:', + style: const TextStyle( + fontSize: 14, + fontFamily: 'Inter', + height: 1.5, + color: Color(0xCC5D4037), + ), + ), + ), + ], + ), + ], + ), + ); + } + + /// 构建社区/省份标签 + Widget _buildCommunityLabel() { + return const Text( + '社区 / 省份', + style: TextStyle( + fontSize: 16, + fontFamily: 'Inter', + height: 1.5, + color: Color(0xFF5D4037), + ), + ); + } + + /// 构建认种按钮 + Widget _buildPlantingButton() { + return GestureDetector( + onTap: _goToPlanting, + child: Container( + width: double.infinity, + height: 48, + decoration: BoxDecoration( + color: const Color(0xFFD4AF37), + borderRadius: BorderRadius.circular(8), + ), + child: const Center( + child: Text( + '认种 / Planting', + style: TextStyle( + fontSize: 16, + fontFamily: 'Inter', + fontWeight: FontWeight.w700, + height: 1.5, + letterSpacing: 0.24, + color: Colors.white, + ), + ), + ), + ), + ); + } + + /// 构建主要内容卡片 + Widget _buildMainContentCard() { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: const Color(0x80FFFFFF), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: const Color(0x33D4AF37), + width: 1, + ), + ), + child: Column( + children: [ + // 收益区域 + _buildEarningsSection(), + const SizedBox(height: 16), + // 结算区域 + _buildSettlementSection(), + const SizedBox(height: 16), + // 已过期区域 + _buildExpiredSection(), + const SizedBox(height: 16), + // 操作按钮 + _buildActionButtons(), + const SizedBox(height: 16), + // 团队统计 + _buildTeamStats(), + const SizedBox(height: 16), + // 直推列表 + _buildReferralList(), + const SizedBox(height: 16), + // 复制分享链接 + _buildCopyShareButton(), + const SizedBox(height: 16), + // 社区权益考核 + _buildCommunityAssessment(), + const SizedBox(height: 16), + // 设置菜单 + _buildSettingsMenu(), + ], + ), + ); + } + + /// 构建收益区域 + Widget _buildEarningsSection() { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: const Color(0xFFFFF5E6), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: const Color(0x33D4AF37), + width: 1, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 倒计时 + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + '24 小时倒计时', + style: TextStyle( + fontSize: 12, + fontFamily: 'Inter', + fontWeight: FontWeight.w500, + height: 1.5, + color: Color(0xCC5D4037), + ), + ), + Text( + _formatCountdown(), + style: const TextStyle( + fontSize: 14, + fontFamily: 'Consolas', + fontWeight: FontWeight.w700, + height: 1.25, + letterSpacing: 0.7, + color: Color(0xFFD4AF37), + ), + ), + ], + ), + const SizedBox(height: 15), + // 待领取 USDT + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + '待领取 (USDT)', + style: TextStyle( + fontSize: 14, + fontFamily: 'Inter', + fontWeight: FontWeight.w500, + height: 1.5, + color: Color(0xCC5D4037), + ), + ), + Text( + _formatNumber(_pendingUsdt), + style: const TextStyle( + fontSize: 20, + fontFamily: 'Inter', + fontWeight: FontWeight.w700, + height: 1.25, + color: Color(0xFF5D4037), + ), + ), + ], + ), + const SizedBox(height: 11), + // 待领取 算力 + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + '待领取 (算力)', + style: TextStyle( + fontSize: 14, + fontFamily: 'Inter', + fontWeight: FontWeight.w500, + height: 1.5, + color: Color(0xCC5D4037), + ), + ), + Text( + _formatNumber(_pendingPower), + style: const TextStyle( + fontSize: 20, + fontFamily: 'Inter', + fontWeight: FontWeight.w700, + height: 1.25, + color: Color(0xFF5D4037), + ), + ), + ], + ), + const SizedBox(height: 15), + // 领取全部按钮 + GestureDetector( + onTap: _claimAllEarnings, + child: Container( + width: double.infinity, + height: 40, + decoration: BoxDecoration( + color: const Color(0xFFD4AF37), + borderRadius: BorderRadius.circular(8), + ), + child: const Center( + child: Text( + '领取全部', + style: TextStyle( + fontSize: 14, + fontFamily: 'Inter', + fontWeight: FontWeight.w700, + height: 1.5, + letterSpacing: 0.21, + color: Colors.white, + ), + ), + ), + ), + ), + ], + ), + ); + } + + /// 构建结算区域 + Widget _buildSettlementSection() { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: const Color(0xFFFFF5E6), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: const Color(0x33D4AF37), + width: 1, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 可结算 USDT + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + '可结算 (USDT)', + style: TextStyle( + fontSize: 14, + fontFamily: 'Inter', + fontWeight: FontWeight.w500, + height: 1.5, + color: Color(0xCC5D4037), + ), + ), + Text( + _formatNumber(_settleableUsdt), + style: const TextStyle( + fontSize: 20, + fontFamily: 'Inter', + fontWeight: FontWeight.w700, + height: 1.25, + color: Color(0xFF5D4037), + ), + ), + ], + ), + const SizedBox(height: 11), + // 已结算 USDT + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + '已结算 (USDT)', + style: TextStyle( + fontSize: 14, + fontFamily: 'Inter', + fontWeight: FontWeight.w500, + height: 1.5, + color: Color(0xCC5D4037), + ), + ), + GestureDetector( + onTap: _onSettlement, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 7), + decoration: BoxDecoration( + color: const Color(0xFFD4AF37), + borderRadius: BorderRadius.circular(4), + ), + child: const Text( + '结算', + style: TextStyle( + fontSize: 12, + fontFamily: 'Inter', + fontWeight: FontWeight.w700, + height: 1.5, + letterSpacing: 0.18, + color: Colors.white, + ), + ), + ), + ), + ], + ), + const SizedBox(height: 4), + Text( + _formatNumber(_settledUsdt), + style: const TextStyle( + fontSize: 20, + fontFamily: 'Inter', + fontWeight: FontWeight.w700, + height: 1.25, + color: Color(0xFF5D4037), + ), + ), + ], + ), + ); + } + + /// 构建已过期区域 + Widget _buildExpiredSection() { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: const Color(0xFFFFF5E6), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: const Color(0x33D4AF37), + width: 1, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + '已过期', + style: TextStyle( + fontSize: 16, + fontFamily: 'Inter', + fontWeight: FontWeight.w700, + height: 1.5, + color: Color(0xFF5D4037), + ), + ), + const SizedBox(height: 7), + // 已过期 USDT + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + '已过期 (USDT)', + style: TextStyle( + fontSize: 14, + fontFamily: 'Inter', + fontWeight: FontWeight.w500, + height: 1.5, + color: Color(0xCC5D4037), + ), + ), + Text( + _formatNumber(_expiredUsdt), + style: const TextStyle( + fontSize: 20, + fontFamily: 'Inter', + fontWeight: FontWeight.w700, + height: 1.25, + color: Color(0xFF5D4037), + ), + ), + ], + ), + const SizedBox(height: 11), + // 已过期 算力 + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + '已过期 (算力)', + style: TextStyle( + fontSize: 14, + fontFamily: 'Inter', + fontWeight: FontWeight.w500, + height: 1.5, + color: Color(0xCC5D4037), + ), + ), + Text( + _formatNumber(_expiredPower), + style: const TextStyle( + fontSize: 20, + fontFamily: 'Inter', + fontWeight: FontWeight.w700, + height: 1.25, + color: Color(0xFF5D4037), + ), + ), + ], + ), + ], + ), + ); + } + + /// 构建操作按钮 + Widget _buildActionButtons() { + return Column( + children: [ + // 充值 USDT 按钮 + GestureDetector( + onTap: _onDeposit, + child: Container( + width: double.infinity, + height: 48, + decoration: BoxDecoration( + color: const Color(0xFFD4AF37), + borderRadius: BorderRadius.circular(8), + ), + child: const Center( + child: Text( + '充值 USDT (KAVA / BSC)', + style: TextStyle( + fontSize: 16, + fontFamily: 'Inter', + fontWeight: FontWeight.w700, + height: 1.5, + letterSpacing: 0.24, + color: Colors.white, + ), + ), + ), + ), + ), + const SizedBox(height: 12), + // 进入交易按钮 + GestureDetector( + onTap: _goToTrading, + child: Container( + width: double.infinity, + height: 48, + decoration: BoxDecoration( + color: const Color(0xFF8B5A2B), + borderRadius: BorderRadius.circular(8), + ), + child: const Center( + child: Text( + '进入交易 (卖出 DST → USDT)', + style: TextStyle( + fontSize: 16, + fontFamily: 'Inter', + fontWeight: FontWeight.w700, + height: 1.5, + letterSpacing: 0.24, + color: Colors.white, + ), + ), + ), + ), + ), + ], + ); + } + + /// 构建团队统计 + Widget _buildTeamStats() { + return Row( + children: [ + Expanded( + child: Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: const Color(0xFFFFF5E6), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: const Color(0x33D4AF37), + width: 1, + ), + ), + child: Column( + children: [ + const Text( + '团队注册用户', + style: TextStyle( + fontSize: 14, + fontFamily: 'Inter', + fontWeight: FontWeight.w500, + height: 1.5, + color: Color(0xCC5D4037), + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + Text( + _formatInt(_teamUsers), + style: const TextStyle( + fontSize: 24, + fontFamily: 'Inter', + fontWeight: FontWeight.w700, + height: 1.25, + color: Color(0xFF5D4037), + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + ), + const SizedBox(width: 16), + Expanded( + child: Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: const Color(0xFFFFF5E6), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: const Color(0x33D4AF37), + width: 1, + ), + ), + child: Column( + children: [ + const Text( + '团队种植数量', + style: TextStyle( + fontSize: 14, + fontFamily: 'Inter', + fontWeight: FontWeight.w500, + height: 1.5, + color: Color(0xCC5D4037), + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + Text( + _formatInt(_teamPlanting), + style: const TextStyle( + fontSize: 24, + fontFamily: 'Inter', + fontWeight: FontWeight.w700, + height: 1.25, + color: Color(0xFF5D4037), + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + ), + ], + ); + } + + /// 构建直推列表 + Widget _buildReferralList() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Padding( + padding: EdgeInsets.only(top: 8), + child: Text( + '直推列表', + style: TextStyle( + fontSize: 16, + fontFamily: 'Inter', + fontWeight: FontWeight.w700, + height: 1.5, + color: Color(0xFF5D4037), + ), + ), + ), + const SizedBox(height: 8), + ..._referrals.map((referral) => Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: const Color(0xCCFFF5E6), + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '序列号: ${referral['serial']}', + style: const TextStyle( + fontSize: 14, + fontFamily: 'Inter', + height: 1.43, + color: Color(0xFF5D4037), + ), + ), + Text( + '个人/团队: ${referral['personal']} / ${referral['team']}', + style: const TextStyle( + fontSize: 14, + fontFamily: 'Inter', + height: 1.43, + color: Color(0xCC5D4037), + ), + ), + ], + ), + ), + )), + ], + ); + } + + /// 构建复制分享按钮 + Widget _buildCopyShareButton() { + return GestureDetector( + onTap: _copyShareLink, + child: Container( + width: double.infinity, + height: 48, + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + decoration: BoxDecoration( + color: const Color(0x33D4AF37), + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + Icon( + Icons.share, + size: 24, + color: Color(0xFFD4AF37), + ), + SizedBox(width: 8), + Text( + '复制分享链接', + style: TextStyle( + fontSize: 16, + fontFamily: 'Inter', + fontWeight: FontWeight.w700, + height: 1.5, + letterSpacing: 0.24, + color: Color(0xFFD4AF37), + ), + ), + ], + ), + ), + ); + } + + /// 构建社区权益考核 + Widget _buildCommunityAssessment() { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: const Color(0x80FFFFFF), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: const Color(0x33D4AF37), + width: 1, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + '社区权益考核', + style: TextStyle( + fontSize: 18, + fontFamily: 'Inter', + fontWeight: FontWeight.w700, + height: 1.56, + color: Color(0xFF5D4037), + ), + ), + const SizedBox(height: 15), + // 社区等级 + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + '社区等级', + style: TextStyle( + fontSize: 14, + fontFamily: 'Inter', + fontWeight: FontWeight.w500, + height: 1.43, + color: Color(0xCC5D4037), + ), + ), + Text( + 'Level $_communityLevel', + style: const TextStyle( + fontSize: 14, + fontFamily: 'Inter', + fontWeight: FontWeight.w700, + height: 1.43, + color: Color(0xFF5D4037), + ), + ), + ], + ), + const SizedBox(height: 16), + // 种植数量 + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + '种植数量 / 等级要求', + style: TextStyle( + fontSize: 14, + fontFamily: 'Inter', + fontWeight: FontWeight.w500, + height: 1.43, + color: Color(0xCC5D4037), + ), + ), + Text( + '$_currentPlanting / $_requiredPlanting', + style: const TextStyle( + fontSize: 14, + fontFamily: 'Inter', + fontWeight: FontWeight.w500, + height: 1.43, + color: Color(0xFF5D4037), + ), + ), + ], + ), + const SizedBox(height: 4), + // 进度条 + Container( + height: 6, + decoration: BoxDecoration( + color: const Color(0x33D4AF37), + borderRadius: BorderRadius.circular(3), + ), + child: FractionallySizedBox( + alignment: Alignment.centerLeft, + widthFactor: _currentPlanting / _requiredPlanting, + child: Container( + decoration: BoxDecoration( + color: const Color(0xFFD4AF37), + borderRadius: BorderRadius.circular(3), + ), + ), + ), + ), + const SizedBox(height: 16), + // 社区贡献奖励 + Row( + children: const [ + Text( + '社区贡献奖励', + style: TextStyle( + fontSize: 14, + fontFamily: 'Inter', + fontWeight: FontWeight.w500, + height: 1.43, + color: Color(0xCC5D4037), + ), + ), + SizedBox(width: 39), + Expanded( + child: Text( + '有资格获得 3% 的社区福利', + style: TextStyle( + fontSize: 14, + fontFamily: 'Inter', + fontWeight: FontWeight.w500, + height: 1.43, + color: Color(0xFFD4AF37), + ), + ), + ), + ], + ), + ], + ), + ); + } + + /// 构建设置菜单 + Widget _buildSettingsMenu() { + return Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: const Color(0x80FFFFFF), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: const Color(0x33D4AF37), + width: 1, + ), + ), + child: Column( + children: [ + _buildSettingItem( + icon: Icons.verified_user, + title: '谷歌验证器', + onTap: _goToGoogleAuth, + ), + _buildSettingItem( + icon: Icons.lock, + title: '修改登录密码', + onTap: _goToChangePassword, + ), + _buildSettingItem( + icon: Icons.email, + title: '绑定邮箱', + onTap: _goToBindEmail, + ), + ], + ), + ); + } + + /// 构建设置项 + Widget _buildSettingItem({ + required IconData icon, + required String title, + required VoidCallback onTap, + }) { + return GestureDetector( + onTap: onTap, + child: Container( + height: 48, + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + child: Row( + children: [ + Icon( + icon, + size: 24, + color: const Color(0xFF8B5A2B), + ), + const SizedBox(width: 16), + Expanded( + child: Text( + title, + style: const TextStyle( + fontSize: 16, + fontFamily: 'Inter', + fontWeight: FontWeight.w500, + height: 1.5, + color: Color(0xFF5D4037), + ), + ), + ), + const Icon( + Icons.chevron_right, + size: 24, + color: Color(0xFF8B5A2B), + ), + ], + ), + ), + ); + } +} diff --git a/frontend/mobile-app/lib/features/ranking/presentation/pages/ranking_page.dart b/frontend/mobile-app/lib/features/ranking/presentation/pages/ranking_page.dart new file mode 100644 index 00000000..7e078083 --- /dev/null +++ b/frontend/mobile-app/lib/features/ranking/presentation/pages/ranking_page.dart @@ -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 createState() => _RankingPageState(); +} + +class _RankingPageState extends ConsumerState { + // 当前选中的排行榜类型 + RankingType _selectedRankingType = RankingType.daily; + // 当前选中的筛选类型 + FilterType _selectedFilterType = FilterType.all; + + // 模拟排行榜数据 + final List _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), + ), + ); + } +} diff --git a/frontend/mobile-app/lib/features/trading/presentation/pages/trading_page.dart b/frontend/mobile-app/lib/features/trading/presentation/pages/trading_page.dart new file mode 100644 index 00000000..2b0c3b6a --- /dev/null +++ b/frontend/mobile-app/lib/features/trading/presentation/pages/trading_page.dart @@ -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 createState() => _TradingPageState(); +} + +class _TradingPageState extends ConsumerState { + // 当前选中的结算币种 + 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), + ), + ); + } +} diff --git a/frontend/mobile-app/lib/main.dart b/frontend/mobile-app/lib/main.dart index 244a702c..898030ca 100644 --- a/frontend/mobile-app/lib/main.dart +++ b/frontend/mobile-app/lib/main.dart @@ -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 createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - 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()); } diff --git a/frontend/mobile-app/lib/routes/app_router.dart b/frontend/mobile-app/lib/routes/app_router.dart new file mode 100644 index 00000000..f56b6f09 --- /dev/null +++ b/frontend/mobile-app/lib/routes/app_router.dart @@ -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(); +final _shellNavigatorKey = GlobalKey(); + +/// 备份助记词页面参数 +class BackupMnemonicParams { + final List 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((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(), + ), + ), + ], + ), + ], + ); +}); diff --git a/frontend/mobile-app/lib/routes/route_names.dart b/frontend/mobile-app/lib/routes/route_names.dart new file mode 100644 index 00000000..8dab1b8e --- /dev/null +++ b/frontend/mobile-app/lib/routes/route_names.dart @@ -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'; +} diff --git a/frontend/mobile-app/lib/routes/route_paths.dart b/frontend/mobile-app/lib/routes/route_paths.dart new file mode 100644 index 00000000..76012fe8 --- /dev/null +++ b/frontend/mobile-app/lib/routes/route_paths.dart @@ -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'; +} diff --git a/frontend/mobile-app/linux/flutter/generated_plugin_registrant.cc b/frontend/mobile-app/linux/flutter/generated_plugin_registrant.cc index e71a16d2..3ccd5513 100644 --- a/frontend/mobile-app/linux/flutter/generated_plugin_registrant.cc +++ b/frontend/mobile-app/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,18 @@ #include "generated_plugin_registrant.h" +#include +#include +#include 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); } diff --git a/frontend/mobile-app/linux/flutter/generated_plugins.cmake b/frontend/mobile-app/linux/flutter/generated_plugins.cmake index 2e1de87a..9ce94c49 100644 --- a/frontend/mobile-app/linux/flutter/generated_plugins.cmake +++ b/frontend/mobile-app/linux/flutter/generated_plugins.cmake @@ -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 diff --git a/frontend/mobile-app/macos/Flutter/GeneratedPluginRegistrant.swift b/frontend/mobile-app/macos/Flutter/GeneratedPluginRegistrant.swift index cccf817a..e494aff0 100644 --- a/frontend/mobile-app/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/frontend/mobile-app/macos/Flutter/GeneratedPluginRegistrant.swift @@ -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")) } diff --git a/frontend/mobile-app/pubspec.lock b/frontend/mobile-app/pubspec.lock index 74ff1d29..37551d7a 100644 --- a/frontend/mobile-app/pubspec.lock +++ b/frontend/mobile-app/pubspec.lock @@ -1,6 +1,46 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" + url: "https://pub.dev" + source: hosted + version: "67.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" + url: "https://pub.dev" + source: hosted + version: "6.4.1" + analyzer_plugin: + dependency: transitive + description: + name: analyzer_plugin + sha256: "9661b30b13a685efaee9f02e5d01ed9f2b423bd889d28a304d02d704aee69161" + url: "https://pub.dev" + source: hosted + version: "0.11.3" + archive: + dependency: transitive + description: + name: archive + sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" + url: "https://pub.dev" + source: hosted + version: "4.0.7" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" async: dependency: transitive description: @@ -9,6 +49,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.13.0" + bip39: + dependency: "direct main" + description: + name: bip39 + sha256: de1ee27ebe7d96b84bb3a04a4132a0a3007dcdd5ad27dd14aa87a29d97c45edc + url: "https://pub.dev" + source: hosted + version: "1.0.6" boolean_selector: dependency: transitive description: @@ -17,6 +65,94 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + build: + dependency: transitive + description: + name: build + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + build_config: + dependency: transitive + description: + name: build_config + sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957 + url: "https://pub.dev" + source: hosted + version: "4.1.1" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" + url: "https://pub.dev" + source: hosted + version: "2.4.13" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 + url: "https://pub.dev" + source: hosted + version: "7.3.2" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: "426cf75afdb23aa74bd4e471704de3f9393f3c7b04c1e2d9c6f1073ae0b8b139" + url: "https://pub.dev" + source: hosted + version: "8.12.1" + cached_network_image: + dependency: "direct main" + description: + name: cached_network_image + sha256: "4a5d8d2c728b0f3d0245f69f921d7be90cae4c2fd5288f773088672c0893f819" + url: "https://pub.dev" + source: hosted + version: "3.4.0" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" + url: "https://pub.dev" + source: hosted + version: "4.1.1" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + sha256: "6322dde7a5ad92202e64df659241104a43db20ed594c41ca18de1014598d7996" + url: "https://pub.dev" + source: hosted + version: "1.3.0" characters: dependency: transitive description: @@ -25,6 +161,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.dev" + source: hosted + version: "2.0.4" clock: dependency: transitive description: @@ -33,6 +177,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: "11654819532ba94c34de52ff5feb52bd81cba1de00ef2ed622fd50295f9d4243" + url: "https://pub.dev" + source: hosted + version: "4.11.0" collection: dependency: transitive description: @@ -41,6 +193,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + connectivity_plus: + dependency: "direct main" + description: + name: connectivity_plus + sha256: b5e72753cf63becce2c61fd04dfe0f1c430cc5278b53a1342dc5ad839eab29ec + url: "https://pub.dev" + source: hosted + version: "6.1.5" + connectivity_plus_platform_interface: + dependency: transitive + description: + name: connectivity_plus_platform_interface + sha256: "42657c1715d48b167930d5f34d00222ac100475f73d10162ddf43e714932f204" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "701dcfc06da0882883a2657c445103380e53e647060ad8d9dfb710c100996608" + url: "https://pub.dev" + source: hosted + version: "0.3.5+1" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" cupertino_icons: dependency: "direct main" description: @@ -49,6 +241,86 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + custom_lint_core: + dependency: transitive + description: + name: custom_lint_core + sha256: a85e8f78f4c52f6c63cdaf8c872eb573db0231dcdf3c3a5906d493c1f8bc20e6 + url: "https://pub.dev" + source: hosted + version: "0.6.3" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" + url: "https://pub.dev" + source: hosted + version: "2.3.6" + dartz: + dependency: "direct main" + description: + name: dartz + sha256: e6acf34ad2e31b1eb00948692468c30ab48ac8250e0f0df661e29f12dd252168 + url: "https://pub.dev" + source: hosted + version: "0.10.1" + dbus: + dependency: transitive + description: + name: dbus + sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" + url: "https://pub.dev" + source: hosted + version: "0.7.11" + dio: + dependency: "direct main" + description: + name: dio + sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9 + url: "https://pub.dev" + source: hosted + version: "5.9.0" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + ed25519_hd_key: + dependency: "direct main" + description: + name: ed25519_hd_key + sha256: "31e191ec97492873067e46dc9cc0c7d55170559c83a478400feffa0627acaccf" + url: "https://pub.dev" + source: hosted + version: "2.3.0" + eip1559: + dependency: transitive + description: + name: eip1559 + sha256: c2b81ac85f3e0e71aaf558201dd9a4600f051ece7ebacd0c5d70065c9b458004 + url: "https://pub.dev" + source: hosted + version: "0.6.2" + eip55: + dependency: transitive + description: + name: eip55 + sha256: "213a9b86add87a5216328e8494b0ab836e401210c4d55eb5e521bd39e39169e1" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + equatable: + dependency: "direct main" + description: + name: equatable + sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" + url: "https://pub.dev" + source: hosted + version: "2.0.7" fake_async: dependency: transitive description: @@ -57,11 +329,75 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0" + url: "https://pub.dev" + source: hosted + version: "0.9.4" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a" + url: "https://pub.dev" + source: hosted + version: "0.9.5" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85" + url: "https://pub.dev" + source: hosted + version: "2.7.0" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd" + url: "https://pub.dev" + source: hosted + version: "0.9.3+5" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" + url: "https://pub.dev" + source: hosted + version: "3.4.1" flutter_lints: dependency: "direct dev" description: @@ -70,11 +406,317 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.0" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: ee8068e0e1cd16c4a82714119918efdeed33b3ba7772c54b5d094ab53f9b7fd1 + url: "https://pub.dev" + source: hosted + version: "2.0.33" + flutter_riverpod: + dependency: "direct main" + description: + name: flutter_riverpod + sha256: "9532ee6db4a943a1ed8383072a2e3eeda041db5657cdf6d2acecf3c21ecbe7e1" + url: "https://pub.dev" + source: hosted + version: "2.6.1" + flutter_screenutil: + dependency: "direct main" + description: + name: flutter_screenutil + sha256: "8239210dd68bee6b0577aa4a090890342d04a136ce1c81f98ee513fc0ce891de" + url: "https://pub.dev" + source: hosted + version: "5.9.3" + flutter_secure_storage: + dependency: "direct main" + description: + name: flutter_secure_storage + sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea" + url: "https://pub.dev" + source: hosted + version: "9.2.4" + flutter_secure_storage_linux: + dependency: transitive + description: + name: flutter_secure_storage_linux + sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688 + url: "https://pub.dev" + source: hosted + version: "1.2.3" + flutter_secure_storage_macos: + dependency: transitive + description: + name: flutter_secure_storage_macos + sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247" + url: "https://pub.dev" + source: hosted + version: "3.1.3" + flutter_secure_storage_platform_interface: + dependency: transitive + description: + name: flutter_secure_storage_platform_interface + sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_secure_storage_web: + dependency: transitive + description: + name: flutter_secure_storage_web + sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + flutter_secure_storage_windows: + dependency: transitive + description: + name: flutter_secure_storage_windows + sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + sha256: "87fbd7c534435b6c5d9d98b01e1fd527812b82e68ddd8bd35fc45ed0fa8f0a95" + url: "https://pub.dev" + source: hosted + version: "2.2.3" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + freezed: + dependency: "direct dev" + description: + name: freezed + sha256: a434911f643466d78462625df76fd9eb13e57348ff43fe1f77bbe909522c67a1 + url: "https://pub.dev" + source: hosted + version: "2.5.2" + freezed_annotation: + dependency: "direct dev" + description: + name: freezed_annotation + sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 + url: "https://pub.dev" + source: hosted + version: "2.4.4" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + go_router: + dependency: "direct main" + description: + name: go_router + sha256: f02fd7d2a4dc512fec615529824fdd217fecb3a3d3de68360293a551f21634b3 + url: "https://pub.dev" + source: hosted + version: "14.8.1" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + hex: + dependency: "direct main" + description: + name: hex + sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + hive: + dependency: "direct main" + description: + name: hive + sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" + url: "https://pub.dev" + source: hosted + version: "2.2.3" + hive_flutter: + dependency: "direct main" + description: + name: hive_flutter + sha256: dca1da446b1d808a51689fb5d0c6c9510c0a2ba01e22805d492c73b68e33eecc + url: "https://pub.dev" + source: hosted + version: "1.1.0" + hive_generator: + dependency: "direct dev" + description: + name: hive_generator + sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + http: + dependency: transitive + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.dev" + source: hosted + version: "3.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "784210112be18ea55f69d7076e2c656a4e24949fa9e76429fe53af0c0f4fa320" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: "5e9bf126c37c117cf8094215373c6d561117a3cfb50ebc5add1a61dc6e224677" + url: "https://pub.dev" + source: hosted + version: "0.8.13+10" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "66257a3191ab360d23a55c8241c91a6e329d31e94efa7be9cf7a212e65850214" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: "997d100ce1dda5b1ba4085194c5e36c9f8a1fb7987f6a36ab677a344cd2dc986" + url: "https://pub.dev" + source: hosted + version: "0.8.13+2" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4" + url: "https://pub.dev" + source: hosted + version: "0.2.2" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91" + url: "https://pub.dev" + source: hosted + version: "0.2.2+1" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c" + url: "https://pub.dev" + source: hosted + version: "2.11.1" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae + url: "https://pub.dev" + source: hosted + version: "0.2.2" + intl: + dependency: "direct main" + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.dev" + source: hosted + version: "0.20.2" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.dev" + source: hosted + version: "1.0.5" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + json_rpc_2: + dependency: transitive + description: + name: json_rpc_2 + sha256: "246b321532f0e8e2ba474b4d757eaa558ae4fdd0688fdbc1e1ca9705f9b8ca0e" + url: "https://pub.dev" + source: hosted + version: "3.0.3" + json_serializable: + dependency: "direct dev" + description: + name: json_serializable + sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b + url: "https://pub.dev" + source: hosted + version: "6.8.0" leak_tracker: dependency: transitive description: @@ -107,6 +749,70 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.0" + local_auth: + dependency: "direct main" + description: + name: local_auth + sha256: "434d854cf478f17f12ab29a76a02b3067f86a63a6d6c4eb8fbfdcfe4879c1b7b" + url: "https://pub.dev" + source: hosted + version: "2.3.0" + local_auth_android: + dependency: transitive + description: + name: local_auth_android + sha256: a0bdfcc0607050a26ef5b31d6b4b254581c3d3ce3c1816ab4d4f4a9173e84467 + url: "https://pub.dev" + source: hosted + version: "1.0.56" + local_auth_darwin: + dependency: transitive + description: + name: local_auth_darwin + sha256: "699873970067a40ef2f2c09b4c72eb1cfef64224ef041b3df9fdc5c4c1f91f49" + url: "https://pub.dev" + source: hosted + version: "1.6.1" + local_auth_platform_interface: + dependency: transitive + description: + name: local_auth_platform_interface + sha256: f98b8e388588583d3f781f6806e4f4c9f9e189d898d27f0c249b93a1973dd122 + url: "https://pub.dev" + source: hosted + version: "1.1.0" + local_auth_windows: + dependency: transitive + description: + name: local_auth_windows + sha256: bc4e66a29b0fdf751aafbec923b5bed7ad6ed3614875d8151afe2578520b2ab5 + url: "https://pub.dev" + source: hosted + version: "1.0.11" + logger: + dependency: "direct main" + description: + name: logger + sha256: a7967e31b703831a893bbc3c3dd11db08126fe5f369b5c648a36f821979f5be3 + url: "https://pub.dev" + source: hosted + version: "2.6.2" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + lottie: + dependency: "direct main" + description: + name: lottie + sha256: "8ae0be46dbd9e19641791dc12ee480d34e1fd3f84c749adc05f3ad9342b71b95" + url: "https://pub.dev" + source: hosted + version: "3.3.2" matcher: dependency: transitive description: @@ -131,6 +837,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.17.0" + mime: + dependency: transitive + description: + name: mime + sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" + url: "https://pub.dev" + source: hosted + version: "1.0.6" + mocktail: + dependency: "direct dev" + description: + name: mocktail + sha256: "890df3f9688106f25755f26b1c60589a92b3ab91a22b8b224947ad041bf172d8" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + nm: + dependency: transitive + description: + name: nm + sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + octo_image: + dependency: transitive + description: + name: octo_image + sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" path: dependency: transitive description: @@ -139,11 +885,363 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e + url: "https://pub.dev" + source: hosted + version: "2.2.22" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "97390a0719146c7c3e71b6866c34f1cde92685933165c1c671984390d2aca776" + url: "https://pub.dev" + source: hosted + version: "2.4.4" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + sha256: "59adad729136f01ea9e35a48f5d1395e25cba6cea552249ddbe9cf950f5d7849" + url: "https://pub.dev" + source: hosted + version: "11.4.0" + permission_handler_android: + dependency: transitive + description: + name: permission_handler_android + sha256: d3971dcdd76182a0c198c096b5db2f0884b0d4196723d21a866fc4cdea057ebc + url: "https://pub.dev" + source: hosted + version: "12.1.0" + permission_handler_apple: + dependency: transitive + description: + name: permission_handler_apple + sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023 + url: "https://pub.dev" + source: hosted + version: "9.4.7" + permission_handler_html: + dependency: transitive + description: + name: permission_handler_html + sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" + url: "https://pub.dev" + source: hosted + version: "0.1.3+5" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878 + url: "https://pub.dev" + source: hosted + version: "4.3.0" + permission_handler_windows: + dependency: transitive + description: + name: permission_handler_windows + sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" + url: "https://pub.dev" + source: hosted + version: "0.2.1" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" + url: "https://pub.dev" + source: hosted + version: "7.0.1" + pinenacl: + dependency: transitive + description: + name: pinenacl + sha256: "57e907beaacbc3c024a098910b6240758e899674de07d6949a67b52fd984cbdf" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe" + url: "https://pub.dev" + source: hosted + version: "3.9.1" + pool: + dependency: transitive + description: + name: pool + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" + url: "https://pub.dev" + source: hosted + version: "1.5.2" + posix: + dependency: transitive + description: + name: posix + sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61" + url: "https://pub.dev" + source: hosted + version: "6.0.3" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + qr: + dependency: transitive + description: + name: qr + sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + qr_flutter: + dependency: "direct main" + description: + name: qr_flutter + sha256: "5095f0fc6e3f71d08adef8feccc8cea4f12eec18a2e31c2e8d82cb6019f4b097" + url: "https://pub.dev" + source: hosted + version: "4.1.0" + riverpod: + dependency: transitive + description: + name: riverpod + sha256: "59062512288d3056b2321804332a13ffdd1bf16df70dcc8e506e411280a72959" + url: "https://pub.dev" + source: hosted + version: "2.6.1" + riverpod_analyzer_utils: + dependency: transitive + description: + name: riverpod_analyzer_utils + sha256: "8b71f03fc47ae27d13769496a1746332df4cec43918aeba9aff1e232783a780f" + url: "https://pub.dev" + source: hosted + version: "0.5.1" + riverpod_annotation: + dependency: "direct main" + description: + name: riverpod_annotation + sha256: e14b0bf45b71326654e2705d462f21b958f987087be850afd60578fcd502d1b8 + url: "https://pub.dev" + source: hosted + version: "2.6.1" + riverpod_generator: + dependency: "direct dev" + description: + name: riverpod_generator + sha256: d451608bf17a372025fc36058863737636625dfdb7e3cbf6142e0dfeb366ab22 + url: "https://pub.dev" + source: hosted + version: "2.4.0" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" + url: "https://pub.dev" + source: hosted + version: "0.28.0" + sec: + dependency: transitive + description: + name: sec + sha256: "8bbd56df884502192a441b5f5d667265498f2f8728a282beccd9db79e215f379" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + share_plus: + dependency: "direct main" + description: + name: share_plus + sha256: fb5319f3aab4c5dda5ebb92dca978179ba21f8c783ee4380910ef4c1c6824f51 + url: "https://pub.dev" + source: hosted + version: "8.0.3" + share_plus_platform_interface: + dependency: transitive + description: + name: share_plus_platform_interface + sha256: "251eb156a8b5fa9ce033747d73535bf53911071f8d3b6f4f0b578505ce0d4496" + url: "https://pub.dev" + source: hosted + version: "3.4.0" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + url: "https://pub.dev" + source: hosted + version: "2.5.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "46a46fd64659eff15f4638bbe19de43f9483f0e0bf024a9fb6b3582064bacc7b" + url: "https://pub.dev" + source: hosted + version: "2.4.17" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + shimmer: + dependency: "direct main" + description: + name: shimmer + sha256: "5f88c883a22e9f9f299e5ba0e4f7e6054857224976a5d9f839d4ebdc94a14ac9" + url: "https://pub.dev" + source: hosted + version: "3.0.0" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.0" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + source_helper: + dependency: transitive + description: + name: source_helper + sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c" + url: "https://pub.dev" + source: hosted + version: "1.3.5" source_span: dependency: transitive description: @@ -152,6 +1250,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.1" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: ecd684501ebc2ae9a83536e8b15731642b9570dc8623e0073d227d0ee2bfea88 + url: "https://pub.dev" + source: hosted + version: "2.4.2+2" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" stack_trace: dependency: transitive description: @@ -160,6 +1298,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.12.1" + state_notifier: + dependency: transitive + description: + name: state_notifier + sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb + url: "https://pub.dev" + source: hosted + version: "1.0.0" stream_channel: dependency: transitive description: @@ -168,6 +1314,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + url: "https://pub.dev" + source: hosted + version: "2.1.1" string_scanner: dependency: transitive description: @@ -176,6 +1330,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 + url: "https://pub.dev" + source: hosted + version: "3.4.0" term_glyph: dependency: transitive description: @@ -192,6 +1354,118 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.7" + timing: + dependency: transitive + description: + name: timing + sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 + url: "https://pub.dev" + source: hosted + version: "6.3.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611" + url: "https://pub.dev" + source: hosted + version: "6.3.28" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: cfde38aa257dae62ffe79c87fab20165dfdf6988c1d31b58ebf59b9106062aad + url: "https://pub.dev" + source: hosted + version: "6.3.6" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" + url: "https://pub.dev" + source: hosted + version: "3.2.5" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" + url: "https://pub.dev" + source: hosted + version: "3.1.5" + uuid: + dependency: "direct main" + description: + name: uuid + sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8 + url: "https://pub.dev" + source: hosted + version: "4.5.2" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6 + url: "https://pub.dev" + source: hosted + version: "1.1.19" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" + url: "https://pub.dev" + source: hosted + version: "1.1.13" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc + url: "https://pub.dev" + source: hosted + version: "1.1.19" vector_math: dependency: transitive description: @@ -208,6 +1482,86 @@ packages: url: "https://pub.dev" source: hosted version: "15.0.2" + wallet: + dependency: transitive + description: + name: wallet + sha256: "687fd89a16557649b26189e597792962f405797fc64113e8758eabc2c2605c32" + url: "https://pub.dev" + source: hosted + version: "0.0.13" + watcher: + dependency: transitive + description: + name: watcher + sha256: "592ab6e2892f67760543fb712ff0177f4ec76c031f02f5b4ff8d3fc5eb9fb61a" + url: "https://pub.dev" + source: hosted + version: "1.1.4" + web: + dependency: transitive + description: + name: web + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + url: "https://pub.dev" + source: hosted + version: "0.5.1" + web3dart: + dependency: "direct main" + description: + name: web3dart + sha256: "885e5e8f0cc3c87c09f160a7fce6279226ca41316806f7ece2001959c62ecced" + url: "https://pub.dev" + source: hosted + version: "2.7.3" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" + win32: + dependency: transitive + description: + name: win32 + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + url: "https://pub.dev" + source: hosted + version: "5.15.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + url: "https://pub.dev" + source: hosted + version: "6.6.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" sdks: dart: ">=3.10.1 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + flutter: ">=3.35.0" diff --git a/frontend/mobile-app/pubspec.yaml b/frontend/mobile-app/pubspec.yaml index 85e9d0b5..154542ea 100644 --- a/frontend/mobile-app/pubspec.yaml +++ b/frontend/mobile-app/pubspec.yaml @@ -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/ diff --git a/frontend/mobile-app/windows/flutter/generated_plugin_registrant.cc b/frontend/mobile-app/windows/flutter/generated_plugin_registrant.cc index 8b6d4680..4124c5b9 100644 --- a/frontend/mobile-app/windows/flutter/generated_plugin_registrant.cc +++ b/frontend/mobile-app/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,27 @@ #include "generated_plugin_registrant.h" +#include +#include +#include +#include +#include +#include +#include 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")); } diff --git a/frontend/mobile-app/windows/flutter/generated_plugins.cmake b/frontend/mobile-app/windows/flutter/generated_plugins.cmake index b93c4c30..4aa2827f 100644 --- a/frontend/mobile-app/windows/flutter/generated_plugins.cmake +++ b/frontend/mobile-app/windows/flutter/generated_plugins.cmake @@ -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