feat(mining-app): integrate real APIs for Asset and Profile pages
- Asset page now uses trading-service /asset/my endpoint - Profile page integrates auth-service /user/profile and contribution-service - Add new entities: AssetDisplay, PriceInfo, MarketOverview, TradingAccount - Add corresponding models with JSON parsing - Create asset_providers and profile_providers for state management - Update trading_providers with real API integration - Extend UserState and UserInfo with additional profile fields - Remove obsolete buy_shares and sell_shares use cases - Fix compilation errors in get_current_price and trading_page Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
106a287260
commit
6bcb4af028
|
|
@ -15,9 +15,6 @@ import '../../domain/repositories/trading_repository.dart';
|
||||||
import '../../domain/repositories/contribution_repository.dart';
|
import '../../domain/repositories/contribution_repository.dart';
|
||||||
import '../../domain/usecases/mining/get_share_account.dart';
|
import '../../domain/usecases/mining/get_share_account.dart';
|
||||||
import '../../domain/usecases/mining/get_global_state.dart';
|
import '../../domain/usecases/mining/get_global_state.dart';
|
||||||
import '../../domain/usecases/trading/get_current_price.dart';
|
|
||||||
import '../../domain/usecases/trading/sell_shares.dart';
|
|
||||||
import '../../domain/usecases/trading/buy_shares.dart';
|
|
||||||
import '../../domain/usecases/contribution/get_user_contribution.dart';
|
import '../../domain/usecases/contribution/get_user_contribution.dart';
|
||||||
|
|
||||||
final getIt = GetIt.instance;
|
final getIt = GetIt.instance;
|
||||||
|
|
@ -72,11 +69,6 @@ Future<void> configureDependencies() async {
|
||||||
getIt.registerLazySingleton(() => GetShareAccount(getIt<MiningRepository>()));
|
getIt.registerLazySingleton(() => GetShareAccount(getIt<MiningRepository>()));
|
||||||
getIt.registerLazySingleton(() => GetGlobalState(getIt<MiningRepository>()));
|
getIt.registerLazySingleton(() => GetGlobalState(getIt<MiningRepository>()));
|
||||||
|
|
||||||
// Use Cases - Trading
|
|
||||||
getIt.registerLazySingleton(() => GetCurrentPrice(getIt<TradingRepository>()));
|
|
||||||
getIt.registerLazySingleton(() => SellShares(getIt<TradingRepository>()));
|
|
||||||
getIt.registerLazySingleton(() => BuyShares(getIt<TradingRepository>()));
|
|
||||||
|
|
||||||
// Use Cases - Contribution
|
// Use Cases - Contribution
|
||||||
getIt.registerLazySingleton(() => GetUserContribution(getIt<ContributionRepository>()));
|
getIt.registerLazySingleton(() => GetUserContribution(getIt<ContributionRepository>()));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,16 +23,31 @@ class ApiEndpoints {
|
||||||
'/api/v2/mining/accounts/$accountSequence/realtime';
|
'/api/v2/mining/accounts/$accountSequence/realtime';
|
||||||
|
|
||||||
// Trading Service 2.0 (Kong路由: /api/v2/trading)
|
// Trading Service 2.0 (Kong路由: /api/v2/trading)
|
||||||
static const String currentPrice = '/api/v2/trading/price';
|
// Price endpoints
|
||||||
static const String klineData = '/api/v2/trading/kline';
|
static const String currentPrice = '/api/v2/trading/price/current';
|
||||||
|
static const String latestPrice = '/api/v2/trading/price/latest';
|
||||||
|
static const String priceHistory = '/api/v2/trading/price/history';
|
||||||
|
|
||||||
|
// Trading account endpoints
|
||||||
static String tradingAccount(String accountSequence) =>
|
static String tradingAccount(String accountSequence) =>
|
||||||
'/api/v2/trading/accounts/$accountSequence';
|
'/api/v2/trading/trading/accounts/$accountSequence';
|
||||||
static String createOrder(String accountSequence) =>
|
static const String orderBook = '/api/v2/trading/trading/orderbook';
|
||||||
'/api/v2/trading/accounts/$accountSequence/orders';
|
static const String createOrder = '/api/v2/trading/trading/orders';
|
||||||
static String orders(String accountSequence) =>
|
static const String orders = '/api/v2/trading/trading/orders';
|
||||||
'/api/v2/trading/accounts/$accountSequence/orders';
|
static String cancelOrder(String orderNo) =>
|
||||||
static String transfer(String accountSequence) =>
|
'/api/v2/trading/trading/orders/$orderNo/cancel';
|
||||||
'/api/v2/trading/accounts/$accountSequence/transfer';
|
|
||||||
|
// Asset endpoints
|
||||||
|
static const String myAsset = '/api/v2/trading/asset/my';
|
||||||
|
static String accountAsset(String accountSequence) =>
|
||||||
|
'/api/v2/trading/asset/account/$accountSequence';
|
||||||
|
static const String estimateSell = '/api/v2/trading/asset/estimate-sell';
|
||||||
|
static const String marketOverview = '/api/v2/trading/asset/market';
|
||||||
|
|
||||||
|
// Transfer endpoints
|
||||||
|
static const String transferIn = '/api/v2/trading/transfers/in';
|
||||||
|
static const String transferOut = '/api/v2/trading/transfers/out';
|
||||||
|
static const String transferHistory = '/api/v2/trading/transfers/history';
|
||||||
|
|
||||||
// Contribution Service 2.0 (Kong路由: /api/v2/contribution -> /api/v1/contributions)
|
// Contribution Service 2.0 (Kong路由: /api/v2/contribution -> /api/v1/contributions)
|
||||||
static String contribution(String accountSequence) =>
|
static String contribution(String accountSequence) =>
|
||||||
|
|
|
||||||
|
|
@ -30,12 +30,20 @@ class UserInfo {
|
||||||
final String phone;
|
final String phone;
|
||||||
final String source;
|
final String source;
|
||||||
final String kycStatus;
|
final String kycStatus;
|
||||||
|
final String? status;
|
||||||
|
final String? realName;
|
||||||
|
final DateTime? createdAt;
|
||||||
|
final DateTime? lastLoginAt;
|
||||||
|
|
||||||
UserInfo({
|
UserInfo({
|
||||||
required this.accountSequence,
|
required this.accountSequence,
|
||||||
required this.phone,
|
required this.phone,
|
||||||
required this.source,
|
required this.source,
|
||||||
required this.kycStatus,
|
required this.kycStatus,
|
||||||
|
this.status,
|
||||||
|
this.realName,
|
||||||
|
this.createdAt,
|
||||||
|
this.lastLoginAt,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory UserInfo.fromJson(Map<String, dynamic> json) {
|
factory UserInfo.fromJson(Map<String, dynamic> json) {
|
||||||
|
|
@ -44,8 +52,18 @@ class UserInfo {
|
||||||
phone: json['phone'] as String,
|
phone: json['phone'] as String,
|
||||||
source: json['source'] as String,
|
source: json['source'] as String,
|
||||||
kycStatus: json['kycStatus'] as String,
|
kycStatus: json['kycStatus'] as String,
|
||||||
|
status: json['status'] as String?,
|
||||||
|
realName: json['realName'] as String?,
|
||||||
|
createdAt: json['createdAt'] != null
|
||||||
|
? DateTime.parse(json['createdAt'].toString())
|
||||||
|
: null,
|
||||||
|
lastLoginAt: json['lastLoginAt'] != null
|
||||||
|
? DateTime.parse(json['lastLoginAt'].toString())
|
||||||
|
: null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool get isKycVerified => kycStatus == 'VERIFIED';
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class AuthRemoteDataSource {
|
abstract class AuthRemoteDataSource {
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,52 @@
|
||||||
import '../../models/trade_order_model.dart';
|
import '../../models/trade_order_model.dart';
|
||||||
import '../../models/kline_model.dart';
|
import '../../models/price_info_model.dart';
|
||||||
|
import '../../models/trading_account_model.dart';
|
||||||
|
import '../../models/market_overview_model.dart';
|
||||||
|
import '../../models/asset_display_model.dart';
|
||||||
import '../../../core/network/api_client.dart';
|
import '../../../core/network/api_client.dart';
|
||||||
import '../../../core/network/api_endpoints.dart';
|
import '../../../core/network/api_endpoints.dart';
|
||||||
import '../../../core/error/exceptions.dart';
|
import '../../../core/error/exceptions.dart';
|
||||||
|
|
||||||
abstract class TradingRemoteDataSource {
|
abstract class TradingRemoteDataSource {
|
||||||
Future<String> getCurrentPrice();
|
/// 获取当前价格信息
|
||||||
Future<List<KlineModel>> getKlineData(String period);
|
Future<PriceInfoModel> getCurrentPrice();
|
||||||
Future<TradeOrderModel> buyShares({
|
|
||||||
required String accountSequence,
|
/// 获取市场概览
|
||||||
required String amount,
|
Future<MarketOverviewModel> getMarketOverview();
|
||||||
|
|
||||||
|
/// 获取交易账户信息
|
||||||
|
Future<TradingAccountModel> getTradingAccount(String accountSequence);
|
||||||
|
|
||||||
|
/// 创建订单
|
||||||
|
Future<Map<String, dynamic>> createOrder({
|
||||||
|
required String type,
|
||||||
|
required String price,
|
||||||
|
required String quantity,
|
||||||
});
|
});
|
||||||
Future<TradeOrderModel> sellShares({
|
|
||||||
required String accountSequence,
|
/// 取消订单
|
||||||
required String amount,
|
Future<void> cancelOrder(String orderNo);
|
||||||
});
|
|
||||||
Future<List<TradeOrderModel>> getOrders(
|
/// 获取用户订单列表
|
||||||
String accountSequence, {
|
Future<OrdersPageModel> getOrders({
|
||||||
int page = 1,
|
int page = 1,
|
||||||
int limit = 20,
|
int pageSize = 50,
|
||||||
});
|
|
||||||
Future<void> transfer({
|
|
||||||
required String accountSequence,
|
|
||||||
required String amount,
|
|
||||||
required String direction,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// 预估卖出收益
|
||||||
|
Future<Map<String, dynamic>> estimateSell(String quantity);
|
||||||
|
|
||||||
|
/// 划入积分股 (从挖矿账户到交易账户)
|
||||||
|
Future<Map<String, dynamic>> transferIn(String amount);
|
||||||
|
|
||||||
|
/// 划出积分股 (从交易账户到挖矿账户)
|
||||||
|
Future<Map<String, dynamic>> transferOut(String amount);
|
||||||
|
|
||||||
|
/// 获取我的资产显示信息
|
||||||
|
Future<AssetDisplayModel> getMyAsset({String? dailyAllocation});
|
||||||
|
|
||||||
|
/// 获取指定账户资产显示信息
|
||||||
|
Future<AssetDisplayModel> getAccountAsset(String accountSequence, {String? dailyAllocation});
|
||||||
}
|
}
|
||||||
|
|
||||||
class TradingRemoteDataSourceImpl implements TradingRemoteDataSource {
|
class TradingRemoteDataSourceImpl implements TradingRemoteDataSource {
|
||||||
|
|
@ -33,90 +55,149 @@ class TradingRemoteDataSourceImpl implements TradingRemoteDataSource {
|
||||||
TradingRemoteDataSourceImpl({required this.client});
|
TradingRemoteDataSourceImpl({required this.client});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String> getCurrentPrice() async {
|
Future<PriceInfoModel> getCurrentPrice() async {
|
||||||
try {
|
try {
|
||||||
final response = await client.get(ApiEndpoints.currentPrice);
|
final response = await client.get(ApiEndpoints.currentPrice);
|
||||||
return response.data['price']?.toString() ?? '0';
|
return PriceInfoModel.fromJson(response.data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw ServerException(e.toString());
|
throw ServerException(e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<KlineModel>> getKlineData(String period) async {
|
Future<MarketOverviewModel> getMarketOverview() async {
|
||||||
try {
|
try {
|
||||||
final response = await client.get(
|
final response = await client.get(ApiEndpoints.marketOverview);
|
||||||
ApiEndpoints.klineData,
|
return MarketOverviewModel.fromJson(response.data);
|
||||||
queryParameters: {'period': period},
|
|
||||||
);
|
|
||||||
final items = response.data as List? ?? [];
|
|
||||||
return items.map((json) => KlineModel.fromJson(json)).toList();
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw ServerException(e.toString());
|
throw ServerException(e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<TradeOrderModel> buyShares({
|
Future<TradingAccountModel> getTradingAccount(String accountSequence) async {
|
||||||
required String accountSequence,
|
try {
|
||||||
required String amount,
|
final response = await client.get(ApiEndpoints.tradingAccount(accountSequence));
|
||||||
|
return TradingAccountModel.fromJson(response.data);
|
||||||
|
} catch (e) {
|
||||||
|
throw ServerException(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Map<String, dynamic>> createOrder({
|
||||||
|
required String type,
|
||||||
|
required String price,
|
||||||
|
required String quantity,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final response = await client.post(
|
final response = await client.post(
|
||||||
ApiEndpoints.createOrder(accountSequence),
|
ApiEndpoints.createOrder,
|
||||||
data: {'orderType': 'BUY', 'quantity': amount},
|
data: {
|
||||||
|
'type': type,
|
||||||
|
'price': price,
|
||||||
|
'quantity': quantity,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
return TradeOrderModel.fromJson(response.data);
|
return response.data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw ServerException(e.toString());
|
throw ServerException(e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<TradeOrderModel> sellShares({
|
Future<void> cancelOrder(String orderNo) async {
|
||||||
required String accountSequence,
|
|
||||||
required String amount,
|
|
||||||
}) async {
|
|
||||||
try {
|
try {
|
||||||
final response = await client.post(
|
await client.post(ApiEndpoints.cancelOrder(orderNo));
|
||||||
ApiEndpoints.createOrder(accountSequence),
|
|
||||||
data: {'orderType': 'SELL', 'quantity': amount},
|
|
||||||
);
|
|
||||||
return TradeOrderModel.fromJson(response.data);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw ServerException(e.toString());
|
throw ServerException(e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<TradeOrderModel>> getOrders(
|
Future<OrdersPageModel> getOrders({
|
||||||
String accountSequence, {
|
|
||||||
int page = 1,
|
int page = 1,
|
||||||
int limit = 20,
|
int pageSize = 50,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final response = await client.get(
|
final response = await client.get(
|
||||||
ApiEndpoints.orders(accountSequence),
|
ApiEndpoints.orders,
|
||||||
queryParameters: {'page': page, 'pageSize': limit},
|
queryParameters: {'page': page, 'pageSize': pageSize},
|
||||||
);
|
);
|
||||||
final items = response.data['items'] as List? ?? [];
|
return OrdersPageModel.fromJson(response.data);
|
||||||
return items.map((json) => TradeOrderModel.fromJson(json)).toList();
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw ServerException(e.toString());
|
throw ServerException(e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> transfer({
|
Future<Map<String, dynamic>> estimateSell(String quantity) async {
|
||||||
required String accountSequence,
|
|
||||||
required String amount,
|
|
||||||
required String direction,
|
|
||||||
}) async {
|
|
||||||
try {
|
try {
|
||||||
await client.post(
|
final response = await client.get(
|
||||||
ApiEndpoints.transfer(accountSequence),
|
ApiEndpoints.estimateSell,
|
||||||
data: {'amount': amount, 'direction': direction},
|
queryParameters: {'quantity': quantity},
|
||||||
);
|
);
|
||||||
|
return response.data;
|
||||||
|
} catch (e) {
|
||||||
|
throw ServerException(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Map<String, dynamic>> transferIn(String amount) async {
|
||||||
|
try {
|
||||||
|
final response = await client.post(
|
||||||
|
ApiEndpoints.transferIn,
|
||||||
|
data: {'amount': amount},
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
} catch (e) {
|
||||||
|
throw ServerException(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Map<String, dynamic>> transferOut(String amount) async {
|
||||||
|
try {
|
||||||
|
final response = await client.post(
|
||||||
|
ApiEndpoints.transferOut,
|
||||||
|
data: {'amount': amount},
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
} catch (e) {
|
||||||
|
throw ServerException(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<AssetDisplayModel> getMyAsset({String? dailyAllocation}) async {
|
||||||
|
try {
|
||||||
|
final queryParams = <String, dynamic>{};
|
||||||
|
if (dailyAllocation != null) {
|
||||||
|
queryParams['dailyAllocation'] = dailyAllocation;
|
||||||
|
}
|
||||||
|
final response = await client.get(
|
||||||
|
ApiEndpoints.myAsset,
|
||||||
|
queryParameters: queryParams.isNotEmpty ? queryParams : null,
|
||||||
|
);
|
||||||
|
return AssetDisplayModel.fromJson(response.data);
|
||||||
|
} catch (e) {
|
||||||
|
throw ServerException(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<AssetDisplayModel> getAccountAsset(String accountSequence, {String? dailyAllocation}) async {
|
||||||
|
try {
|
||||||
|
final queryParams = <String, dynamic>{};
|
||||||
|
if (dailyAllocation != null) {
|
||||||
|
queryParams['dailyAllocation'] = dailyAllocation;
|
||||||
|
}
|
||||||
|
final response = await client.get(
|
||||||
|
ApiEndpoints.accountAsset(accountSequence),
|
||||||
|
queryParameters: queryParams.isNotEmpty ? queryParams : null,
|
||||||
|
);
|
||||||
|
return AssetDisplayModel.fromJson(response.data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw ServerException(e.toString());
|
throw ServerException(e.toString());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
import '../../domain/entities/asset_display.dart';
|
||||||
|
|
||||||
|
class AssetDisplayModel extends AssetDisplay {
|
||||||
|
const AssetDisplayModel({
|
||||||
|
required super.shareBalance,
|
||||||
|
required super.cashBalance,
|
||||||
|
required super.frozenShares,
|
||||||
|
required super.frozenCash,
|
||||||
|
required super.availableShares,
|
||||||
|
required super.availableCash,
|
||||||
|
required super.currentPrice,
|
||||||
|
required super.burnMultiplier,
|
||||||
|
required super.effectiveShares,
|
||||||
|
required super.displayAssetValue,
|
||||||
|
required super.assetGrowthPerSecond,
|
||||||
|
required super.totalBought,
|
||||||
|
required super.totalSold,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory AssetDisplayModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
return AssetDisplayModel(
|
||||||
|
shareBalance: json['shareBalance']?.toString() ?? '0',
|
||||||
|
cashBalance: json['cashBalance']?.toString() ?? '0',
|
||||||
|
frozenShares: json['frozenShares']?.toString() ?? '0',
|
||||||
|
frozenCash: json['frozenCash']?.toString() ?? '0',
|
||||||
|
availableShares: json['availableShares']?.toString() ?? '0',
|
||||||
|
availableCash: json['availableCash']?.toString() ?? '0',
|
||||||
|
currentPrice: json['currentPrice']?.toString() ?? '0',
|
||||||
|
burnMultiplier: json['burnMultiplier']?.toString() ?? '0',
|
||||||
|
effectiveShares: json['effectiveShares']?.toString() ?? '0',
|
||||||
|
displayAssetValue: json['displayAssetValue']?.toString() ?? '0',
|
||||||
|
assetGrowthPerSecond: json['assetGrowthPerSecond']?.toString() ?? '0',
|
||||||
|
totalBought: json['totalBought']?.toString() ?? '0',
|
||||||
|
totalSold: json['totalSold']?.toString() ?? '0',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'shareBalance': shareBalance,
|
||||||
|
'cashBalance': cashBalance,
|
||||||
|
'frozenShares': frozenShares,
|
||||||
|
'frozenCash': frozenCash,
|
||||||
|
'availableShares': availableShares,
|
||||||
|
'availableCash': availableCash,
|
||||||
|
'currentPrice': currentPrice,
|
||||||
|
'burnMultiplier': burnMultiplier,
|
||||||
|
'effectiveShares': effectiveShares,
|
||||||
|
'displayAssetValue': displayAssetValue,
|
||||||
|
'assetGrowthPerSecond': assetGrowthPerSecond,
|
||||||
|
'totalBought': totalBought,
|
||||||
|
'totalSold': totalSold,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,32 +2,51 @@ import '../../domain/entities/contribution.dart';
|
||||||
|
|
||||||
class ContributionModel extends Contribution {
|
class ContributionModel extends Contribution {
|
||||||
const ContributionModel({
|
const ContributionModel({
|
||||||
|
required super.status,
|
||||||
|
required super.message,
|
||||||
required super.accountSequence,
|
required super.accountSequence,
|
||||||
required super.personalContribution,
|
required super.personalContribution,
|
||||||
required super.systemContribution,
|
|
||||||
required super.teamLevelContribution,
|
required super.teamLevelContribution,
|
||||||
required super.teamBonusContribution,
|
required super.teamBonusContribution,
|
||||||
required super.totalContribution,
|
required super.totalContribution,
|
||||||
required super.effectiveContribution,
|
|
||||||
required super.hasAdopted,
|
required super.hasAdopted,
|
||||||
required super.directReferralAdoptedCount,
|
required super.directReferralAdoptedCount,
|
||||||
required super.unlockedLevelDepth,
|
required super.unlockedLevelDepth,
|
||||||
required super.unlockedBonusTiers,
|
required super.unlockedBonusTiers,
|
||||||
|
required super.isCalculated,
|
||||||
|
super.lastCalculatedAt,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory ContributionModel.fromJson(Map<String, dynamic> json) {
|
factory ContributionModel.fromJson(Map<String, dynamic> json) {
|
||||||
return ContributionModel(
|
return ContributionModel(
|
||||||
|
status: _parseStatus(json['status']),
|
||||||
|
message: json['message']?.toString() ?? '',
|
||||||
accountSequence: json['accountSequence']?.toString() ?? '',
|
accountSequence: json['accountSequence']?.toString() ?? '',
|
||||||
personalContribution: json['personalContribution']?.toString() ?? '0',
|
personalContribution: json['personalContribution']?.toString() ?? '0',
|
||||||
systemContribution: json['systemContribution']?.toString() ?? '0',
|
|
||||||
teamLevelContribution: json['teamLevelContribution']?.toString() ?? '0',
|
teamLevelContribution: json['teamLevelContribution']?.toString() ?? '0',
|
||||||
teamBonusContribution: json['teamBonusContribution']?.toString() ?? '0',
|
teamBonusContribution: json['teamBonusContribution']?.toString() ?? '0',
|
||||||
totalContribution: json['totalContribution']?.toString() ?? '0',
|
totalContribution: json['totalContribution']?.toString() ?? '0',
|
||||||
effectiveContribution: json['effectiveContribution']?.toString() ?? '0',
|
|
||||||
hasAdopted: json['hasAdopted'] == true,
|
hasAdopted: json['hasAdopted'] == true,
|
||||||
directReferralAdoptedCount: json['directReferralAdoptedCount'] ?? 0,
|
directReferralAdoptedCount: json['directReferralAdoptedCount'] ?? 0,
|
||||||
unlockedLevelDepth: json['unlockedLevelDepth'] ?? 0,
|
unlockedLevelDepth: json['unlockedLevelDepth'] ?? 0,
|
||||||
unlockedBonusTiers: json['unlockedBonusTiers'] ?? 0,
|
unlockedBonusTiers: json['unlockedBonusTiers'] ?? 0,
|
||||||
|
isCalculated: json['isCalculated'] == true,
|
||||||
|
lastCalculatedAt: json['lastCalculatedAt'] != null
|
||||||
|
? DateTime.tryParse(json['lastCalculatedAt'].toString())
|
||||||
|
: null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static ContributionAccountStatus _parseStatus(String? status) {
|
||||||
|
switch (status) {
|
||||||
|
case 'ACTIVE':
|
||||||
|
return ContributionAccountStatus.active;
|
||||||
|
case 'INACTIVE':
|
||||||
|
return ContributionAccountStatus.inactive;
|
||||||
|
case 'USER_NOT_FOUND':
|
||||||
|
return ContributionAccountStatus.userNotFound;
|
||||||
|
default:
|
||||||
|
return ContributionAccountStatus.inactive;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
import '../../domain/entities/market_overview.dart';
|
||||||
|
|
||||||
|
class MarketOverviewModel extends MarketOverview {
|
||||||
|
const MarketOverviewModel({
|
||||||
|
required super.price,
|
||||||
|
required super.greenPoints,
|
||||||
|
required super.blackHoleAmount,
|
||||||
|
required super.circulationPool,
|
||||||
|
required super.effectiveDenominator,
|
||||||
|
required super.burnMultiplier,
|
||||||
|
required super.totalShares,
|
||||||
|
required super.burnTarget,
|
||||||
|
required super.burnProgress,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory MarketOverviewModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
return MarketOverviewModel(
|
||||||
|
price: json['price']?.toString() ?? '0',
|
||||||
|
greenPoints: json['greenPoints']?.toString() ?? '0',
|
||||||
|
blackHoleAmount: json['blackHoleAmount']?.toString() ?? '0',
|
||||||
|
circulationPool: json['circulationPool']?.toString() ?? '0',
|
||||||
|
effectiveDenominator: json['effectiveDenominator']?.toString() ?? '0',
|
||||||
|
burnMultiplier: json['burnMultiplier']?.toString() ?? '0',
|
||||||
|
totalShares: json['totalShares']?.toString() ?? '0',
|
||||||
|
burnTarget: json['burnTarget']?.toString() ?? '0',
|
||||||
|
burnProgress: json['burnProgress']?.toString() ?? '0',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
import '../../domain/entities/price_info.dart';
|
||||||
|
|
||||||
|
class PriceInfoModel extends PriceInfo {
|
||||||
|
const PriceInfoModel({
|
||||||
|
required super.price,
|
||||||
|
required super.greenPoints,
|
||||||
|
required super.blackHoleAmount,
|
||||||
|
required super.circulationPool,
|
||||||
|
required super.effectiveDenominator,
|
||||||
|
required super.burnMultiplier,
|
||||||
|
required super.minuteBurnRate,
|
||||||
|
required super.snapshotTime,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory PriceInfoModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
return PriceInfoModel(
|
||||||
|
price: json['price']?.toString() ?? '0',
|
||||||
|
greenPoints: json['greenPoints']?.toString() ?? '0',
|
||||||
|
blackHoleAmount: json['blackHoleAmount']?.toString() ?? '0',
|
||||||
|
circulationPool: json['circulationPool']?.toString() ?? '0',
|
||||||
|
effectiveDenominator: json['effectiveDenominator']?.toString() ?? '0',
|
||||||
|
burnMultiplier: json['burnMultiplier']?.toString() ?? '0',
|
||||||
|
minuteBurnRate: json['minuteBurnRate']?.toString() ?? '0',
|
||||||
|
snapshotTime: json['snapshotTime'] != null
|
||||||
|
? DateTime.parse(json['snapshotTime'].toString())
|
||||||
|
: DateTime.now(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,36 +3,55 @@ import '../../domain/entities/trade_order.dart';
|
||||||
class TradeOrderModel extends TradeOrder {
|
class TradeOrderModel extends TradeOrder {
|
||||||
const TradeOrderModel({
|
const TradeOrderModel({
|
||||||
required super.id,
|
required super.id,
|
||||||
required super.accountSequence,
|
required super.orderNo,
|
||||||
required super.orderType,
|
required super.orderType,
|
||||||
required super.status,
|
required super.status,
|
||||||
required super.price,
|
required super.price,
|
||||||
required super.quantity,
|
required super.quantity,
|
||||||
required super.filledQuantity,
|
required super.filledQuantity,
|
||||||
|
required super.remainingQuantity,
|
||||||
|
required super.averagePrice,
|
||||||
required super.totalAmount,
|
required super.totalAmount,
|
||||||
required super.createdAt,
|
required super.createdAt,
|
||||||
super.updatedAt,
|
super.completedAt,
|
||||||
|
super.cancelledAt,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory TradeOrderModel.fromJson(Map<String, dynamic> json) {
|
factory TradeOrderModel.fromJson(Map<String, dynamic> json) {
|
||||||
return TradeOrderModel(
|
return TradeOrderModel(
|
||||||
id: json['id'] ?? '',
|
id: json['id']?.toString() ?? '',
|
||||||
accountSequence: json['accountSequence']?.toString() ?? '',
|
orderNo: json['orderNo']?.toString() ?? '',
|
||||||
orderType: json['orderType'] == 'BUY' ? OrderType.buy : OrderType.sell,
|
orderType: _parseOrderType(json['type']),
|
||||||
status: _parseStatus(json['status']),
|
status: _parseStatus(json['status']),
|
||||||
price: json['price']?.toString() ?? '0',
|
price: json['price']?.toString() ?? '0',
|
||||||
quantity: json['quantity']?.toString() ?? '0',
|
quantity: json['quantity']?.toString() ?? '0',
|
||||||
filledQuantity: json['filledQuantity']?.toString() ?? '0',
|
filledQuantity: json['filledQuantity']?.toString() ?? '0',
|
||||||
|
remainingQuantity: json['remainingQuantity']?.toString() ?? '0',
|
||||||
|
averagePrice: json['averagePrice']?.toString() ?? '0',
|
||||||
totalAmount: json['totalAmount']?.toString() ?? '0',
|
totalAmount: json['totalAmount']?.toString() ?? '0',
|
||||||
createdAt: json['createdAt'] != null
|
createdAt: json['createdAt'] != null
|
||||||
? DateTime.parse(json['createdAt'])
|
? DateTime.parse(json['createdAt'].toString())
|
||||||
: DateTime.now(),
|
: DateTime.now(),
|
||||||
updatedAt: json['updatedAt'] != null
|
completedAt: json['completedAt'] != null
|
||||||
? DateTime.parse(json['updatedAt'])
|
? DateTime.parse(json['completedAt'].toString())
|
||||||
|
: null,
|
||||||
|
cancelledAt: json['cancelledAt'] != null
|
||||||
|
? DateTime.parse(json['cancelledAt'].toString())
|
||||||
: null,
|
: null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static OrderType _parseOrderType(String? type) {
|
||||||
|
switch (type) {
|
||||||
|
case 'BUY':
|
||||||
|
return OrderType.buy;
|
||||||
|
case 'SELL':
|
||||||
|
return OrderType.sell;
|
||||||
|
default:
|
||||||
|
return OrderType.buy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static OrderStatus _parseStatus(String? status) {
|
static OrderStatus _parseStatus(String? status) {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'PENDING':
|
case 'PENDING':
|
||||||
|
|
@ -48,3 +67,22 @@ class TradeOrderModel extends TradeOrder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 订单列表响应
|
||||||
|
class OrdersPageModel {
|
||||||
|
final List<TradeOrderModel> data;
|
||||||
|
final int total;
|
||||||
|
|
||||||
|
const OrdersPageModel({
|
||||||
|
required this.data,
|
||||||
|
required this.total,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory OrdersPageModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
final dataList = (json['data'] as List<dynamic>?) ?? [];
|
||||||
|
return OrdersPageModel(
|
||||||
|
data: dataList.map((e) => TradeOrderModel.fromJson(e)).toList(),
|
||||||
|
total: json['total'] ?? 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
import '../../domain/entities/trading_account.dart';
|
||||||
|
|
||||||
|
class TradingAccountModel extends TradingAccount {
|
||||||
|
const TradingAccountModel({
|
||||||
|
required super.accountSequence,
|
||||||
|
required super.shareBalance,
|
||||||
|
required super.cashBalance,
|
||||||
|
required super.availableShares,
|
||||||
|
required super.availableCash,
|
||||||
|
required super.frozenShares,
|
||||||
|
required super.frozenCash,
|
||||||
|
required super.totalBought,
|
||||||
|
required super.totalSold,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory TradingAccountModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
return TradingAccountModel(
|
||||||
|
accountSequence: json['accountSequence']?.toString() ?? '',
|
||||||
|
shareBalance: json['shareBalance']?.toString() ?? '0',
|
||||||
|
cashBalance: json['cashBalance']?.toString() ?? '0',
|
||||||
|
availableShares: json['availableShares']?.toString() ?? '0',
|
||||||
|
availableCash: json['availableCash']?.toString() ?? '0',
|
||||||
|
frozenShares: json['frozenShares']?.toString() ?? '0',
|
||||||
|
frozenCash: json['frozenCash']?.toString() ?? '0',
|
||||||
|
totalBought: json['totalBought']?.toString() ?? '0',
|
||||||
|
totalSold: json['totalSold']?.toString() ?? '0',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
import '../../domain/entities/trade_order.dart';
|
import '../../domain/entities/price_info.dart';
|
||||||
import '../../domain/entities/kline.dart';
|
import '../../domain/entities/market_overview.dart';
|
||||||
|
import '../../domain/entities/trading_account.dart';
|
||||||
|
import '../../domain/entities/asset_display.dart';
|
||||||
import '../../domain/repositories/trading_repository.dart';
|
import '../../domain/repositories/trading_repository.dart';
|
||||||
import '../../core/error/exceptions.dart';
|
import '../../core/error/exceptions.dart';
|
||||||
import '../../core/error/failures.dart';
|
import '../../core/error/failures.dart';
|
||||||
import '../datasources/remote/trading_remote_datasource.dart';
|
import '../datasources/remote/trading_remote_datasource.dart';
|
||||||
|
import '../models/trade_order_model.dart';
|
||||||
|
|
||||||
class TradingRepositoryImpl implements TradingRepository {
|
class TradingRepositoryImpl implements TradingRepository {
|
||||||
final TradingRemoteDataSource remoteDataSource;
|
final TradingRemoteDataSource remoteDataSource;
|
||||||
|
|
@ -12,7 +15,7 @@ class TradingRepositoryImpl implements TradingRepository {
|
||||||
TradingRepositoryImpl({required this.remoteDataSource});
|
TradingRepositoryImpl({required this.remoteDataSource});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Either<Failure, String>> getCurrentPrice() async {
|
Future<Either<Failure, PriceInfo>> getCurrentPrice() async {
|
||||||
try {
|
try {
|
||||||
final result = await remoteDataSource.getCurrentPrice();
|
final result = await remoteDataSource.getCurrentPrice();
|
||||||
return Right(result);
|
return Right(result);
|
||||||
|
|
@ -24,9 +27,9 @@ class TradingRepositoryImpl implements TradingRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Either<Failure, List<Kline>>> getKlineData(String period) async {
|
Future<Either<Failure, MarketOverview>> getMarketOverview() async {
|
||||||
try {
|
try {
|
||||||
final result = await remoteDataSource.getKlineData(period);
|
final result = await remoteDataSource.getMarketOverview();
|
||||||
return Right(result);
|
return Right(result);
|
||||||
} on ServerException catch (e) {
|
} on ServerException catch (e) {
|
||||||
return Left(ServerFailure(e.message));
|
return Left(ServerFailure(e.message));
|
||||||
|
|
@ -36,14 +39,28 @@ class TradingRepositoryImpl implements TradingRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Either<Failure, TradeOrder>> buyShares({
|
Future<Either<Failure, TradingAccount>> getTradingAccount(String accountSequence) async {
|
||||||
required String accountSequence,
|
try {
|
||||||
required String amount,
|
final result = await remoteDataSource.getTradingAccount(accountSequence);
|
||||||
|
return Right(result);
|
||||||
|
} on ServerException catch (e) {
|
||||||
|
return Left(ServerFailure(e.message));
|
||||||
|
} on NetworkException {
|
||||||
|
return Left(const NetworkFailure());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Either<Failure, Map<String, dynamic>>> createOrder({
|
||||||
|
required String type,
|
||||||
|
required String price,
|
||||||
|
required String quantity,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final result = await remoteDataSource.buyShares(
|
final result = await remoteDataSource.createOrder(
|
||||||
accountSequence: accountSequence,
|
type: type,
|
||||||
amount: amount,
|
price: price,
|
||||||
|
quantity: quantity,
|
||||||
);
|
);
|
||||||
return Right(result);
|
return Right(result);
|
||||||
} on ServerException catch (e) {
|
} on ServerException catch (e) {
|
||||||
|
|
@ -54,55 +71,9 @@ class TradingRepositoryImpl implements TradingRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Either<Failure, TradeOrder>> sellShares({
|
Future<Either<Failure, void>> cancelOrder(String orderNo) async {
|
||||||
required String accountSequence,
|
|
||||||
required String amount,
|
|
||||||
}) async {
|
|
||||||
try {
|
try {
|
||||||
final result = await remoteDataSource.sellShares(
|
await remoteDataSource.cancelOrder(orderNo);
|
||||||
accountSequence: accountSequence,
|
|
||||||
amount: amount,
|
|
||||||
);
|
|
||||||
return Right(result);
|
|
||||||
} on ServerException catch (e) {
|
|
||||||
return Left(ServerFailure(e.message));
|
|
||||||
} on NetworkException {
|
|
||||||
return Left(const NetworkFailure());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Either<Failure, List<TradeOrder>>> getOrders(
|
|
||||||
String accountSequence, {
|
|
||||||
int page = 1,
|
|
||||||
int limit = 20,
|
|
||||||
}) async {
|
|
||||||
try {
|
|
||||||
final result = await remoteDataSource.getOrders(
|
|
||||||
accountSequence,
|
|
||||||
page: page,
|
|
||||||
limit: limit,
|
|
||||||
);
|
|
||||||
return Right(result);
|
|
||||||
} on ServerException catch (e) {
|
|
||||||
return Left(ServerFailure(e.message));
|
|
||||||
} on NetworkException {
|
|
||||||
return Left(const NetworkFailure());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Either<Failure, void>> transfer({
|
|
||||||
required String accountSequence,
|
|
||||||
required String amount,
|
|
||||||
required String direction,
|
|
||||||
}) async {
|
|
||||||
try {
|
|
||||||
await remoteDataSource.transfer(
|
|
||||||
accountSequence: accountSequence,
|
|
||||||
amount: amount,
|
|
||||||
direction: direction,
|
|
||||||
);
|
|
||||||
return const Right(null);
|
return const Right(null);
|
||||||
} on ServerException catch (e) {
|
} on ServerException catch (e) {
|
||||||
return Left(ServerFailure(e.message));
|
return Left(ServerFailure(e.message));
|
||||||
|
|
@ -110,4 +81,82 @@ class TradingRepositoryImpl implements TradingRepository {
|
||||||
return Left(const NetworkFailure());
|
return Left(const NetworkFailure());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Either<Failure, OrdersPageModel>> getOrders({
|
||||||
|
int page = 1,
|
||||||
|
int pageSize = 50,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final result = await remoteDataSource.getOrders(
|
||||||
|
page: page,
|
||||||
|
pageSize: pageSize,
|
||||||
|
);
|
||||||
|
return Right(result);
|
||||||
|
} on ServerException catch (e) {
|
||||||
|
return Left(ServerFailure(e.message));
|
||||||
|
} on NetworkException {
|
||||||
|
return Left(const NetworkFailure());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Either<Failure, Map<String, dynamic>>> estimateSell(String quantity) async {
|
||||||
|
try {
|
||||||
|
final result = await remoteDataSource.estimateSell(quantity);
|
||||||
|
return Right(result);
|
||||||
|
} on ServerException catch (e) {
|
||||||
|
return Left(ServerFailure(e.message));
|
||||||
|
} on NetworkException {
|
||||||
|
return Left(const NetworkFailure());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Either<Failure, Map<String, dynamic>>> transferIn(String amount) async {
|
||||||
|
try {
|
||||||
|
final result = await remoteDataSource.transferIn(amount);
|
||||||
|
return Right(result);
|
||||||
|
} on ServerException catch (e) {
|
||||||
|
return Left(ServerFailure(e.message));
|
||||||
|
} on NetworkException {
|
||||||
|
return Left(const NetworkFailure());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Either<Failure, Map<String, dynamic>>> transferOut(String amount) async {
|
||||||
|
try {
|
||||||
|
final result = await remoteDataSource.transferOut(amount);
|
||||||
|
return Right(result);
|
||||||
|
} on ServerException catch (e) {
|
||||||
|
return Left(ServerFailure(e.message));
|
||||||
|
} on NetworkException {
|
||||||
|
return Left(const NetworkFailure());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Either<Failure, AssetDisplay>> getMyAsset({String? dailyAllocation}) async {
|
||||||
|
try {
|
||||||
|
final result = await remoteDataSource.getMyAsset(dailyAllocation: dailyAllocation);
|
||||||
|
return Right(result);
|
||||||
|
} on ServerException catch (e) {
|
||||||
|
return Left(ServerFailure(e.message));
|
||||||
|
} on NetworkException {
|
||||||
|
return Left(const NetworkFailure());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Either<Failure, AssetDisplay>> getAccountAsset(String accountSequence, {String? dailyAllocation}) async {
|
||||||
|
try {
|
||||||
|
final result = await remoteDataSource.getAccountAsset(accountSequence, dailyAllocation: dailyAllocation);
|
||||||
|
return Right(result);
|
||||||
|
} on ServerException catch (e) {
|
||||||
|
return Left(ServerFailure(e.message));
|
||||||
|
} on NetworkException {
|
||||||
|
return Left(const NetworkFailure());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
/// 用户资产显示信息
|
||||||
|
/// 来自后端 trading-service 的 /asset/my 或 /asset/account/:accountSequence 接口
|
||||||
|
class AssetDisplay extends Equatable {
|
||||||
|
/// 账户积分股余额
|
||||||
|
final String shareBalance;
|
||||||
|
|
||||||
|
/// 账户现金余额
|
||||||
|
final String cashBalance;
|
||||||
|
|
||||||
|
/// 冻结积分股
|
||||||
|
final String frozenShares;
|
||||||
|
|
||||||
|
/// 冻结现金
|
||||||
|
final String frozenCash;
|
||||||
|
|
||||||
|
/// 可用积分股
|
||||||
|
final String availableShares;
|
||||||
|
|
||||||
|
/// 可用现金
|
||||||
|
final String availableCash;
|
||||||
|
|
||||||
|
/// 当前价格
|
||||||
|
final String currentPrice;
|
||||||
|
|
||||||
|
/// 销毁倍数
|
||||||
|
final String burnMultiplier;
|
||||||
|
|
||||||
|
/// 有效积分股(含销毁加成)
|
||||||
|
final String effectiveShares;
|
||||||
|
|
||||||
|
/// 资产显示值 = (账户积分股 + 账户积分股 × 倍数) × 积分股价
|
||||||
|
final String displayAssetValue;
|
||||||
|
|
||||||
|
/// 每秒增长量
|
||||||
|
final String assetGrowthPerSecond;
|
||||||
|
|
||||||
|
/// 累计买入
|
||||||
|
final String totalBought;
|
||||||
|
|
||||||
|
/// 累计卖出
|
||||||
|
final String totalSold;
|
||||||
|
|
||||||
|
const AssetDisplay({
|
||||||
|
required this.shareBalance,
|
||||||
|
required this.cashBalance,
|
||||||
|
required this.frozenShares,
|
||||||
|
required this.frozenCash,
|
||||||
|
required this.availableShares,
|
||||||
|
required this.availableCash,
|
||||||
|
required this.currentPrice,
|
||||||
|
required this.burnMultiplier,
|
||||||
|
required this.effectiveShares,
|
||||||
|
required this.displayAssetValue,
|
||||||
|
required this.assetGrowthPerSecond,
|
||||||
|
required this.totalBought,
|
||||||
|
required this.totalSold,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// 总积分股余额 = 可用 + 冻结
|
||||||
|
String get totalShareBalance {
|
||||||
|
final available = double.tryParse(availableShares) ?? 0;
|
||||||
|
final frozen = double.tryParse(frozenShares) ?? 0;
|
||||||
|
return (available + frozen).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 总现金余额 = 可用 + 冻结
|
||||||
|
String get totalCashBalance {
|
||||||
|
final available = double.tryParse(availableCash) ?? 0;
|
||||||
|
final frozen = double.tryParse(frozenCash) ?? 0;
|
||||||
|
return (available + frozen).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
shareBalance,
|
||||||
|
cashBalance,
|
||||||
|
frozenShares,
|
||||||
|
frozenCash,
|
||||||
|
availableShares,
|
||||||
|
availableCash,
|
||||||
|
currentPrice,
|
||||||
|
burnMultiplier,
|
||||||
|
effectiveShares,
|
||||||
|
displayAssetValue,
|
||||||
|
assetGrowthPerSecond,
|
||||||
|
totalBought,
|
||||||
|
totalSold,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
@ -1,41 +1,83 @@
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
/// 账户状态枚举
|
||||||
|
enum ContributionAccountStatus {
|
||||||
|
/// 正常 - 账户存在且有算力数据
|
||||||
|
active,
|
||||||
|
/// 未激活 - 用户存在但暂无任何算力
|
||||||
|
inactive,
|
||||||
|
/// 用户不存在
|
||||||
|
userNotFound,
|
||||||
|
}
|
||||||
|
|
||||||
class Contribution extends Equatable {
|
class Contribution extends Equatable {
|
||||||
|
/// 账户状态
|
||||||
|
final ContributionAccountStatus status;
|
||||||
|
/// 状态说明
|
||||||
|
final String message;
|
||||||
|
/// 账户序号
|
||||||
final String accountSequence;
|
final String accountSequence;
|
||||||
|
/// 个人算力
|
||||||
final String personalContribution;
|
final String personalContribution;
|
||||||
final String systemContribution;
|
/// 团队层级算力
|
||||||
final String teamLevelContribution;
|
final String teamLevelContribution;
|
||||||
|
/// 团队奖励算力
|
||||||
final String teamBonusContribution;
|
final String teamBonusContribution;
|
||||||
|
/// 总算力
|
||||||
final String totalContribution;
|
final String totalContribution;
|
||||||
final String effectiveContribution;
|
/// 是否已认种
|
||||||
final bool hasAdopted;
|
final bool hasAdopted;
|
||||||
|
/// 直推认种用户数
|
||||||
final int directReferralAdoptedCount;
|
final int directReferralAdoptedCount;
|
||||||
|
/// 已解锁层级深度
|
||||||
final int unlockedLevelDepth;
|
final int unlockedLevelDepth;
|
||||||
|
/// 已解锁奖励档位数
|
||||||
final int unlockedBonusTiers;
|
final int unlockedBonusTiers;
|
||||||
|
/// 是否已完成计算
|
||||||
|
final bool isCalculated;
|
||||||
|
/// 最后计算时间
|
||||||
|
final DateTime? lastCalculatedAt;
|
||||||
|
|
||||||
const Contribution({
|
const Contribution({
|
||||||
|
required this.status,
|
||||||
|
required this.message,
|
||||||
required this.accountSequence,
|
required this.accountSequence,
|
||||||
required this.personalContribution,
|
required this.personalContribution,
|
||||||
required this.systemContribution,
|
|
||||||
required this.teamLevelContribution,
|
required this.teamLevelContribution,
|
||||||
required this.teamBonusContribution,
|
required this.teamBonusContribution,
|
||||||
required this.totalContribution,
|
required this.totalContribution,
|
||||||
required this.effectiveContribution,
|
|
||||||
required this.hasAdopted,
|
required this.hasAdopted,
|
||||||
required this.directReferralAdoptedCount,
|
required this.directReferralAdoptedCount,
|
||||||
required this.unlockedLevelDepth,
|
required this.unlockedLevelDepth,
|
||||||
required this.unlockedBonusTiers,
|
required this.unlockedBonusTiers,
|
||||||
|
required this.isCalculated,
|
||||||
|
this.lastCalculatedAt,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// 团队总贡献值 (层级 + 奖励)
|
||||||
|
String get teamContribution {
|
||||||
|
final teamLevel = double.tryParse(teamLevelContribution) ?? 0;
|
||||||
|
final teamBonus = double.tryParse(teamBonusContribution) ?? 0;
|
||||||
|
return (teamLevel + teamBonus).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 是否处于活跃状态
|
||||||
|
bool get isActive => status == ContributionAccountStatus.active;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [
|
List<Object?> get props => [
|
||||||
|
status,
|
||||||
|
message,
|
||||||
accountSequence,
|
accountSequence,
|
||||||
personalContribution,
|
personalContribution,
|
||||||
systemContribution,
|
|
||||||
teamLevelContribution,
|
teamLevelContribution,
|
||||||
teamBonusContribution,
|
teamBonusContribution,
|
||||||
totalContribution,
|
totalContribution,
|
||||||
effectiveContribution,
|
|
||||||
hasAdopted,
|
hasAdopted,
|
||||||
|
directReferralAdoptedCount,
|
||||||
|
unlockedLevelDepth,
|
||||||
|
unlockedBonusTiers,
|
||||||
|
isCalculated,
|
||||||
|
lastCalculatedAt,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
/// 市场概览信息
|
||||||
|
class MarketOverview extends Equatable {
|
||||||
|
/// 当前价格
|
||||||
|
final String price;
|
||||||
|
/// 绿积分池
|
||||||
|
final String greenPoints;
|
||||||
|
/// 黑洞销毁量
|
||||||
|
final String blackHoleAmount;
|
||||||
|
/// 流通池
|
||||||
|
final String circulationPool;
|
||||||
|
/// 有效分母
|
||||||
|
final String effectiveDenominator;
|
||||||
|
/// 销毁倍数
|
||||||
|
final String burnMultiplier;
|
||||||
|
/// 总积分股
|
||||||
|
final String totalShares;
|
||||||
|
/// 销毁目标
|
||||||
|
final String burnTarget;
|
||||||
|
/// 销毁进度
|
||||||
|
final String burnProgress;
|
||||||
|
|
||||||
|
const MarketOverview({
|
||||||
|
required this.price,
|
||||||
|
required this.greenPoints,
|
||||||
|
required this.blackHoleAmount,
|
||||||
|
required this.circulationPool,
|
||||||
|
required this.effectiveDenominator,
|
||||||
|
required this.burnMultiplier,
|
||||||
|
required this.totalShares,
|
||||||
|
required this.burnTarget,
|
||||||
|
required this.burnProgress,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
price,
|
||||||
|
greenPoints,
|
||||||
|
blackHoleAmount,
|
||||||
|
circulationPool,
|
||||||
|
effectiveDenominator,
|
||||||
|
burnMultiplier,
|
||||||
|
totalShares,
|
||||||
|
burnTarget,
|
||||||
|
burnProgress,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
/// 价格信息
|
||||||
|
class PriceInfo extends Equatable {
|
||||||
|
/// 当前价格
|
||||||
|
final String price;
|
||||||
|
/// 绿积分池
|
||||||
|
final String greenPoints;
|
||||||
|
/// 黑洞销毁量
|
||||||
|
final String blackHoleAmount;
|
||||||
|
/// 流通池
|
||||||
|
final String circulationPool;
|
||||||
|
/// 有效分母
|
||||||
|
final String effectiveDenominator;
|
||||||
|
/// 销毁倍数
|
||||||
|
final String burnMultiplier;
|
||||||
|
/// 每分钟销毁率
|
||||||
|
final String minuteBurnRate;
|
||||||
|
/// 快照时间
|
||||||
|
final DateTime snapshotTime;
|
||||||
|
|
||||||
|
const PriceInfo({
|
||||||
|
required this.price,
|
||||||
|
required this.greenPoints,
|
||||||
|
required this.blackHoleAmount,
|
||||||
|
required this.circulationPool,
|
||||||
|
required this.effectiveDenominator,
|
||||||
|
required this.burnMultiplier,
|
||||||
|
required this.minuteBurnRate,
|
||||||
|
required this.snapshotTime,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
price,
|
||||||
|
greenPoints,
|
||||||
|
blackHoleAmount,
|
||||||
|
circulationPool,
|
||||||
|
effectiveDenominator,
|
||||||
|
burnMultiplier,
|
||||||
|
minuteBurnRate,
|
||||||
|
snapshotTime,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
@ -4,41 +4,64 @@ enum OrderType { buy, sell }
|
||||||
enum OrderStatus { pending, partial, filled, cancelled }
|
enum OrderStatus { pending, partial, filled, cancelled }
|
||||||
|
|
||||||
class TradeOrder extends Equatable {
|
class TradeOrder extends Equatable {
|
||||||
|
/// 订单ID
|
||||||
final String id;
|
final String id;
|
||||||
final String accountSequence;
|
/// 订单号
|
||||||
|
final String orderNo;
|
||||||
|
/// 订单类型
|
||||||
final OrderType orderType;
|
final OrderType orderType;
|
||||||
|
/// 订单状态
|
||||||
final OrderStatus status;
|
final OrderStatus status;
|
||||||
|
/// 价格
|
||||||
final String price;
|
final String price;
|
||||||
|
/// 数量
|
||||||
final String quantity;
|
final String quantity;
|
||||||
|
/// 已成交数量
|
||||||
final String filledQuantity;
|
final String filledQuantity;
|
||||||
|
/// 剩余数量
|
||||||
|
final String remainingQuantity;
|
||||||
|
/// 平均成交价格
|
||||||
|
final String averagePrice;
|
||||||
|
/// 总金额
|
||||||
final String totalAmount;
|
final String totalAmount;
|
||||||
|
/// 创建时间
|
||||||
final DateTime createdAt;
|
final DateTime createdAt;
|
||||||
final DateTime? updatedAt;
|
/// 完成时间
|
||||||
|
final DateTime? completedAt;
|
||||||
|
/// 取消时间
|
||||||
|
final DateTime? cancelledAt;
|
||||||
|
|
||||||
const TradeOrder({
|
const TradeOrder({
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.accountSequence,
|
required this.orderNo,
|
||||||
required this.orderType,
|
required this.orderType,
|
||||||
required this.status,
|
required this.status,
|
||||||
required this.price,
|
required this.price,
|
||||||
required this.quantity,
|
required this.quantity,
|
||||||
required this.filledQuantity,
|
required this.filledQuantity,
|
||||||
|
required this.remainingQuantity,
|
||||||
|
required this.averagePrice,
|
||||||
required this.totalAmount,
|
required this.totalAmount,
|
||||||
required this.createdAt,
|
required this.createdAt,
|
||||||
this.updatedAt,
|
this.completedAt,
|
||||||
|
this.cancelledAt,
|
||||||
});
|
});
|
||||||
|
|
||||||
bool get isBuy => orderType == OrderType.buy;
|
bool get isBuy => orderType == OrderType.buy;
|
||||||
bool get isSell => orderType == OrderType.sell;
|
bool get isSell => orderType == OrderType.sell;
|
||||||
bool get isPending => status == OrderStatus.pending;
|
bool get isPending => status == OrderStatus.pending;
|
||||||
|
bool get isPartial => status == OrderStatus.partial;
|
||||||
bool get isFilled => status == OrderStatus.filled;
|
bool get isFilled => status == OrderStatus.filled;
|
||||||
|
bool get isCancelled => status == OrderStatus.cancelled;
|
||||||
|
|
||||||
String get remainingQuantity {
|
/// 成交进度百分比
|
||||||
|
double get fillProgress {
|
||||||
final qty = double.tryParse(quantity) ?? 0;
|
final qty = double.tryParse(quantity) ?? 0;
|
||||||
final filled = double.tryParse(filledQuantity) ?? 0;
|
final filled = double.tryParse(filledQuantity) ?? 0;
|
||||||
return (qty - filled).toString();
|
if (qty == 0) return 0;
|
||||||
|
return filled / qty;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [id, accountSequence, orderType, status, price, quantity];
|
List<Object?> get props => [id, orderNo, orderType, status, price, quantity];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
/// 交易账户信息
|
||||||
|
class TradingAccount extends Equatable {
|
||||||
|
/// 账户序号
|
||||||
|
final String accountSequence;
|
||||||
|
/// 积分股余额
|
||||||
|
final String shareBalance;
|
||||||
|
/// 现金余额(绿积分)
|
||||||
|
final String cashBalance;
|
||||||
|
/// 可用积分股
|
||||||
|
final String availableShares;
|
||||||
|
/// 可用现金
|
||||||
|
final String availableCash;
|
||||||
|
/// 冻结积分股
|
||||||
|
final String frozenShares;
|
||||||
|
/// 冻结现金
|
||||||
|
final String frozenCash;
|
||||||
|
/// 累计买入
|
||||||
|
final String totalBought;
|
||||||
|
/// 累计卖出
|
||||||
|
final String totalSold;
|
||||||
|
|
||||||
|
const TradingAccount({
|
||||||
|
required this.accountSequence,
|
||||||
|
required this.shareBalance,
|
||||||
|
required this.cashBalance,
|
||||||
|
required this.availableShares,
|
||||||
|
required this.availableCash,
|
||||||
|
required this.frozenShares,
|
||||||
|
required this.frozenCash,
|
||||||
|
required this.totalBought,
|
||||||
|
required this.totalSold,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
accountSequence,
|
||||||
|
shareBalance,
|
||||||
|
cashBalance,
|
||||||
|
availableShares,
|
||||||
|
availableCash,
|
||||||
|
frozenShares,
|
||||||
|
frozenCash,
|
||||||
|
totalBought,
|
||||||
|
totalSold,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
@ -1,32 +1,50 @@
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
import '../../core/error/failures.dart';
|
import '../../core/error/failures.dart';
|
||||||
|
import '../entities/price_info.dart';
|
||||||
|
import '../entities/market_overview.dart';
|
||||||
|
import '../entities/trading_account.dart';
|
||||||
import '../entities/trade_order.dart';
|
import '../entities/trade_order.dart';
|
||||||
import '../entities/kline.dart';
|
import '../entities/asset_display.dart';
|
||||||
|
import '../../data/models/trade_order_model.dart';
|
||||||
|
|
||||||
abstract class TradingRepository {
|
abstract class TradingRepository {
|
||||||
Future<Either<Failure, String>> getCurrentPrice();
|
/// 获取当前价格信息
|
||||||
|
Future<Either<Failure, PriceInfo>> getCurrentPrice();
|
||||||
|
|
||||||
Future<Either<Failure, List<Kline>>> getKlineData(String period);
|
/// 获取市场概览
|
||||||
|
Future<Either<Failure, MarketOverview>> getMarketOverview();
|
||||||
|
|
||||||
Future<Either<Failure, TradeOrder>> buyShares({
|
/// 获取交易账户信息
|
||||||
required String accountSequence,
|
Future<Either<Failure, TradingAccount>> getTradingAccount(String accountSequence);
|
||||||
required String amount,
|
|
||||||
|
/// 创建订单
|
||||||
|
Future<Either<Failure, Map<String, dynamic>>> createOrder({
|
||||||
|
required String type,
|
||||||
|
required String price,
|
||||||
|
required String quantity,
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<Either<Failure, TradeOrder>> sellShares({
|
/// 取消订单
|
||||||
required String accountSequence,
|
Future<Either<Failure, void>> cancelOrder(String orderNo);
|
||||||
required String amount,
|
|
||||||
});
|
|
||||||
|
|
||||||
Future<Either<Failure, List<TradeOrder>>> getOrders(
|
/// 获取用户订单列表
|
||||||
String accountSequence, {
|
Future<Either<Failure, OrdersPageModel>> getOrders({
|
||||||
int page = 1,
|
int page = 1,
|
||||||
int limit = 20,
|
int pageSize = 50,
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<Either<Failure, void>> transfer({
|
/// 预估卖出收益
|
||||||
required String accountSequence,
|
Future<Either<Failure, Map<String, dynamic>>> estimateSell(String quantity);
|
||||||
required String amount,
|
|
||||||
required String direction,
|
/// 划入积分股 (从挖矿账户到交易账户)
|
||||||
});
|
Future<Either<Failure, Map<String, dynamic>>> transferIn(String amount);
|
||||||
|
|
||||||
|
/// 划出积分股 (从交易账户到挖矿账户)
|
||||||
|
Future<Either<Failure, Map<String, dynamic>>> transferOut(String amount);
|
||||||
|
|
||||||
|
/// 获取我的资产显示信息
|
||||||
|
Future<Either<Failure, AssetDisplay>> getMyAsset({String? dailyAllocation});
|
||||||
|
|
||||||
|
/// 获取指定账户资产显示信息
|
||||||
|
Future<Either<Failure, AssetDisplay>> getAccountAsset(String accountSequence, {String? dailyAllocation});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
import 'package:dartz/dartz.dart';
|
|
||||||
import '../../../core/error/failures.dart';
|
|
||||||
import '../../entities/trade_order.dart';
|
|
||||||
import '../../repositories/trading_repository.dart';
|
|
||||||
|
|
||||||
class BuySharesParams {
|
|
||||||
final String accountSequence;
|
|
||||||
final String amount;
|
|
||||||
|
|
||||||
BuySharesParams({required this.accountSequence, required this.amount});
|
|
||||||
}
|
|
||||||
|
|
||||||
class BuyShares {
|
|
||||||
final TradingRepository repository;
|
|
||||||
|
|
||||||
BuyShares(this.repository);
|
|
||||||
|
|
||||||
Future<Either<Failure, TradeOrder>> call(BuySharesParams params) async {
|
|
||||||
return await repository.buyShares(
|
|
||||||
accountSequence: params.accountSequence,
|
|
||||||
amount: params.amount,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
import '../../../core/error/failures.dart';
|
import '../../../core/error/failures.dart';
|
||||||
|
import '../../entities/price_info.dart';
|
||||||
import '../../repositories/trading_repository.dart';
|
import '../../repositories/trading_repository.dart';
|
||||||
|
|
||||||
class GetCurrentPrice {
|
class GetCurrentPrice {
|
||||||
|
|
@ -7,7 +8,7 @@ class GetCurrentPrice {
|
||||||
|
|
||||||
GetCurrentPrice(this.repository);
|
GetCurrentPrice(this.repository);
|
||||||
|
|
||||||
Future<Either<Failure, String>> call() async {
|
Future<Either<Failure, PriceInfo>> call() async {
|
||||||
return await repository.getCurrentPrice();
|
return await repository.getCurrentPrice();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
import 'package:dartz/dartz.dart';
|
|
||||||
import '../../../core/error/failures.dart';
|
|
||||||
import '../../entities/trade_order.dart';
|
|
||||||
import '../../repositories/trading_repository.dart';
|
|
||||||
|
|
||||||
class SellSharesParams {
|
|
||||||
final String accountSequence;
|
|
||||||
final String amount;
|
|
||||||
|
|
||||||
SellSharesParams({required this.accountSequence, required this.amount});
|
|
||||||
}
|
|
||||||
|
|
||||||
class SellShares {
|
|
||||||
final TradingRepository repository;
|
|
||||||
|
|
||||||
SellShares(this.repository);
|
|
||||||
|
|
||||||
Future<Either<Failure, TradeOrder>> call(SellSharesParams params) async {
|
|
||||||
return await repository.sellShares(
|
|
||||||
accountSequence: params.accountSequence,
|
|
||||||
amount: params.amount,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import '../../../core/utils/format_utils.dart';
|
import '../../../core/utils/format_utils.dart';
|
||||||
|
import '../../../domain/entities/asset_display.dart';
|
||||||
import '../../providers/user_providers.dart';
|
import '../../providers/user_providers.dart';
|
||||||
import '../../providers/mining_providers.dart';
|
import '../../providers/asset_providers.dart';
|
||||||
import '../../widgets/shimmer_loading.dart';
|
import '../../widgets/shimmer_loading.dart';
|
||||||
|
|
||||||
class AssetPage extends ConsumerWidget {
|
class AssetPage extends ConsumerWidget {
|
||||||
|
|
@ -23,12 +24,11 @@ class AssetPage extends ConsumerWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final user = ref.watch(userNotifierProvider);
|
final user = ref.watch(userNotifierProvider);
|
||||||
final accountSequence = user.accountSequence ?? '';
|
final assetAsync = ref.watch(myAssetProvider);
|
||||||
final accountAsync = ref.watch(shareAccountProvider(accountSequence));
|
|
||||||
|
|
||||||
// 提取数据和加载状态
|
// 提取数据和加载状态
|
||||||
final isLoading = accountAsync.isLoading;
|
final isLoading = assetAsync.isLoading;
|
||||||
final account = accountAsync.valueOrNull;
|
final asset = assetAsync.valueOrNull;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
|
|
@ -38,7 +38,7 @@ class AssetPage extends ConsumerWidget {
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
ref.invalidate(shareAccountProvider(accountSequence));
|
ref.invalidate(myAssetProvider);
|
||||||
},
|
},
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
|
|
@ -55,19 +55,19 @@ class AssetPage extends ConsumerWidget {
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
// 总资产卡片 - 始终显示,数字部分闪烁
|
// 总资产卡片 - 始终显示,数字部分闪烁
|
||||||
_buildTotalAssetCard(account, isLoading),
|
_buildTotalAssetCard(asset, isLoading),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
// 快捷操作按钮
|
// 快捷操作按钮
|
||||||
_buildQuickActions(),
|
_buildQuickActions(),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
// 资产列表 - 始终显示,数字部分闪烁
|
// 资产列表 - 始终显示,数字部分闪烁
|
||||||
_buildAssetList(account, isLoading),
|
_buildAssetList(asset, isLoading),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
// 收益统计
|
// 交易统计
|
||||||
_buildEarningsCard(account, isLoading),
|
_buildEarningsCard(asset, isLoading),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
// 账户列表
|
// 账户列表
|
||||||
_buildAccountList(account, isLoading),
|
_buildAccountList(asset, isLoading),
|
||||||
const SizedBox(height: 100),
|
const SizedBox(height: 100),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -181,7 +181,12 @@ class AssetPage extends ConsumerWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildTotalAssetCard(account, bool isLoading) {
|
Widget _buildTotalAssetCard(AssetDisplay? asset, bool isLoading) {
|
||||||
|
// 计算每秒增长
|
||||||
|
final growthPerSecond = asset != null
|
||||||
|
? AssetValueCalculator.calculateGrowthPerSecond(asset.assetGrowthPerSecond)
|
||||||
|
: 0.0;
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
|
|
@ -254,7 +259,7 @@ class AssetPage extends ConsumerWidget {
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
// 金额 - 闪烁占位符
|
// 金额 - 闪烁占位符
|
||||||
AmountText(
|
AmountText(
|
||||||
amount: account != null ? formatAmount(account.tradingBalance ?? '0') : null,
|
amount: asset != null ? formatAmount(asset.displayAssetValue) : null,
|
||||||
isLoading: isLoading,
|
isLoading: isLoading,
|
||||||
prefix: '¥ ',
|
prefix: '¥ ',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
|
|
@ -265,18 +270,18 @@ class AssetPage extends ConsumerWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
// USDT估值
|
// 有效积分股
|
||||||
DataText(
|
DataText(
|
||||||
data: account != null ? '≈ 12,345.67 USDT' : null,
|
data: asset != null ? '≈ ${formatCompact(asset.effectiveShares)} 积分股 (含倍数)' : null,
|
||||||
isLoading: isLoading,
|
isLoading: isLoading,
|
||||||
placeholder: '≈ -- USDT',
|
placeholder: '≈ -- 积分股',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: Color(0xFF9CA3AF),
|
color: Color(0xFF9CA3AF),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
// 今日收益
|
// 每秒增长
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
|
@ -286,12 +291,14 @@ class AssetPage extends ConsumerWidget {
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.trending_up, size: 14, color: _green),
|
const Icon(Icons.bolt, size: 14, color: _green),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
DataText(
|
DataText(
|
||||||
data: account != null ? '+¥ 156.78 今日' : null,
|
data: asset != null
|
||||||
|
? '+${formatDecimal(growthPerSecond.toString(), 8)}/秒'
|
||||||
|
: null,
|
||||||
isLoading: isLoading,
|
isLoading: isLoading,
|
||||||
placeholder: '+¥ -- 今日',
|
placeholder: '+--/秒',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
|
|
@ -346,7 +353,12 @@ class AssetPage extends ConsumerWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildAssetList(account, bool isLoading) {
|
Widget _buildAssetList(AssetDisplay? asset, bool isLoading) {
|
||||||
|
// 计算倍数资产
|
||||||
|
final shareBalance = double.tryParse(asset?.shareBalance ?? '0') ?? 0;
|
||||||
|
final multiplier = double.tryParse(asset?.burnMultiplier ?? '0') ?? 0;
|
||||||
|
final multipliedAsset = shareBalance * multiplier;
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
// 积分股
|
// 积分股
|
||||||
|
|
@ -355,41 +367,49 @@ class AssetPage extends ConsumerWidget {
|
||||||
iconColor: _orange,
|
iconColor: _orange,
|
||||||
iconBgColor: _serenade,
|
iconBgColor: _serenade,
|
||||||
title: '积分股',
|
title: '积分股',
|
||||||
amount: account?.miningBalance,
|
amount: asset?.shareBalance,
|
||||||
isLoading: isLoading,
|
isLoading: isLoading,
|
||||||
valueInCny: '¥15,234.56',
|
valueInCny: asset != null
|
||||||
tag: '含倍数资产: 246,913.56',
|
? '¥${formatAmount(_calculateValue(asset.shareBalance, asset.currentPrice))}'
|
||||||
growthText: '每秒 +0.0015',
|
: null,
|
||||||
|
tag: asset != null ? '含倍数资产: ${formatCompact(multipliedAsset.toString())}' : null,
|
||||||
|
growthText: asset != null ? '每秒 +${formatDecimal(asset.assetGrowthPerSecond, 8)}' : null,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
// 绿积分
|
// 绿积分(现金余额)
|
||||||
_buildAssetItem(
|
_buildAssetItem(
|
||||||
icon: Icons.eco,
|
icon: Icons.eco,
|
||||||
iconColor: _green,
|
iconColor: _green,
|
||||||
iconBgColor: _feta,
|
iconBgColor: _feta,
|
||||||
title: '绿积分',
|
title: '绿积分',
|
||||||
amount: account?.tradingBalance,
|
amount: asset?.cashBalance,
|
||||||
isLoading: isLoading,
|
isLoading: isLoading,
|
||||||
valueInCny: '¥10,986.54',
|
valueInCny: asset != null ? '¥${formatAmount(asset.cashBalance)}' : null,
|
||||||
badge: '可提现',
|
badge: '可提现',
|
||||||
badgeColor: _jewel,
|
badgeColor: _jewel,
|
||||||
badgeBgColor: _scandal,
|
badgeBgColor: _scandal,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
// 待分配积分股
|
// 冻结积分股
|
||||||
_buildAssetItem(
|
_buildAssetItem(
|
||||||
icon: Icons.hourglass_empty,
|
icon: Icons.lock_outline,
|
||||||
iconColor: _orange,
|
iconColor: _orange,
|
||||||
iconBgColor: _serenade,
|
iconBgColor: _serenade,
|
||||||
title: '待分配积分股',
|
title: '冻结积分股',
|
||||||
amount: '1,234.56',
|
amount: asset?.frozenShares,
|
||||||
isLoading: isLoading,
|
isLoading: isLoading,
|
||||||
subtitle: '次日开始参与分配',
|
subtitle: '交易挂单中',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _calculateValue(String balance, String price) {
|
||||||
|
final b = double.tryParse(balance) ?? 0;
|
||||||
|
final p = double.tryParse(price) ?? 0;
|
||||||
|
return (b * p).toString();
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildAssetItem({
|
Widget _buildAssetItem({
|
||||||
required IconData icon,
|
required IconData icon,
|
||||||
required Color iconColor,
|
required Color iconColor,
|
||||||
|
|
@ -555,7 +575,7 @@ class AssetPage extends ConsumerWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildEarningsCard(account, bool isLoading) {
|
Widget _buildEarningsCard(AssetDisplay? asset, bool isLoading) {
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.all(20),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
|
@ -584,7 +604,7 @@ class AssetPage extends ConsumerWidget {
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
const Text(
|
const Text(
|
||||||
'收益统计',
|
'交易统计',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
|
@ -597,19 +617,34 @@ class AssetPage extends ConsumerWidget {
|
||||||
// 统计数据
|
// 统计数据
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
_buildEarningsItem('累计收益', isLoading ? null : '12,345.67', _orange, isLoading),
|
_buildEarningsItem(
|
||||||
|
'累计买入',
|
||||||
|
asset != null ? formatCompact(asset.totalBought) : null,
|
||||||
|
_orange,
|
||||||
|
isLoading,
|
||||||
|
),
|
||||||
Container(
|
Container(
|
||||||
width: 1,
|
width: 1,
|
||||||
height: 40,
|
height: 40,
|
||||||
color: _serenade,
|
color: _serenade,
|
||||||
),
|
),
|
||||||
_buildEarningsItem('今日收益', isLoading ? null : '+156.78', _green, isLoading),
|
_buildEarningsItem(
|
||||||
|
'累计卖出',
|
||||||
|
asset != null ? formatCompact(asset.totalSold) : null,
|
||||||
|
_green,
|
||||||
|
isLoading,
|
||||||
|
),
|
||||||
Container(
|
Container(
|
||||||
width: 1,
|
width: 1,
|
||||||
height: 40,
|
height: 40,
|
||||||
color: _serenade,
|
color: _serenade,
|
||||||
),
|
),
|
||||||
_buildEarningsItem('昨日收益', isLoading ? null : '143.21', const Color(0xFF9CA3AF), isLoading),
|
_buildEarningsItem(
|
||||||
|
'销毁倍数',
|
||||||
|
asset != null ? '${formatDecimal(asset.burnMultiplier, 4)}x' : null,
|
||||||
|
const Color(0xFF9CA3AF),
|
||||||
|
isLoading,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -646,31 +681,31 @@ class AssetPage extends ConsumerWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildAccountList(account, bool isLoading) {
|
Widget _buildAccountList(AssetDisplay? asset, bool isLoading) {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
// 交易账户
|
// 交易账户(可用现金)
|
||||||
_buildAccountItem(
|
_buildAccountItem(
|
||||||
icon: Icons.account_balance_wallet,
|
icon: Icons.account_balance_wallet,
|
||||||
iconColor: _orange,
|
iconColor: _orange,
|
||||||
title: '交易账户',
|
title: '可用绿积分',
|
||||||
balance: account?.tradingBalance,
|
balance: asset?.availableCash,
|
||||||
isLoading: isLoading,
|
isLoading: isLoading,
|
||||||
unit: '绿积分',
|
unit: '绿积分',
|
||||||
status: '正常',
|
status: '可交易',
|
||||||
statusColor: _green,
|
statusColor: _green,
|
||||||
statusBgColor: _feta,
|
statusBgColor: _feta,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
// 提现账户
|
// 冻结现金
|
||||||
_buildAccountItem(
|
_buildAccountItem(
|
||||||
icon: Icons.savings,
|
icon: Icons.lock_outline,
|
||||||
iconColor: _orange,
|
iconColor: _orange,
|
||||||
title: '提现账户',
|
title: '冻结绿积分',
|
||||||
balance: '1,234.56',
|
balance: asset?.frozenCash,
|
||||||
isLoading: isLoading,
|
isLoading: isLoading,
|
||||||
unit: '绿积分',
|
unit: '绿积分',
|
||||||
status: '已绑定',
|
status: '挂单中',
|
||||||
statusColor: const Color(0xFF9CA3AF),
|
statusColor: const Color(0xFF9CA3AF),
|
||||||
statusBgColor: Colors.white,
|
statusBgColor: Colors.white,
|
||||||
statusBorder: true,
|
statusBorder: true,
|
||||||
|
|
@ -734,7 +769,7 @@ class AssetPage extends ConsumerWidget {
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
DataText(
|
DataText(
|
||||||
data: balance,
|
data: balance != null ? formatAmount(balance) : null,
|
||||||
isLoading: isLoading,
|
isLoading: isLoading,
|
||||||
placeholder: '--',
|
placeholder: '--',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
|
|
@ -778,5 +813,4 @@ class AssetPage extends ConsumerWidget {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -159,7 +159,7 @@ class ContributionPage extends ConsumerWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildTotalContributionCard(Contribution? contribution, bool isLoading) {
|
Widget _buildTotalContributionCard(Contribution? contribution, bool isLoading) {
|
||||||
final total = contribution?.effectiveContribution ?? '0';
|
final total = contribution?.totalContribution ?? '0';
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.all(20),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
|
@ -226,8 +226,8 @@ class ContributionPage extends ConsumerWidget {
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
_buildStatColumn('个人贡献值', contribution?.personalContribution, isLoading, false),
|
_buildStatColumn('个人贡献值', contribution?.personalContribution, isLoading, false),
|
||||||
_buildStatColumn('团队贡献值', contribution?.teamLevelContribution, isLoading, true),
|
_buildStatColumn('团队层级', contribution?.teamLevelContribution, isLoading, true),
|
||||||
_buildStatColumn('省市公司', contribution?.systemContribution, isLoading, true),
|
_buildStatColumn('团队奖励', contribution?.teamBonusContribution, isLoading, true),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -261,10 +261,10 @@ class ContributionPage extends ConsumerWidget {
|
||||||
|
|
||||||
Widget _buildTodayEstimateCard(Contribution? contribution, bool isLoading) {
|
Widget _buildTodayEstimateCard(Contribution? contribution, bool isLoading) {
|
||||||
// 基于贡献值计算预估收益(暂时显示占位符,后续可接入实际计算API)
|
// 基于贡献值计算预估收益(暂时显示占位符,后续可接入实际计算API)
|
||||||
final effectiveContribution = double.tryParse(contribution?.effectiveContribution ?? '0') ?? 0;
|
final totalContribution = double.tryParse(contribution?.totalContribution ?? '0') ?? 0;
|
||||||
// 简单估算:假设每日发放总量为 10000 积分股,按贡献值占比分配
|
// 简单估算:假设每日发放总量为 10000 积分股,按贡献值占比分配
|
||||||
// 这里先显示"--"表示暂无数据,后续可接入实际计算
|
// 这里先显示"--"表示暂无数据,后续可接入实际计算
|
||||||
final hasContribution = effectiveContribution > 0;
|
final hasContribution = totalContribution > 0;
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.all(20),
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import '../../../core/router/routes.dart';
|
import '../../../core/router/routes.dart';
|
||||||
import '../../providers/user_providers.dart';
|
import '../../providers/user_providers.dart';
|
||||||
|
import '../../providers/profile_providers.dart';
|
||||||
|
import '../../widgets/shimmer_loading.dart';
|
||||||
|
|
||||||
class ProfilePage extends ConsumerWidget {
|
class ProfilePage extends ConsumerWidget {
|
||||||
const ProfilePage({super.key});
|
const ProfilePage({super.key});
|
||||||
|
|
@ -20,72 +22,84 @@ class ProfilePage extends ConsumerWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final user = ref.watch(userNotifierProvider);
|
final user = ref.watch(userNotifierProvider);
|
||||||
|
final statsAsync = ref.watch(userStatsProvider);
|
||||||
|
final invitationCode = ref.watch(invitationCodeProvider);
|
||||||
|
|
||||||
|
final isStatsLoading = statsAsync.isLoading;
|
||||||
|
final stats = statsAsync.valueOrNull;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: _bgGray,
|
backgroundColor: _bgGray,
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
bottom: false,
|
bottom: false,
|
||||||
child: SingleChildScrollView(
|
child: RefreshIndicator(
|
||||||
child: Column(
|
onRefresh: () async {
|
||||||
children: [
|
ref.invalidate(userStatsProvider);
|
||||||
// 用户头部信息
|
await ref.read(userNotifierProvider.notifier).fetchProfile();
|
||||||
_buildUserHeader(context, user),
|
},
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
// 用户头部信息
|
||||||
|
_buildUserHeader(context, user),
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// 统计数据行
|
// 统计数据行
|
||||||
_buildStatsRow(),
|
_buildStatsRow(stats, isStatsLoading),
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// 邀请码卡片
|
// 邀请码卡片
|
||||||
_buildInvitationCard(context),
|
_buildInvitationCard(context, invitationCode),
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// 账户设置
|
// 账户设置
|
||||||
_buildAccountSettings(context),
|
_buildAccountSettings(context, user),
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// 记录入口
|
// 记录入口
|
||||||
_buildRecordsSection(context),
|
_buildRecordsSection(context),
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// 团队与收益
|
// 团队与收益
|
||||||
_buildTeamEarningsSection(context),
|
_buildTeamEarningsSection(context),
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// 其他设置
|
// 其他设置
|
||||||
_buildOtherSettings(context),
|
_buildOtherSettings(context),
|
||||||
|
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
// 退出登录
|
// 退出登录
|
||||||
_buildLogoutButton(context, ref),
|
_buildLogoutButton(context, ref),
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// 版本信息
|
// 版本信息
|
||||||
const Text(
|
const Text(
|
||||||
'Version 1.0.0',
|
'Version 1.0.0',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: _lightGray,
|
color: _lightGray,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 100),
|
const SizedBox(height: 100),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildUserHeader(BuildContext context, dynamic user) {
|
Widget _buildUserHeader(BuildContext context, UserState user) {
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.all(20),
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
|
|
@ -107,7 +121,9 @@ class ProfilePage extends ConsumerWidget {
|
||||||
child: Text(
|
child: Text(
|
||||||
user.nickname?.isNotEmpty == true
|
user.nickname?.isNotEmpty == true
|
||||||
? user.nickname!.substring(0, 1).toUpperCase()
|
? user.nickname!.substring(0, 1).toUpperCase()
|
||||||
: 'U',
|
: (user.realName?.isNotEmpty == true
|
||||||
|
? user.realName!.substring(0, 1).toUpperCase()
|
||||||
|
: 'U'),
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 36,
|
fontSize: 36,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
|
@ -127,7 +143,7 @@ class ProfilePage extends ConsumerWidget {
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
user.nickname ?? '榴莲用户',
|
user.realName ?? user.nickname ?? '榴莲用户',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
|
@ -135,27 +151,33 @@ class ProfilePage extends ConsumerWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
// VIP 徽章
|
// 实名认证徽章
|
||||||
Container(
|
if (user.isKycVerified)
|
||||||
padding: const EdgeInsets.symmetric(
|
Container(
|
||||||
horizontal: 8,
|
padding: const EdgeInsets.symmetric(
|
||||||
vertical: 2,
|
horizontal: 8,
|
||||||
),
|
vertical: 2,
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: const LinearGradient(
|
|
||||||
colors: [Color(0xFFFFD700), Color(0xFFFF8C00)],
|
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(10),
|
decoration: BoxDecoration(
|
||||||
),
|
color: _green.withValues(alpha: 0.1),
|
||||||
child: const Text(
|
borderRadius: BorderRadius.circular(10),
|
||||||
'VIP 3',
|
),
|
||||||
style: TextStyle(
|
child: const Row(
|
||||||
fontSize: 10,
|
mainAxisSize: MainAxisSize.min,
|
||||||
fontWeight: FontWeight.bold,
|
children: [
|
||||||
color: Colors.white,
|
Icon(Icons.verified, size: 12, color: _green),
|
||||||
|
SizedBox(width: 2),
|
||||||
|
Text(
|
||||||
|
'已实名',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: _green,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
|
|
@ -191,6 +213,18 @@ class ProfilePage extends ConsumerWidget {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
// 手机号
|
||||||
|
if (user.phone != null)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 4),
|
||||||
|
child: Text(
|
||||||
|
'手机: ${user.phone}',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: _lightGray,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -210,30 +244,48 @@ class ProfilePage extends ConsumerWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildStatsRow() {
|
Widget _buildStatsRow(UserStats? stats, bool isLoading) {
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
children: [
|
children: [
|
||||||
_buildStatItem('认种数量', '10'),
|
_buildStatItem(
|
||||||
|
'认种状态',
|
||||||
|
stats?.hasAdopted == true ? '已认种' : '未认种',
|
||||||
|
isLoading,
|
||||||
|
),
|
||||||
_buildDivider(),
|
_buildDivider(),
|
||||||
_buildStatItem('直推人数', '5'),
|
_buildStatItem(
|
||||||
|
'直推人数',
|
||||||
|
stats?.directReferralAdoptedCount.toString() ?? '0',
|
||||||
|
isLoading,
|
||||||
|
),
|
||||||
_buildDivider(),
|
_buildDivider(),
|
||||||
_buildStatItem('团队人数', '128'),
|
_buildStatItem(
|
||||||
|
'团队层级',
|
||||||
|
stats?.unlockedLevelDepth.toString() ?? '0',
|
||||||
|
isLoading,
|
||||||
|
),
|
||||||
_buildDivider(),
|
_buildDivider(),
|
||||||
_buildStatItem('VIP等级', 'V3'),
|
_buildStatItem(
|
||||||
|
'VIP等级',
|
||||||
|
stats?.vipLevel ?? '-',
|
||||||
|
isLoading,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildStatItem(String label, String value) {
|
Widget _buildStatItem(String label, String value, bool isLoading) {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
DataText(
|
||||||
value,
|
data: isLoading ? null : value,
|
||||||
|
isLoading: isLoading,
|
||||||
|
placeholder: '--',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
|
@ -260,9 +312,7 @@ class ProfilePage extends ConsumerWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildInvitationCard(BuildContext context) {
|
Widget _buildInvitationCard(BuildContext context, String invitationCode) {
|
||||||
const invitationCode = 'DUR8888XYZ';
|
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
|
|
@ -293,9 +343,9 @@ class ProfilePage extends ConsumerWidget {
|
||||||
color: _bgGray,
|
color: _bgGray,
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
child: const Text(
|
child: Text(
|
||||||
invitationCode,
|
invitationCode,
|
||||||
style: TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: _darkText,
|
color: _darkText,
|
||||||
|
|
@ -309,7 +359,7 @@ class ProfilePage extends ConsumerWidget {
|
||||||
icon: Icons.copy,
|
icon: Icons.copy,
|
||||||
label: '复制',
|
label: '复制',
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Clipboard.setData(const ClipboardData(text: invitationCode));
|
Clipboard.setData(ClipboardData(text: invitationCode));
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
content: Text('邀请码已复制'),
|
content: Text('邀请码已复制'),
|
||||||
|
|
@ -364,7 +414,7 @@ class ProfilePage extends ConsumerWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildAccountSettings(BuildContext context) {
|
Widget _buildAccountSettings(BuildContext context, UserState user) {
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
|
@ -393,11 +443,11 @@ class ProfilePage extends ConsumerWidget {
|
||||||
_buildSettingItem(
|
_buildSettingItem(
|
||||||
icon: Icons.verified_user,
|
icon: Icons.verified_user,
|
||||||
label: '实名认证',
|
label: '实名认证',
|
||||||
trailing: const Text(
|
trailing: Text(
|
||||||
'已认证',
|
user.isKycVerified ? '已认证' : '未认证',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: _green,
|
color: user.isKycVerified ? _green : _lightGray,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onTap: () {},
|
onTap: () {},
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
import '../../../core/constants/app_colors.dart';
|
import '../../../core/constants/app_colors.dart';
|
||||||
import '../../../core/utils/format_utils.dart';
|
import '../../../core/utils/format_utils.dart';
|
||||||
import '../../providers/mining_providers.dart';
|
import '../../../data/models/trade_order_model.dart';
|
||||||
|
import '../../../domain/entities/price_info.dart';
|
||||||
|
import '../../../domain/entities/market_overview.dart';
|
||||||
|
import '../../../domain/entities/trade_order.dart';
|
||||||
import '../../providers/user_providers.dart';
|
import '../../providers/user_providers.dart';
|
||||||
import '../../providers/trading_providers.dart';
|
import '../../providers/trading_providers.dart';
|
||||||
import '../../widgets/shimmer_loading.dart';
|
import '../../widgets/shimmer_loading.dart';
|
||||||
|
|
@ -28,60 +32,55 @@ class _TradingPageState extends ConsumerState<TradingPage> {
|
||||||
// 状态
|
// 状态
|
||||||
int _selectedTab = 1; // 0: 买入, 1: 卖出
|
int _selectedTab = 1; // 0: 买入, 1: 卖出
|
||||||
int _selectedTimeRange = 1; // 时间周期选择
|
int _selectedTimeRange = 1; // 时间周期选择
|
||||||
final _amountController = TextEditingController();
|
final _quantityController = TextEditingController();
|
||||||
|
final _priceController = TextEditingController();
|
||||||
|
|
||||||
final List<String> _timeRanges = ['1分', '5分', '15分', '30分', '1时', '4时', '日'];
|
final List<String> _timeRanges = ['1分', '5分', '15分', '30分', '1时', '4时', '日'];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_amountController.dispose();
|
_quantityController.dispose();
|
||||||
|
_priceController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final globalState = ref.watch(globalStateProvider);
|
final priceAsync = ref.watch(currentPriceProvider);
|
||||||
|
final marketAsync = ref.watch(marketOverviewProvider);
|
||||||
|
final ordersAsync = ref.watch(ordersProvider);
|
||||||
final user = ref.watch(userNotifierProvider);
|
final user = ref.watch(userNotifierProvider);
|
||||||
final accountSequence = user.accountSequence ?? '';
|
final accountSequence = user.accountSequence ?? '';
|
||||||
|
|
||||||
// Extract loading state and data using shimmer placeholder approach
|
|
||||||
final isLoading = globalState.isLoading;
|
|
||||||
final state = globalState.valueOrNull;
|
|
||||||
final hasError = globalState.hasError;
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: const Color(0xFFF5F5F5),
|
backgroundColor: const Color(0xFFF5F5F5),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
bottom: false,
|
bottom: false,
|
||||||
child: Column(
|
child: RefreshIndicator(
|
||||||
children: [
|
onRefresh: () async {
|
||||||
// 顶部导航栏
|
ref.invalidate(currentPriceProvider);
|
||||||
_buildAppBar(),
|
ref.invalidate(marketOverviewProvider);
|
||||||
// 可滚动内容
|
ref.invalidate(ordersProvider);
|
||||||
Expanded(
|
},
|
||||||
child: SingleChildScrollView(
|
child: Column(
|
||||||
child: Column(
|
children: [
|
||||||
children: [
|
_buildAppBar(),
|
||||||
// 价格卡片 - always render, use shimmer for loading
|
Expanded(
|
||||||
hasError
|
child: SingleChildScrollView(
|
||||||
? _buildErrorCard('价格加载失败')
|
child: Column(
|
||||||
: _buildPriceCard(state, isLoading),
|
children: [
|
||||||
// K线图占位区域
|
_buildPriceCard(priceAsync),
|
||||||
_buildChartSection(),
|
_buildChartSection(priceAsync),
|
||||||
// 市场数据 - always render, use shimmer for loading
|
_buildMarketDataCard(marketAsync),
|
||||||
hasError
|
_buildTradingPanel(priceAsync),
|
||||||
? _buildErrorCard('市场数据加载失败')
|
_buildMyOrdersCard(ordersAsync),
|
||||||
: _buildMarketDataCard(state, isLoading),
|
const SizedBox(height: 100),
|
||||||
// 买入/卖出交易面板
|
],
|
||||||
_buildTradingPanel(accountSequence),
|
),
|
||||||
// 我的挂单
|
|
||||||
_buildMyOrdersCard(),
|
|
||||||
const SizedBox(height: 100),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -89,12 +88,11 @@ class _TradingPageState extends ConsumerState<TradingPage> {
|
||||||
|
|
||||||
Widget _buildAppBar() {
|
Widget _buildAppBar() {
|
||||||
return Container(
|
return Container(
|
||||||
color: _bgGray.withOpacity(0.9),
|
color: _bgGray.withValues(alpha: 0.9),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
// 积分股交易按钮
|
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
|
@ -110,7 +108,6 @@ class _TradingPageState extends ConsumerState<TradingPage> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// 通知图标
|
|
||||||
Container(
|
Container(
|
||||||
width: 40,
|
width: 40,
|
||||||
height: 40,
|
height: 40,
|
||||||
|
|
@ -119,7 +116,7 @@ class _TradingPageState extends ConsumerState<TradingPage> {
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black.withOpacity(0.1),
|
color: Colors.black.withValues(alpha: 0.1),
|
||||||
blurRadius: 4,
|
blurRadius: 4,
|
||||||
offset: const Offset(0, 2),
|
offset: const Offset(0, 2),
|
||||||
),
|
),
|
||||||
|
|
@ -150,10 +147,17 @@ class _TradingPageState extends ConsumerState<TradingPage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildPriceCard(dynamic state, bool isLoading) {
|
Widget _buildPriceCard(AsyncValue<PriceInfo?> priceAsync) {
|
||||||
final isPriceUp = state?.isPriceUp ?? true;
|
final isLoading = priceAsync.isLoading;
|
||||||
final currentPrice = state?.currentPrice;
|
final priceInfo = priceAsync.valueOrNull;
|
||||||
final priceChange = state?.priceChange24h;
|
final hasError = priceAsync.hasError;
|
||||||
|
|
||||||
|
if (hasError && priceInfo == null) {
|
||||||
|
return _buildErrorCard('价格加载失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
final price = priceInfo?.price ?? '0';
|
||||||
|
final greenPoints = priceInfo?.greenPoints ?? '0';
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.all(16),
|
margin: const EdgeInsets.all(16),
|
||||||
|
|
@ -165,11 +169,10 @@ class _TradingPageState extends ConsumerState<TradingPage> {
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// 标题行
|
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
const Text(
|
||||||
'当前积分股价格',
|
'当前积分股价格',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
|
|
@ -178,23 +181,19 @@ class _TradingPageState extends ConsumerState<TradingPage> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
DataText(
|
DataText(
|
||||||
data: '= 156.00 绿积分',
|
data: priceInfo != null ? '= ${formatCompact(greenPoints)} 绿积分' : null,
|
||||||
isLoading: isLoading,
|
isLoading: isLoading,
|
||||||
placeholder: '= -- 绿积分',
|
placeholder: '= -- 绿积分',
|
||||||
style: TextStyle(
|
style: const TextStyle(fontSize: 12, color: _grayText),
|
||||||
fontSize: 12,
|
|
||||||
color: _grayText,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
// 价格和涨跌
|
|
||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
AmountText(
|
AmountText(
|
||||||
amount: currentPrice != null ? formatPrice(currentPrice) : null,
|
amount: priceInfo != null ? formatPrice(price) : null,
|
||||||
isLoading: isLoading,
|
isLoading: isLoading,
|
||||||
prefix: '\u00A5 ',
|
prefix: '\u00A5 ',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
|
|
@ -208,19 +207,15 @@ class _TradingPageState extends ConsumerState<TradingPage> {
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: _green.withOpacity(0.1),
|
color: _green.withValues(alpha: 0.1),
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
const Icon(Icons.trending_up, size: 16, color: _green),
|
||||||
isPriceUp ? Icons.trending_up : Icons.trending_down,
|
|
||||||
size: 16,
|
|
||||||
color: _green,
|
|
||||||
),
|
|
||||||
DataText(
|
DataText(
|
||||||
data: priceChange != null ? '+$priceChange%' : null,
|
data: isLoading ? null : '+0.00%',
|
||||||
isLoading: isLoading,
|
isLoading: isLoading,
|
||||||
placeholder: '+--.--%',
|
placeholder: '+--.--%',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
|
|
@ -239,7 +234,10 @@ class _TradingPageState extends ConsumerState<TradingPage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildChartSection() {
|
Widget _buildChartSection(AsyncValue<PriceInfo?> priceAsync) {
|
||||||
|
final priceInfo = priceAsync.valueOrNull;
|
||||||
|
final currentPrice = priceInfo?.price ?? '0.000000';
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
|
|
@ -249,7 +247,6 @@ class _TradingPageState extends ConsumerState<TradingPage> {
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
// K线图占位
|
|
||||||
Container(
|
Container(
|
||||||
height: 200,
|
height: 200,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
|
@ -258,12 +255,10 @@ class _TradingPageState extends ConsumerState<TradingPage> {
|
||||||
),
|
),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
// 模拟K线图
|
|
||||||
CustomPaint(
|
CustomPaint(
|
||||||
size: const Size(double.infinity, 200),
|
size: const Size(double.infinity, 200),
|
||||||
painter: _CandlestickPainter(),
|
painter: _CandlestickPainter(),
|
||||||
),
|
),
|
||||||
// 当前价格标签
|
|
||||||
Positioned(
|
Positioned(
|
||||||
right: 0,
|
right: 0,
|
||||||
top: 60,
|
top: 60,
|
||||||
|
|
@ -274,18 +269,15 @@ class _TradingPageState extends ConsumerState<TradingPage> {
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black.withOpacity(0.1),
|
color: Colors.black.withValues(alpha: 0.1),
|
||||||
blurRadius: 2,
|
blurRadius: 2,
|
||||||
offset: const Offset(0, 1),
|
offset: const Offset(0, 1),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
child: const Text(
|
child: Text(
|
||||||
'0.000156',
|
formatPrice(currentPrice),
|
||||||
style: TextStyle(
|
style: const TextStyle(fontSize: 10, color: Colors.white),
|
||||||
fontSize: 10,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -293,7 +285,6 @@ class _TradingPageState extends ConsumerState<TradingPage> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
// 时间周期选择
|
|
||||||
SingleChildScrollView(
|
SingleChildScrollView(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
child: Row(
|
child: Row(
|
||||||
|
|
@ -329,11 +320,14 @@ class _TradingPageState extends ConsumerState<TradingPage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildMarketDataCard(dynamic state, bool isLoading) {
|
Widget _buildMarketDataCard(AsyncValue<MarketOverview?> marketAsync) {
|
||||||
final sharesPool = state?.sharesPool;
|
final isLoading = marketAsync.isLoading;
|
||||||
final circulatingPool = state?.circulatingPool;
|
final market = marketAsync.valueOrNull;
|
||||||
final greenPointsPool = state?.greenPointsPool;
|
final hasError = marketAsync.hasError;
|
||||||
final blackHoleBurned = state?.blackHoleBurned;
|
|
||||||
|
if (hasError && market == null) {
|
||||||
|
return _buildErrorCard('市场数据加载失败');
|
||||||
|
}
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.all(16),
|
margin: const EdgeInsets.all(16),
|
||||||
|
|
@ -345,7 +339,6 @@ class _TradingPageState extends ConsumerState<TradingPage> {
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// 标题
|
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
|
|
@ -368,12 +361,11 @@ class _TradingPageState extends ConsumerState<TradingPage> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
// 第一行数据
|
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
_buildMarketDataItem(
|
_buildMarketDataItem(
|
||||||
'积分股池',
|
'总积分股',
|
||||||
sharesPool ?? '8,888,888,888',
|
market != null ? formatCompact(market.totalShares) : null,
|
||||||
_orange,
|
_orange,
|
||||||
isLoading,
|
isLoading,
|
||||||
),
|
),
|
||||||
|
|
@ -381,7 +373,7 @@ class _TradingPageState extends ConsumerState<TradingPage> {
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
_buildMarketDataItem(
|
_buildMarketDataItem(
|
||||||
'流通池',
|
'流通池',
|
||||||
circulatingPool ?? '1,234,567',
|
market != null ? formatCompact(market.circulationPool) : null,
|
||||||
_orange,
|
_orange,
|
||||||
isLoading,
|
isLoading,
|
||||||
),
|
),
|
||||||
|
|
@ -390,12 +382,11 @@ class _TradingPageState extends ConsumerState<TradingPage> {
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
Container(height: 1, color: _bgGray),
|
Container(height: 1, color: _bgGray),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
// 第二行数据
|
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
_buildMarketDataItem(
|
_buildMarketDataItem(
|
||||||
'绿积分池',
|
'绿积分池',
|
||||||
greenPointsPool ?? '99,999,999',
|
market != null ? formatCompact(market.greenPoints) : null,
|
||||||
_orange,
|
_orange,
|
||||||
isLoading,
|
isLoading,
|
||||||
),
|
),
|
||||||
|
|
@ -403,7 +394,7 @@ class _TradingPageState extends ConsumerState<TradingPage> {
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
_buildMarketDataItem(
|
_buildMarketDataItem(
|
||||||
'黑洞销毁量',
|
'黑洞销毁量',
|
||||||
blackHoleBurned ?? '50,000,000',
|
market != null ? formatCompact(market.blackHoleAmount) : null,
|
||||||
_red,
|
_red,
|
||||||
isLoading,
|
isLoading,
|
||||||
),
|
),
|
||||||
|
|
@ -414,18 +405,12 @@ class _TradingPageState extends ConsumerState<TradingPage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildMarketDataItem(String label, String value, Color valueColor, bool isLoading) {
|
Widget _buildMarketDataItem(String label, String? value, Color valueColor, bool isLoading) {
|
||||||
return Expanded(
|
return Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(label, style: const TextStyle(fontSize: 12, color: _grayText)),
|
||||||
label,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: _grayText,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
DataText(
|
DataText(
|
||||||
data: value,
|
data: value,
|
||||||
|
|
@ -442,7 +427,15 @@ class _TradingPageState extends ConsumerState<TradingPage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildTradingPanel(String accountSequence) {
|
Widget _buildTradingPanel(AsyncValue<PriceInfo?> priceAsync) {
|
||||||
|
final priceInfo = priceAsync.valueOrNull;
|
||||||
|
final currentPrice = priceInfo?.price ?? '0';
|
||||||
|
|
||||||
|
// 设置默认价格
|
||||||
|
if (_priceController.text.isEmpty && priceInfo != null) {
|
||||||
|
_priceController.text = currentPrice;
|
||||||
|
}
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.all(20),
|
||||||
|
|
@ -452,7 +445,6 @@ class _TradingPageState extends ConsumerState<TradingPage> {
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
// 买入/卖出切换
|
|
||||||
Container(
|
Container(
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
border: Border(bottom: BorderSide(color: _bgGray)),
|
border: Border(bottom: BorderSide(color: _bgGray)),
|
||||||
|
|
@ -513,71 +505,13 @@ class _TradingPageState extends ConsumerState<TradingPage> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
// 数量输入
|
// 价格输入
|
||||||
Column(
|
_buildInputField('价格', _priceController, '请输入价格', '绿积分'),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
'数量',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: _grayText,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Container(
|
|
||||||
height: 44,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: _bgGray,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: TextField(
|
|
||||||
controller: _amountController,
|
|
||||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
hintText: '请输入数量',
|
|
||||||
hintStyle: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: Color(0xFF9CA3AF),
|
|
||||||
),
|
|
||||||
border: InputBorder.none,
|
|
||||||
contentPadding: EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Icon(Icons.currency_yuan, size: 15, color: _grayText),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
// TODO: 设置最大数量
|
|
||||||
},
|
|
||||||
child: const Text(
|
|
||||||
'MAX',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: _orange,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
// 预计获得
|
// 数量输入
|
||||||
|
_buildInputField('数量', _quantityController, '请输入数量', '积分股'),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
// 预计获得/支出
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
|
@ -587,16 +521,13 @@ class _TradingPageState extends ConsumerState<TradingPage> {
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
const Text(
|
Text(
|
||||||
'预计获得',
|
_selectedTab == 0 ? '预计支出' : '预计获得',
|
||||||
style: TextStyle(
|
style: const TextStyle(fontSize: 12, color: _grayText),
|
||||||
fontSize: 12,
|
|
||||||
color: _grayText,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const Text(
|
Text(
|
||||||
'0.00 绿积分',
|
_calculateEstimate(),
|
||||||
style: TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: _orange,
|
color: _orange,
|
||||||
|
|
@ -606,37 +537,35 @@ class _TradingPageState extends ConsumerState<TradingPage> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
// 手续费
|
// 销毁说明 (卖出时显示)
|
||||||
Padding(
|
if (_selectedTab == 1)
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
Padding(
|
||||||
child: Row(
|
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
child: Row(
|
||||||
children: const [
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
Text(
|
children: const [
|
||||||
'手续费 (10%)',
|
Text(
|
||||||
style: TextStyle(
|
'销毁比例',
|
||||||
fontSize: 12,
|
style: TextStyle(fontSize: 12, color: _grayText),
|
||||||
color: _grayText,
|
|
||||||
),
|
),
|
||||||
),
|
Text(
|
||||||
Text(
|
'10% 进入黑洞',
|
||||||
'0.00',
|
style: TextStyle(
|
||||||
style: TextStyle(
|
fontSize: 12,
|
||||||
fontSize: 12,
|
color: _red,
|
||||||
color: _grayText,
|
fontFamily: 'monospace',
|
||||||
fontFamily: 'monospace',
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
// 提交按钮
|
// 提交按钮
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 48,
|
height: 48,
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: () => _handleTrade(accountSequence),
|
onPressed: _handleTrade,
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: _orange,
|
backgroundColor: _orange,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
|
|
@ -658,7 +587,79 @@ class _TradingPageState extends ConsumerState<TradingPage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildMyOrdersCard() {
|
Widget _buildInputField(
|
||||||
|
String label,
|
||||||
|
TextEditingController controller,
|
||||||
|
String hint,
|
||||||
|
String suffix,
|
||||||
|
) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: _grayText,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Container(
|
||||||
|
height: 44,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: _bgGray,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
controller: controller,
|
||||||
|
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: hint,
|
||||||
|
hintStyle: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Color(0xFF9CA3AF),
|
||||||
|
),
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(suffix, style: const TextStyle(fontSize: 12, color: _grayText)),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _calculateEstimate() {
|
||||||
|
final price = double.tryParse(_priceController.text) ?? 0;
|
||||||
|
final quantity = double.tryParse(_quantityController.text) ?? 0;
|
||||||
|
final total = price * quantity;
|
||||||
|
|
||||||
|
if (total == 0) {
|
||||||
|
return '0.00 绿积分';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_selectedTab == 1) {
|
||||||
|
// 卖出时扣除10%销毁
|
||||||
|
final afterBurn = total * 0.9;
|
||||||
|
return '${formatAmount(afterBurn.toString())} 绿积分';
|
||||||
|
}
|
||||||
|
|
||||||
|
return '${formatAmount(total.toString())} 绿积分';
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMyOrdersCard(AsyncValue<OrdersPageModel?> ordersAsync) {
|
||||||
|
final isLoading = ordersAsync.isLoading;
|
||||||
|
final ordersPage = ordersAsync.valueOrNull;
|
||||||
|
final orders = ordersPage?.data ?? [];
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.all(16),
|
margin: const EdgeInsets.all(16),
|
||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.all(20),
|
||||||
|
|
@ -668,7 +669,6 @@ class _TradingPageState extends ConsumerState<TradingPage> {
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
// 标题
|
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -696,27 +696,54 @@ class _TradingPageState extends ConsumerState<TradingPage> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
// 挂单列表项
|
if (isLoading)
|
||||||
_buildOrderItem(
|
const Center(
|
||||||
type: '卖出',
|
child: Padding(
|
||||||
price: '0.000156',
|
padding: EdgeInsets.all(20),
|
||||||
quantity: '1,000 股',
|
child: CircularProgressIndicator(color: _orange),
|
||||||
time: '12/05 14:30',
|
),
|
||||||
status: '待成交',
|
)
|
||||||
),
|
else if (orders.isEmpty)
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 20),
|
||||||
|
child: Text(
|
||||||
|
'暂无挂单',
|
||||||
|
style: TextStyle(fontSize: 14, color: _grayText),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Column(
|
||||||
|
children: orders.take(3).map((order) => Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 8),
|
||||||
|
child: _buildOrderItemFromEntity(order),
|
||||||
|
)).toList(),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildOrderItem({
|
Widget _buildOrderItemFromEntity(TradeOrder order) {
|
||||||
required String type,
|
final isSell = order.isSell;
|
||||||
required String price,
|
final dateFormat = DateFormat('MM/dd HH:mm');
|
||||||
required String quantity,
|
final formattedDate = dateFormat.format(order.createdAt);
|
||||||
required String time,
|
|
||||||
required String status,
|
String statusText;
|
||||||
}) {
|
switch (order.status) {
|
||||||
final isSell = type == '卖出';
|
case OrderStatus.pending:
|
||||||
|
statusText = '待成交';
|
||||||
|
break;
|
||||||
|
case OrderStatus.partial:
|
||||||
|
statusText = '部分成交';
|
||||||
|
break;
|
||||||
|
case OrderStatus.filled:
|
||||||
|
statusText = '已成交';
|
||||||
|
break;
|
||||||
|
case OrderStatus.cancelled:
|
||||||
|
statusText = '已取消';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
|
@ -735,11 +762,11 @@ class _TradingPageState extends ConsumerState<TradingPage> {
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: (isSell ? _red : _green).withOpacity(0.1),
|
color: (isSell ? _red : _green).withValues(alpha: 0.1),
|
||||||
borderRadius: BorderRadius.circular(6),
|
borderRadius: BorderRadius.circular(6),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
type,
|
isSell ? '卖出' : '买入',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
|
@ -749,7 +776,7 @@ class _TradingPageState extends ConsumerState<TradingPage> {
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
price,
|
formatPrice(order.price),
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
|
@ -760,19 +787,16 @@ class _TradingPageState extends ConsumerState<TradingPage> {
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
time,
|
formattedDate,
|
||||||
style: const TextStyle(
|
style: const TextStyle(fontSize: 12, color: _grayText),
|
||||||
fontSize: 12,
|
|
||||||
color: _grayText,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
quantity,
|
'${formatCompact(order.quantity)} 股',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
|
|
@ -783,15 +807,12 @@ class _TradingPageState extends ConsumerState<TradingPage> {
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: _orange.withOpacity(0.1),
|
color: _orange.withValues(alpha: 0.1),
|
||||||
borderRadius: BorderRadius.circular(9999),
|
borderRadius: BorderRadius.circular(9999),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
status,
|
statusText,
|
||||||
style: const TextStyle(
|
style: const TextStyle(fontSize: 12, color: _orange),
|
||||||
fontSize: 12,
|
|
||||||
color: _orange,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -821,8 +842,15 @@ class _TradingPageState extends ConsumerState<TradingPage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleTrade(String accountSequence) async {
|
void _handleTrade() async {
|
||||||
if (_amountController.text.isEmpty) {
|
if (_priceController.text.isEmpty) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(content: Text('请输入价格')),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_quantityController.text.isEmpty) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(content: Text('请输入数量')),
|
const SnackBar(content: Text('请输入数量')),
|
||||||
);
|
);
|
||||||
|
|
@ -835,11 +863,11 @@ class _TradingPageState extends ConsumerState<TradingPage> {
|
||||||
if (isBuy) {
|
if (isBuy) {
|
||||||
success = await ref
|
success = await ref
|
||||||
.read(tradingNotifierProvider.notifier)
|
.read(tradingNotifierProvider.notifier)
|
||||||
.buyShares(accountSequence, _amountController.text);
|
.buyShares(_priceController.text, _quantityController.text);
|
||||||
} else {
|
} else {
|
||||||
success = await ref
|
success = await ref
|
||||||
.read(tradingNotifierProvider.notifier)
|
.read(tradingNotifierProvider.notifier)
|
||||||
.sellShares(accountSequence, _amountController.text);
|
.sellShares(_priceController.text, _quantityController.text);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
|
|
@ -852,10 +880,11 @@ class _TradingPageState extends ConsumerState<TradingPage> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (success) {
|
if (success) {
|
||||||
_amountController.clear();
|
_quantityController.clear();
|
||||||
// 交易成功后刷新所有相关数据
|
// 交易成功后刷新订单列表
|
||||||
ref.invalidate(shareAccountProvider(accountSequence));
|
ref.invalidate(ordersProvider);
|
||||||
ref.invalidate(globalStateProvider);
|
ref.invalidate(currentPriceProvider);
|
||||||
|
ref.invalidate(marketOverviewProvider);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -889,7 +918,7 @@ class _CandlestickPainter extends CustomPainter {
|
||||||
];
|
];
|
||||||
|
|
||||||
final candleWidth = (size.width - 40) / candleData.length;
|
final candleWidth = (size.width - 40) / candleData.length;
|
||||||
final padding = 20.0;
|
const padding = 20.0;
|
||||||
|
|
||||||
for (int i = 0; i < candleData.length; i++) {
|
for (int i = 0; i < candleData.length; i++) {
|
||||||
final data = candleData[i];
|
final data = candleData[i];
|
||||||
|
|
@ -907,14 +936,12 @@ class _CandlestickPainter extends CustomPainter {
|
||||||
final yHigh = size.height - (high * size.height * 0.8 + size.height * 0.1);
|
final yHigh = size.height - (high * size.height * 0.8 + size.height * 0.1);
|
||||||
final yLow = size.height - (low * size.height * 0.8 + size.height * 0.1);
|
final yLow = size.height - (low * size.height * 0.8 + size.height * 0.1);
|
||||||
|
|
||||||
// 绘制影线
|
|
||||||
canvas.drawLine(
|
canvas.drawLine(
|
||||||
Offset(x, yHigh),
|
Offset(x, yHigh),
|
||||||
Offset(x, yLow),
|
Offset(x, yLow),
|
||||||
paint..strokeWidth = 1,
|
paint..strokeWidth = 1,
|
||||||
);
|
);
|
||||||
|
|
||||||
// 绘制实体
|
|
||||||
final bodyTop = isGreen ? yClose : yOpen;
|
final bodyTop = isGreen ? yClose : yOpen;
|
||||||
final bodyBottom = isGreen ? yOpen : yClose;
|
final bodyBottom = isGreen ? yOpen : yClose;
|
||||||
canvas.drawRect(
|
canvas.drawRect(
|
||||||
|
|
@ -923,7 +950,6 @@ class _CandlestickPainter extends CustomPainter {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 绘制虚线参考线
|
|
||||||
final dashY = size.height * 0.35;
|
final dashY = size.height * 0.35;
|
||||||
const dashWidth = 5.0;
|
const dashWidth = 5.0;
|
||||||
const dashSpace = 3.0;
|
const dashSpace = 3.0;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import '../../domain/entities/asset_display.dart';
|
||||||
|
import '../../domain/repositories/trading_repository.dart';
|
||||||
|
import '../../core/di/injection.dart';
|
||||||
|
import 'trading_providers.dart';
|
||||||
|
|
||||||
|
/// 我的资产显示 Provider (需要登录,使用 JWT token)
|
||||||
|
final myAssetProvider = FutureProvider<AssetDisplay?>((ref) async {
|
||||||
|
final repository = ref.watch(tradingRepositoryProvider);
|
||||||
|
final result = await repository.getMyAsset();
|
||||||
|
|
||||||
|
ref.keepAlive();
|
||||||
|
final timer = Timer(const Duration(minutes: 2), () {
|
||||||
|
ref.invalidateSelf();
|
||||||
|
});
|
||||||
|
ref.onDispose(() => timer.cancel());
|
||||||
|
|
||||||
|
return result.fold(
|
||||||
|
(failure) => throw Exception(failure.message),
|
||||||
|
(asset) => asset,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
/// 指定账户资产显示 Provider (public API)
|
||||||
|
final accountAssetProvider = FutureProvider.family<AssetDisplay?, String>(
|
||||||
|
(ref, accountSequence) async {
|
||||||
|
if (accountSequence.isEmpty) return null;
|
||||||
|
|
||||||
|
final repository = ref.watch(tradingRepositoryProvider);
|
||||||
|
final result = await repository.getAccountAsset(accountSequence);
|
||||||
|
|
||||||
|
ref.keepAlive();
|
||||||
|
final timer = Timer(const Duration(minutes: 2), () {
|
||||||
|
ref.invalidateSelf();
|
||||||
|
});
|
||||||
|
ref.onDispose(() => timer.cancel());
|
||||||
|
|
||||||
|
return result.fold(
|
||||||
|
(failure) => throw Exception(failure.message),
|
||||||
|
(asset) => asset,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/// 资产显示值计算器 - 用于实时显示资产估值
|
||||||
|
/// displayAssetValue = (账户积分股 + 账户积分股 × 倍数) × 积分股价
|
||||||
|
class AssetValueCalculator {
|
||||||
|
final String shareBalance;
|
||||||
|
final String burnMultiplier;
|
||||||
|
final String currentPrice;
|
||||||
|
|
||||||
|
AssetValueCalculator({
|
||||||
|
required this.shareBalance,
|
||||||
|
required this.burnMultiplier,
|
||||||
|
required this.currentPrice,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// 计算有效积分股 = 余额 × (1 + 倍数)
|
||||||
|
double get effectiveShares {
|
||||||
|
final balance = double.tryParse(shareBalance) ?? 0;
|
||||||
|
final multiplier = double.tryParse(burnMultiplier) ?? 0;
|
||||||
|
return balance * (1 + multiplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 计算资产显示值 = 有效积分股 × 价格
|
||||||
|
double get displayValue {
|
||||||
|
final price = double.tryParse(currentPrice) ?? 0;
|
||||||
|
return effectiveShares * price;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 计算每秒增长值
|
||||||
|
static double calculateGrowthPerSecond(String dailyAllocation) {
|
||||||
|
final daily = double.tryParse(dailyAllocation) ?? 0;
|
||||||
|
return daily / 24 / 60 / 60;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import '../../domain/entities/contribution.dart';
|
||||||
|
import '../../domain/repositories/contribution_repository.dart';
|
||||||
|
import '../../core/di/injection.dart';
|
||||||
|
import 'user_providers.dart';
|
||||||
|
|
||||||
|
/// 用户统计数据 - 来自贡献值服务
|
||||||
|
class UserStats {
|
||||||
|
/// 是否已认种
|
||||||
|
final bool hasAdopted;
|
||||||
|
|
||||||
|
/// 直推认种用户数
|
||||||
|
final int directReferralAdoptedCount;
|
||||||
|
|
||||||
|
/// 已解锁层级深度(可理解为团队层数)
|
||||||
|
final int unlockedLevelDepth;
|
||||||
|
|
||||||
|
/// 已解锁奖励档位数(VIP等级)
|
||||||
|
final int unlockedBonusTiers;
|
||||||
|
|
||||||
|
/// 总贡献值
|
||||||
|
final String totalContribution;
|
||||||
|
|
||||||
|
const UserStats({
|
||||||
|
this.hasAdopted = false,
|
||||||
|
this.directReferralAdoptedCount = 0,
|
||||||
|
this.unlockedLevelDepth = 0,
|
||||||
|
this.unlockedBonusTiers = 0,
|
||||||
|
this.totalContribution = '0',
|
||||||
|
});
|
||||||
|
|
||||||
|
/// 从贡献值账户数据构建
|
||||||
|
factory UserStats.fromContribution(Contribution contribution) {
|
||||||
|
return UserStats(
|
||||||
|
hasAdopted: contribution.hasAdopted,
|
||||||
|
directReferralAdoptedCount: contribution.directReferralAdoptedCount,
|
||||||
|
unlockedLevelDepth: contribution.unlockedLevelDepth,
|
||||||
|
unlockedBonusTiers: contribution.unlockedBonusTiers,
|
||||||
|
totalContribution: contribution.totalContribution,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// VIP等级显示
|
||||||
|
String get vipLevel {
|
||||||
|
if (unlockedBonusTiers <= 0) return '-';
|
||||||
|
return 'V$unlockedBonusTiers';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 认种数量(简化展示直推数)
|
||||||
|
int get adoptionCount => hasAdopted ? 1 : 0;
|
||||||
|
|
||||||
|
/// 团队人数估算(基于已解锁层级)
|
||||||
|
int get teamSize {
|
||||||
|
// 简单估算:每层平均3人
|
||||||
|
if (unlockedLevelDepth <= 0) return 0;
|
||||||
|
int total = 0;
|
||||||
|
for (int i = 1; i <= unlockedLevelDepth; i++) {
|
||||||
|
total += (directReferralAdoptedCount > 0 ? directReferralAdoptedCount : 1) * i;
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 用户统计数据 Provider
|
||||||
|
final userStatsProvider = FutureProvider<UserStats?>((ref) async {
|
||||||
|
final user = ref.watch(userNotifierProvider);
|
||||||
|
if (!user.isLoggedIn || user.accountSequence == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final repository = getIt<ContributionRepository>();
|
||||||
|
final result = await repository.getUserContribution(user.accountSequence!);
|
||||||
|
|
||||||
|
ref.keepAlive();
|
||||||
|
final timer = Timer(const Duration(minutes: 5), () {
|
||||||
|
ref.invalidateSelf();
|
||||||
|
});
|
||||||
|
ref.onDispose(() => timer.cancel());
|
||||||
|
|
||||||
|
return result.fold(
|
||||||
|
(failure) => const UserStats(),
|
||||||
|
(contribution) => UserStats.fromContribution(contribution),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
/// 邀请码 Provider - 使用账户序号作为邀请码
|
||||||
|
final invitationCodeProvider = Provider<String>((ref) {
|
||||||
|
final user = ref.watch(userNotifierProvider);
|
||||||
|
return user.accountSequence ?? '--------';
|
||||||
|
});
|
||||||
|
|
@ -1,60 +1,131 @@
|
||||||
|
import 'dart:async';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import '../../domain/entities/price_info.dart';
|
||||||
|
import '../../domain/entities/market_overview.dart';
|
||||||
|
import '../../domain/entities/trading_account.dart';
|
||||||
import '../../domain/entities/trade_order.dart';
|
import '../../domain/entities/trade_order.dart';
|
||||||
import '../../domain/usecases/trading/buy_shares.dart';
|
import '../../domain/repositories/trading_repository.dart';
|
||||||
import '../../domain/usecases/trading/sell_shares.dart';
|
import '../../data/models/trade_order_model.dart';
|
||||||
import '../../core/di/injection.dart';
|
import '../../core/di/injection.dart';
|
||||||
|
|
||||||
// Use Cases Providers
|
// Repository Provider
|
||||||
final buySharesUseCaseProvider = Provider<BuyShares>((ref) {
|
final tradingRepositoryProvider = Provider<TradingRepository>((ref) {
|
||||||
return getIt<BuyShares>();
|
return getIt<TradingRepository>();
|
||||||
});
|
|
||||||
|
|
||||||
final sellSharesUseCaseProvider = Provider<SellShares>((ref) {
|
|
||||||
return getIt<SellShares>();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// K线周期选择
|
// K线周期选择
|
||||||
final selectedKlinePeriodProvider = StateProvider<String>((ref) => '1h');
|
final selectedKlinePeriodProvider = StateProvider<String>((ref) => '1h');
|
||||||
|
|
||||||
|
// 当前价格 Provider (5分钟缓存)
|
||||||
|
final currentPriceProvider = FutureProvider<PriceInfo?>((ref) async {
|
||||||
|
final repository = ref.watch(tradingRepositoryProvider);
|
||||||
|
final result = await repository.getCurrentPrice();
|
||||||
|
|
||||||
|
ref.keepAlive();
|
||||||
|
final timer = Timer(const Duration(minutes: 5), () {
|
||||||
|
ref.invalidateSelf();
|
||||||
|
});
|
||||||
|
ref.onDispose(() => timer.cancel());
|
||||||
|
|
||||||
|
return result.fold(
|
||||||
|
(failure) => throw Exception(failure.message),
|
||||||
|
(priceInfo) => priceInfo,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 市场概览 Provider (5分钟缓存)
|
||||||
|
final marketOverviewProvider = FutureProvider<MarketOverview?>((ref) async {
|
||||||
|
final repository = ref.watch(tradingRepositoryProvider);
|
||||||
|
final result = await repository.getMarketOverview();
|
||||||
|
|
||||||
|
ref.keepAlive();
|
||||||
|
final timer = Timer(const Duration(minutes: 5), () {
|
||||||
|
ref.invalidateSelf();
|
||||||
|
});
|
||||||
|
ref.onDispose(() => timer.cancel());
|
||||||
|
|
||||||
|
return result.fold(
|
||||||
|
(failure) => throw Exception(failure.message),
|
||||||
|
(overview) => overview,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 交易账户 Provider (带参数)
|
||||||
|
final tradingAccountProvider = FutureProvider.family<TradingAccount?, String>(
|
||||||
|
(ref, accountSequence) async {
|
||||||
|
if (accountSequence.isEmpty) return null;
|
||||||
|
|
||||||
|
final repository = ref.watch(tradingRepositoryProvider);
|
||||||
|
final result = await repository.getTradingAccount(accountSequence);
|
||||||
|
|
||||||
|
ref.keepAlive();
|
||||||
|
final timer = Timer(const Duration(minutes: 2), () {
|
||||||
|
ref.invalidateSelf();
|
||||||
|
});
|
||||||
|
ref.onDispose(() => timer.cancel());
|
||||||
|
|
||||||
|
return result.fold(
|
||||||
|
(failure) => throw Exception(failure.message),
|
||||||
|
(account) => account,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// 订单列表 Provider
|
||||||
|
final ordersProvider = FutureProvider<OrdersPageModel?>((ref) async {
|
||||||
|
final repository = ref.watch(tradingRepositoryProvider);
|
||||||
|
final result = await repository.getOrders(page: 1, pageSize: 10);
|
||||||
|
|
||||||
|
ref.keepAlive();
|
||||||
|
final timer = Timer(const Duration(minutes: 2), () {
|
||||||
|
ref.invalidateSelf();
|
||||||
|
});
|
||||||
|
ref.onDispose(() => timer.cancel());
|
||||||
|
|
||||||
|
return result.fold(
|
||||||
|
(failure) => throw Exception(failure.message),
|
||||||
|
(orders) => orders,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
// 交易状态
|
// 交易状态
|
||||||
class TradingState {
|
class TradingState {
|
||||||
final bool isLoading;
|
final bool isLoading;
|
||||||
final String? error;
|
final String? error;
|
||||||
final TradeOrder? lastOrder;
|
final Map<String, dynamic>? lastOrderResult;
|
||||||
|
|
||||||
TradingState({
|
TradingState({
|
||||||
this.isLoading = false,
|
this.isLoading = false,
|
||||||
this.error,
|
this.error,
|
||||||
this.lastOrder,
|
this.lastOrderResult,
|
||||||
});
|
});
|
||||||
|
|
||||||
TradingState copyWith({
|
TradingState copyWith({
|
||||||
bool? isLoading,
|
bool? isLoading,
|
||||||
String? error,
|
String? error,
|
||||||
TradeOrder? lastOrder,
|
Map<String, dynamic>? lastOrderResult,
|
||||||
}) {
|
}) {
|
||||||
return TradingState(
|
return TradingState(
|
||||||
isLoading: isLoading ?? this.isLoading,
|
isLoading: isLoading ?? this.isLoading,
|
||||||
error: error,
|
error: error,
|
||||||
lastOrder: lastOrder ?? this.lastOrder,
|
lastOrderResult: lastOrderResult ?? this.lastOrderResult,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TradingNotifier extends StateNotifier<TradingState> {
|
class TradingNotifier extends StateNotifier<TradingState> {
|
||||||
final BuyShares buySharesUseCase;
|
final TradingRepository repository;
|
||||||
final SellShares sellSharesUseCase;
|
|
||||||
|
|
||||||
TradingNotifier({
|
TradingNotifier({required this.repository}) : super(TradingState());
|
||||||
required this.buySharesUseCase,
|
|
||||||
required this.sellSharesUseCase,
|
|
||||||
}) : super(TradingState());
|
|
||||||
|
|
||||||
Future<bool> buyShares(String accountSequence, String amount) async {
|
/// 创建买入订单
|
||||||
|
Future<bool> buyShares(String price, String quantity) async {
|
||||||
state = state.copyWith(isLoading: true, error: null);
|
state = state.copyWith(isLoading: true, error: null);
|
||||||
|
|
||||||
final result = await buySharesUseCase(
|
final result = await repository.createOrder(
|
||||||
BuySharesParams(accountSequence: accountSequence, amount: amount),
|
type: 'BUY',
|
||||||
|
price: price,
|
||||||
|
quantity: quantity,
|
||||||
);
|
);
|
||||||
|
|
||||||
return result.fold(
|
return result.fold(
|
||||||
|
|
@ -62,18 +133,21 @@ class TradingNotifier extends StateNotifier<TradingState> {
|
||||||
state = state.copyWith(isLoading: false, error: failure.message);
|
state = state.copyWith(isLoading: false, error: failure.message);
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
(order) {
|
(orderResult) {
|
||||||
state = state.copyWith(isLoading: false, lastOrder: order);
|
state = state.copyWith(isLoading: false, lastOrderResult: orderResult);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> sellShares(String accountSequence, String amount) async {
|
/// 创建卖出订单
|
||||||
|
Future<bool> sellShares(String price, String quantity) async {
|
||||||
state = state.copyWith(isLoading: true, error: null);
|
state = state.copyWith(isLoading: true, error: null);
|
||||||
|
|
||||||
final result = await sellSharesUseCase(
|
final result = await repository.createOrder(
|
||||||
SellSharesParams(accountSequence: accountSequence, amount: amount),
|
type: 'SELL',
|
||||||
|
price: price,
|
||||||
|
quantity: quantity,
|
||||||
);
|
);
|
||||||
|
|
||||||
return result.fold(
|
return result.fold(
|
||||||
|
|
@ -81,8 +155,62 @@ class TradingNotifier extends StateNotifier<TradingState> {
|
||||||
state = state.copyWith(isLoading: false, error: failure.message);
|
state = state.copyWith(isLoading: false, error: failure.message);
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
(order) {
|
(orderResult) {
|
||||||
state = state.copyWith(isLoading: false, lastOrder: order);
|
state = state.copyWith(isLoading: false, lastOrderResult: orderResult);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 取消订单
|
||||||
|
Future<bool> cancelOrder(String orderNo) async {
|
||||||
|
state = state.copyWith(isLoading: true, error: null);
|
||||||
|
|
||||||
|
final result = await repository.cancelOrder(orderNo);
|
||||||
|
|
||||||
|
return result.fold(
|
||||||
|
(failure) {
|
||||||
|
state = state.copyWith(isLoading: false, error: failure.message);
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
(_) {
|
||||||
|
state = state.copyWith(isLoading: false);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 划入积分股
|
||||||
|
Future<bool> transferIn(String amount) async {
|
||||||
|
state = state.copyWith(isLoading: true, error: null);
|
||||||
|
|
||||||
|
final result = await repository.transferIn(amount);
|
||||||
|
|
||||||
|
return result.fold(
|
||||||
|
(failure) {
|
||||||
|
state = state.copyWith(isLoading: false, error: failure.message);
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
(_) {
|
||||||
|
state = state.copyWith(isLoading: false);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 划出积分股
|
||||||
|
Future<bool> transferOut(String amount) async {
|
||||||
|
state = state.copyWith(isLoading: true, error: null);
|
||||||
|
|
||||||
|
final result = await repository.transferOut(amount);
|
||||||
|
|
||||||
|
return result.fold(
|
||||||
|
(failure) {
|
||||||
|
state = state.copyWith(isLoading: false, error: failure.message);
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
(_) {
|
||||||
|
state = state.copyWith(isLoading: false);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -95,7 +223,6 @@ class TradingNotifier extends StateNotifier<TradingState> {
|
||||||
|
|
||||||
final tradingNotifierProvider = StateNotifierProvider<TradingNotifier, TradingState>(
|
final tradingNotifierProvider = StateNotifierProvider<TradingNotifier, TradingState>(
|
||||||
(ref) => TradingNotifier(
|
(ref) => TradingNotifier(
|
||||||
buySharesUseCase: ref.watch(buySharesUseCaseProvider),
|
repository: ref.watch(tradingRepositoryProvider),
|
||||||
sellSharesUseCase: ref.watch(sellSharesUseCaseProvider),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,10 @@ class UserState {
|
||||||
final String? phone;
|
final String? phone;
|
||||||
final String? kycStatus;
|
final String? kycStatus;
|
||||||
final String? source;
|
final String? source;
|
||||||
|
final String? status;
|
||||||
|
final String? realName;
|
||||||
|
final DateTime? createdAt;
|
||||||
|
final DateTime? lastLoginAt;
|
||||||
final String? accessToken;
|
final String? accessToken;
|
||||||
final String? refreshToken;
|
final String? refreshToken;
|
||||||
final bool isLoggedIn;
|
final bool isLoggedIn;
|
||||||
|
|
@ -21,6 +25,10 @@ class UserState {
|
||||||
this.phone,
|
this.phone,
|
||||||
this.kycStatus,
|
this.kycStatus,
|
||||||
this.source,
|
this.source,
|
||||||
|
this.status,
|
||||||
|
this.realName,
|
||||||
|
this.createdAt,
|
||||||
|
this.lastLoginAt,
|
||||||
this.accessToken,
|
this.accessToken,
|
||||||
this.refreshToken,
|
this.refreshToken,
|
||||||
this.isLoggedIn = false,
|
this.isLoggedIn = false,
|
||||||
|
|
@ -28,12 +36,18 @@ class UserState {
|
||||||
this.error,
|
this.error,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
bool get isKycVerified => kycStatus == 'VERIFIED';
|
||||||
|
|
||||||
UserState copyWith({
|
UserState copyWith({
|
||||||
String? accountSequence,
|
String? accountSequence,
|
||||||
String? nickname,
|
String? nickname,
|
||||||
String? phone,
|
String? phone,
|
||||||
String? kycStatus,
|
String? kycStatus,
|
||||||
String? source,
|
String? source,
|
||||||
|
String? status,
|
||||||
|
String? realName,
|
||||||
|
DateTime? createdAt,
|
||||||
|
DateTime? lastLoginAt,
|
||||||
String? accessToken,
|
String? accessToken,
|
||||||
String? refreshToken,
|
String? refreshToken,
|
||||||
bool? isLoggedIn,
|
bool? isLoggedIn,
|
||||||
|
|
@ -46,6 +60,10 @@ class UserState {
|
||||||
phone: phone ?? this.phone,
|
phone: phone ?? this.phone,
|
||||||
kycStatus: kycStatus ?? this.kycStatus,
|
kycStatus: kycStatus ?? this.kycStatus,
|
||||||
source: source ?? this.source,
|
source: source ?? this.source,
|
||||||
|
status: status ?? this.status,
|
||||||
|
realName: realName ?? this.realName,
|
||||||
|
createdAt: createdAt ?? this.createdAt,
|
||||||
|
lastLoginAt: lastLoginAt ?? this.lastLoginAt,
|
||||||
accessToken: accessToken ?? this.accessToken,
|
accessToken: accessToken ?? this.accessToken,
|
||||||
refreshToken: refreshToken ?? this.refreshToken,
|
refreshToken: refreshToken ?? this.refreshToken,
|
||||||
isLoggedIn: isLoggedIn ?? this.isLoggedIn,
|
isLoggedIn: isLoggedIn ?? this.isLoggedIn,
|
||||||
|
|
@ -77,6 +95,8 @@ class UserNotifier extends StateNotifier<UserState> {
|
||||||
phone: phone,
|
phone: phone,
|
||||||
isLoggedIn: true,
|
isLoggedIn: true,
|
||||||
);
|
);
|
||||||
|
// 登录后自动获取用户详情
|
||||||
|
await fetchProfile();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -143,6 +163,8 @@ class UserNotifier extends StateNotifier<UserState> {
|
||||||
isLoggedIn: true,
|
isLoggedIn: true,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
);
|
);
|
||||||
|
// 登录后获取详细信息
|
||||||
|
await fetchProfile();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
state = state.copyWith(isLoading: false, error: e.toString());
|
state = state.copyWith(isLoading: false, error: e.toString());
|
||||||
rethrow;
|
rethrow;
|
||||||
|
|
@ -164,6 +186,8 @@ class UserNotifier extends StateNotifier<UserState> {
|
||||||
isLoggedIn: true,
|
isLoggedIn: true,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
);
|
);
|
||||||
|
// 登录后获取详细信息
|
||||||
|
await fetchProfile();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
state = state.copyWith(isLoading: false, error: e.toString());
|
state = state.copyWith(isLoading: false, error: e.toString());
|
||||||
rethrow;
|
rethrow;
|
||||||
|
|
@ -219,6 +243,24 @@ class UserNotifier extends StateNotifier<UserState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 获取用户详细信息
|
||||||
|
Future<void> fetchProfile() async {
|
||||||
|
if (!state.isLoggedIn) return;
|
||||||
|
try {
|
||||||
|
final profile = await _authDataSource.getProfile();
|
||||||
|
state = state.copyWith(
|
||||||
|
phone: profile.phone,
|
||||||
|
kycStatus: profile.kycStatus,
|
||||||
|
source: profile.source,
|
||||||
|
status: profile.status,
|
||||||
|
realName: profile.realName,
|
||||||
|
createdAt: profile.createdAt,
|
||||||
|
lastLoginAt: profile.lastLoginAt,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
// 静默失败,不影响用户体验
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final userNotifierProvider = StateNotifierProvider<UserNotifier, UserState>(
|
final userNotifierProvider = StateNotifierProvider<UserNotifier, UserState>(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue