-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* fix: add caching balances * fix: balances logging * fix: update money_improver
- Loading branch information
Showing
16 changed files
with
305 additions
and
26 deletions.
There are no files selected for viewing
159 changes: 159 additions & 0 deletions
159
lib/app/service/storage_service/balance_storage_service.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
import 'dart:convert'; | ||
|
||
import 'package:app/app/service/service.dart'; | ||
import 'package:app/data/models/models.dart'; | ||
import 'package:encrypted_storage/encrypted_storage.dart'; | ||
import 'package:injectable/injectable.dart'; | ||
import 'package:logging/logging.dart'; | ||
import 'package:nekoton_repository/nekoton_repository.dart'; | ||
import 'package:rxdart/rxdart.dart'; | ||
|
||
const _overallBalancesDomain = 'overallBalancesDomain'; | ||
const _balancesDomain = 'balancesDomain'; | ||
|
||
@singleton | ||
class BalanceStorageService extends AbstractStorageService { | ||
BalanceStorageService(this._storage); | ||
|
||
static final _logger = Logger('BalanceStorageService'); | ||
|
||
/// Storage that is used to store data | ||
final EncryptedStorage _storage; | ||
|
||
@override | ||
Future<void> init() => Future.wait([ | ||
_streamedOverallBalance(), | ||
_streamedBalance(), | ||
]); | ||
|
||
@override | ||
Future<void> clearSensitiveData() => Future.wait([ | ||
deleteOverallBalances(), | ||
]); | ||
|
||
/// Subject for overall balances for accounts | ||
/// key - address of account, value - overall balance in fiat | ||
final _overallBalancesSubject = | ||
BehaviorSubject<Map<Address, Fixed>>.seeded({}); | ||
|
||
/// Get cached overall balance for accounts | ||
/// key - address of account, value - overall balance in fiat | ||
Map<Address, Fixed> get overallBalance => _overallBalancesSubject.value; | ||
|
||
/// Stream that allows tracking overall balance changing | ||
Stream<Map<Address, Fixed>> get overallBalancesStream => | ||
_overallBalancesSubject; | ||
|
||
/// Put overall balances into stream | ||
Future<void> _streamedOverallBalance() async => | ||
_overallBalancesSubject.add(await readOverallBalances()); | ||
|
||
/// Get all overall balances for accounts | ||
/// key - address, value - overall balance | ||
Future<Map<Address, Fixed>> readOverallBalances() async { | ||
final encoded = await _storage.getDomain(domain: _overallBalancesDomain); | ||
|
||
return encoded.map( | ||
(key, value) => MapEntry( | ||
Address(address: key), | ||
FixedImprover.fromJson(jsonDecode(value) as Map<String, dynamic>), | ||
), | ||
); | ||
} | ||
|
||
/// Set overall balance for specified account | ||
Future<void> setOverallBalance({ | ||
required Address accountAddress, | ||
required Fixed balance, | ||
}) async { | ||
try { | ||
await _storage.set( | ||
accountAddress.address, | ||
jsonEncode(balance.toJson()), | ||
domain: _overallBalancesDomain, | ||
); | ||
await _streamedOverallBalance(); | ||
} catch (e, t) { | ||
_logger.severe('setOverallBalance', e, t); | ||
} | ||
} | ||
|
||
/// Delete overall balances | ||
Future<void> deleteOverallBalances() async { | ||
await _storage.clearDomain(_overallBalancesDomain); | ||
} | ||
|
||
/// Subject for token balances for accounts | ||
/// key - address of account, value - list of balances for tokens in scope of | ||
/// this account. | ||
final _balancesSubject = | ||
BehaviorSubject<Map<Address, List<AccountBalanceModel>>>.seeded({}); | ||
|
||
/// Get cached token balances for accounts | ||
/// key - address of account, value - list of balances for tokens in scope of | ||
/// this account. | ||
Map<Address, List<AccountBalanceModel>> get balances => | ||
_balancesSubject.value; | ||
|
||
/// Stream that allows tracking token balances for accounts | ||
Stream<Map<Address, List<AccountBalanceModel>>> get balancesStream => | ||
_balancesSubject; | ||
|
||
/// Put token balances for accounts into stream | ||
Future<void> _streamedBalance() async => | ||
_balancesSubject.add(await readBalances()); | ||
|
||
/// Get all token balances for accounts | ||
/// key - address of account, value - list of balances for tokens in scope of | ||
/// this account. | ||
Future<Map<Address, List<AccountBalanceModel>>> readBalances() async { | ||
final encoded = await _storage.getDomain(domain: _balancesDomain); | ||
|
||
return encoded.map( | ||
(key, value) { | ||
final decoded = jsonDecode(value) as List<dynamic>; | ||
|
||
return MapEntry( | ||
Address(address: key), | ||
decoded | ||
.map( | ||
(e) => AccountBalanceModel.fromJson(e as Map<String, dynamic>), | ||
) | ||
.toList(), | ||
); | ||
}, | ||
); | ||
} | ||
|
||
/// Set token balances for accounts with [accountAddress] | ||
Future<void> setBalances({ | ||
required Address accountAddress, | ||
required AccountBalanceModel balance, | ||
}) async { | ||
try { | ||
var existedForAccount = balances[accountAddress]; | ||
|
||
if (existedForAccount == null) { | ||
existedForAccount = [balance]; | ||
} else { | ||
existedForAccount | ||
..removeWhere((b) => b.rootTokenContract == balance.rootTokenContract) | ||
..add(balance); | ||
} | ||
|
||
await _storage.set( | ||
accountAddress.address, | ||
jsonEncode(existedForAccount.map((b) => b.toJson()).toList()), | ||
domain: _balancesDomain, | ||
); | ||
await _streamedOverallBalance(); | ||
} catch (e, t) { | ||
_logger.severe('setBalances', e, t); | ||
} | ||
} | ||
|
||
/// Delete token balances | ||
Future<void> deleteBalances() async { | ||
await _storage.clearDomain(_balancesDomain); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import 'package:app/utils/utils.dart'; | ||
import 'package:collection/collection.dart'; | ||
import 'package:freezed_annotation/freezed_annotation.dart'; | ||
import 'package:nekoton_repository/nekoton_repository.dart'; | ||
|
||
part 'account_balance.freezed.dart'; | ||
part 'account_balance.g.dart'; | ||
|
||
/// Model of cached fiat and token balance that could be saved and restored | ||
/// to access balance before creating subscription. | ||
@immutable | ||
@freezed | ||
class AccountBalanceModel with _$AccountBalanceModel { | ||
const factory AccountBalanceModel({ | ||
required Address rootTokenContract, | ||
@moneyFromStringJsonConverter required Money fiatBalance, | ||
@moneyFromStringJsonConverter required Money tokenBalance, | ||
}) = _AccountBalanceModel; | ||
|
||
factory AccountBalanceModel.fromJson(Map<String, dynamic> json) => | ||
_$AccountBalanceModelFromJson(json); | ||
} | ||
|
||
extension AccountBalancesExt on List<AccountBalanceModel> { | ||
AccountBalanceModel? tokenBalance(Address rootTokenContract) => | ||
firstWhereOrNull((b) => b.rootTokenContract == rootTokenContract); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.