fix:参考金值,研听,研值工程通用通用工程公共能力,重点对比网络请求、登录认证、状态管理、本地存储、WebView、推送、版本更新、一键登录、主题/UI 等模块,确定后续统一整改方案

This commit is contained in:
jingyun committed 2026-09-10 09:52:36 +08:00
1 parent 6997a65c96
commit d364c6d908
46 files changed
+2060 -395

No files matched your search

@@ -1,16 +1,29 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import '../../../common/domain/metal_type.dart';
import '../../auth/application/auth_controller.dart';
import '../../auth/data/auth_models.dart';
import '../../market/data/mock_market_repository.dart';
import '../data/asset_models.dart';
import '../data/mock_asset_repository.dart';
final assetPortfolioControllerProvider =
StateNotifierProvider<AssetPortfolioController, PortfolioSummary>((ref) {
final holdings = ref.watch(mockAssetRepositoryProvider).loadHoldings();
final repository = ref.watch(mockAssetRepositoryProvider);
final authState = ref.watch(authProvider).valueOrNull;
final accountScope = switch (authState) {
LoggedInAuthState(:final user) => MockAssetRepository.accountScope(
userId: user.id,
phone: user.phone,
),
_ => MockAssetRepository.guestAccountScope,
};
final holdings = repository.loadHoldings(accountScope);
final market = ref.watch(mockMarketRepositoryProvider);
return AssetPortfolioController(
holdings: holdings,
repository: repository,
accountScope: accountScope,
spotPriceFor: (metal) => market.quoteFor(metal).spotPrice,
changeFor: (metal) => market.quoteFor(metal).changeAmount,
);
@@ -19,10 +32,14 @@ final assetPortfolioControllerProvider =
class AssetPortfolioController extends StateNotifier<PortfolioSummary> {
AssetPortfolioController({
required List<GoldAssetHolding> holdings,
required MockAssetRepository repository,
required String accountScope,
required double Function(MetalType metal) spotPriceFor,
required double Function(MetalType metal) changeFor,
this.recycleDiscount = 0.97,
}) : _holdings = holdings,
_repository = repository,
_accountScope = accountScope,
_spotPriceFor = spotPriceFor,
_changeFor = changeFor,
super(
@@ -35,12 +52,25 @@ class AssetPortfolioController extends StateNotifier<PortfolioSummary> {
);
final List<GoldAssetHolding> _holdings;
final MockAssetRepository _repository;
final String _accountScope;
final double Function(MetalType metal) _spotPriceFor;
final double Function(MetalType metal) _changeFor;
final double recycleDiscount;
void addHolding(GoldAssetHolding holding) {
Future<void> addHolding(GoldAssetHolding holding) async {
_holdings.add(holding);
await _repository.saveHoldings(_accountScope, _holdings);
state = _buildSummary(
holdings: _holdings,
spotPriceFor: _spotPriceFor,
changeFor: _changeFor,
recycleDiscount: recycleDiscount,
);
}
void clearHoldings() {
_holdings.clear();
state = _buildSummary(
holdings: _holdings,
spotPriceFor: _spotPriceFor,
@@ -51,6 +51,45 @@ class GoldAssetHolding {
final HoldingStatus status;
double materialValue(double spotPrice) => weightGram * purity * spotPrice;
Map<String, Object?> toJson() => {
'id': id,
'name': name,
'metal': metal.name,
'category': category.name,
'purity': purity,
'purityLabel': purityLabel,
'weightGram': weightGram,
'costAmount': costAmount,
'purchaseDate': purchaseDate?.toIso8601String(),
'channel': channel,
'note': note,
'createdAt': createdAt.toIso8601String(),
'updatedAt': updatedAt.toIso8601String(),
'status': status.name,
};
factory GoldAssetHolding.fromJson(Map<String, Object?> json) {
return GoldAssetHolding(
id: json['id']! as String,
name: json['name']! as String,
metal: MetalType.values.byName(json['metal']! as String),
category: AssetCategory.values.byName(json['category']! as String),
purity: (json['purity']! as num).toDouble(),
purityLabel: json['purityLabel']! as String,
weightGram: (json['weightGram']! as num).toDouble(),
costAmount: (json['costAmount'] as num?)?.toDouble(),
purchaseDate: switch (json['purchaseDate']) {
final String value when value.isNotEmpty => DateTime.parse(value),
_ => null,
},
channel: json['channel'] as String?,
note: json['note'] as String?,
createdAt: DateTime.parse(json['createdAt']! as String),
updatedAt: DateTime.parse(json['updatedAt']! as String),
status: HoldingStatus.values.byName(json['status']! as String),
);
}
}
class HoldingValuation {
@@ -1,14 +1,91 @@
import 'dart:convert';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import '../../../common/domain/metal_type.dart';
import '../../../common/storage/preferences_storage.dart';
import '../../../common/storage/storage_keys.dart';
import 'asset_models.dart';
final mockAssetRepositoryProvider = Provider<MockAssetRepository>(
(ref) => MockAssetRepository(),
(ref) => MockAssetRepository(ref.watch(preferencesStorageProvider)),
);
class MockAssetRepository {
List<GoldAssetHolding> loadHoldings() {
MockAssetRepository(this._preferences);
static const guestAccountScope = 'guest';
final PreferencesStorage _preferences;
Future<void> _operationTail = Future<void>.value();
static String accountScope({String? userId, String? phone}) {
final normalizedUserId = userId?.trim() ?? '';
if (normalizedUserId.isNotEmpty) return 'id:$normalizedUserId';
final normalizedPhone = phone?.trim() ?? '';
if (normalizedPhone.isNotEmpty) return 'phone:$normalizedPhone';
return guestAccountScope;
}
static String holdingsStorageKey(String accountScope) =>
'$storageJinzhiHoldings:${Uri.encodeComponent(accountScope)}';
static String deletedStorageKey(String accountScope) =>
'$storageJinzhiHoldingsDeleted:${Uri.encodeComponent(accountScope)}';
List<GoldAssetHolding> loadHoldings(String accountScope) {
if (_preferences.getBool(deletedStorageKey(accountScope))) return [];
final encoded = _preferences.getString(holdingsStorageKey(accountScope));
if (encoded == null || encoded.isEmpty) return _seedHoldings();
try {
final decoded = jsonDecode(encoded);
if (decoded is! List) return _seedHoldings();
return [
for (final item in decoded)
GoldAssetHolding.fromJson(Map<String, Object?>.from(item as Map)),
];
} catch (_) {
return _seedHoldings();
}
}
Future<void> saveHoldings(
String accountScope,
List<GoldAssetHolding> holdings,
) async {
final encoded = jsonEncode([
for (final holding in holdings) holding.toJson(),
]);
await _serialize(() async {
await _preferences.setString(holdingsStorageKey(accountScope), encoded);
await _preferences.remove(deletedStorageKey(accountScope));
});
}
Future<void> deleteAccountHoldings(String accountScope) async {
await _serialize(() async {
final scopes = {accountScope, guestAccountScope};
await Future.wait([
for (final scope in scopes) ...[
_preferences.remove(holdingsStorageKey(scope)),
_preferences.setBool(deletedStorageKey(scope), true),
],
// Remove the pre-account-scoping key left by earlier builds.
_preferences.remove(storageJinzhiHoldings),
]);
});
}
Future<T> _serialize<T>(Future<T> Function() operation) {
final result = _operationTail.then((_) => operation());
_operationTail = result.then<void>(
(_) {},
onError: (Object _, StackTrace _) {},
);
return result;
}
List<GoldAssetHolding> _seedHoldings() {
final now = DateTime(2026, 6, 18, 16, 11);
return [
GoldAssetHolding(