Skip to content

Commit

Permalink
feat: sort crypto portfolio list (#1480)
Browse files Browse the repository at this point in the history
Co-authored-by: Kirill Bubochkin <[email protected]>
  • Loading branch information
justinenerio and ookami-kb authored Jun 4, 2024
1 parent 5cc7050 commit 046de43
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,10 @@ import '../../../ui/colors.dart';
import '../../../ui/home_tile.dart';
import '../../../ui/theme.dart';
import '../../../ui/value_stream_builder.dart';
import '../../balances/data/repository.dart';
import '../../conversion_rates/services/token_fiat_balance_service.dart';
import '../../conversion_rates/widgets/extensions.dart';
import '../../currency/models/amount.dart';
import '../../currency/models/currency.dart';
import '../../tokens/token.dart';
import '../../tokens/widgets/token_icon.dart';

class PortfolioWidget extends StatefulWidget {
Expand All @@ -32,12 +30,10 @@ class _PortfolioWidgetState extends State<PortfolioWidget>
Widget build(BuildContext context) {
super.build(context);

return ValueStreamBuilder<IList<CryptoAmount>>(
return ValueStreamBuilder<IList<CryptoFiatAmount>>(
create: () => (
sl<TokenBalancesRepository>().watchTokenBalances(
ignoreTokens: [Token.usdc],
),
const IListConst([])
sl<TokenFiatBalanceService>().watchBalances(),
const IListConst([]),
),
builder: (context, balances) {
final hasTokens = balances.isNotEmpty;
Expand All @@ -53,7 +49,7 @@ class _PortfolioWidgetState extends State<PortfolioWidget>
class PortfolioTile extends StatelessWidget {
const PortfolioTile({super.key, required this.balances});

final IList<CryptoAmount> balances;
final IList<CryptoFiatAmount> balances;

@override
Widget build(BuildContext context) => HomeTile(
Expand Down Expand Up @@ -108,15 +104,16 @@ class PortfolioTile extends StatelessWidget {
class _PortfolioWidget extends StatelessWidget {
const _PortfolioWidget(this.balances);

final IList<CryptoAmount> balances;
final IList<CryptoFiatAmount> balances;

@override
Widget build(BuildContext context) {
final children = balances
.map(
(balance) => _TokenItem(
key: ValueKey(balance.token),
amount: balance,
key: ValueKey(balance.$1.token),
cryptoAmount: balance.$1,
fiatAmount: balance.$2,
),
)
.expand(
Expand All @@ -132,73 +129,70 @@ class _PortfolioWidget extends StatelessWidget {
}

class _TokenItem extends StatelessWidget {
const _TokenItem({super.key, required this.amount});
const _TokenItem({
super.key,
required this.cryptoAmount,
required this.fiatAmount,
});

final CryptoAmount amount;
final CryptoAmount cryptoAmount;
final FiatAmount fiatAmount;

static const double _iconSize = 36.0;
static const double _minFiatAmount = 0.01;

@override
Widget build(BuildContext context) => ValueStreamBuilder<Amount?>(
create: () => (sl<TokenFiatBalanceService>().watch(amount.token), null),
builder: (context, fiatAmount) {
String fiatAmountText;

if (fiatAmount != null) {
fiatAmountText = fiatAmount.value < _minFiatAmount
? r'<$0.01'
: fiatAmount.format(context.locale, maxDecimals: 2);
} else {
fiatAmountText = '-';
}

return _Card(
child: ListTile(
key: key,
contentPadding: const EdgeInsets.symmetric(horizontal: 12),
dense: true,
horizontalTitleGap: 4,
leading: ClipRRect(
borderRadius: BorderRadius.circular(_iconSize / 2),
child: TokenIcon(token: amount.token, size: _iconSize),
),
title: Text(
amount.token.name,
style: const TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w600,
),
overflow: TextOverflow.ellipsis,
Widget build(BuildContext context) {
String fiatAmountText;

fiatAmountText = fiatAmount.value < _minFiatAmount
? r'<$0.01'
: fiatAmount.format(context.locale, maxDecimals: 2);

return _Card(
child: ListTile(
key: key,
contentPadding: const EdgeInsets.symmetric(horizontal: 12),
dense: true,
horizontalTitleGap: 4,
leading: ClipRRect(
borderRadius: BorderRadius.circular(_iconSize / 2),
child: TokenIcon(token: cryptoAmount.token, size: _iconSize),
),
title: Text(
cryptoAmount.token.name,
style: const TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w600,
),
overflow: TextOverflow.ellipsis,
),
trailing: Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
fiatAmountText,
style: const TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w600,
),
trailing: Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
fiatAmountText,
style: const TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
Text(
amount.format(context.locale),
style: const TextStyle(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.w400,
),
),
],
),
Text(
cryptoAmount.format(context.locale),
style: const TextStyle(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.w400,
),
// isThreeLine: true,
),
);
},
);
],
),
),
);
}
}

class _Card extends StatelessWidget {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ class TokenBalancesRepository {
.map((row) {
final token = _tokens.findTokenByMint(row.token);

if (ignoreTokens.contains(token)) {
return null;
}

return token == null
? null
: CryptoAmount(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:collection/collection.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:injectable/injectable.dart';
import 'package:rxdart/rxdart.dart';

Expand All @@ -8,6 +9,8 @@ import '../../currency/models/currency.dart';
import '../../tokens/token.dart';
import '../data/repository.dart';

typedef CryptoFiatAmount = (CryptoAmount, FiatAmount);

@injectable
class TokenFiatBalanceService {
const TokenFiatBalanceService(
Expand All @@ -18,6 +21,9 @@ class TokenFiatBalanceService {
final ConversionRatesRepository _conversionRatesRepository;
final TokenBalancesRepository _balancesRepository;

static const _zeroFiat =
FiatAmount(value: 0, fiatCurrency: defaultFiatCurrency);

Stream<FiatAmount?> watch(Token token) {
const fiatCurrency = defaultFiatCurrency;
final conversionRate = _conversionRatesRepository.watchRate(
Expand All @@ -30,11 +36,9 @@ class TokenFiatBalanceService {
return Rx.combineLatest2(
balance,
conversionRate,
(cryptoAmount, rate) {
if (rate == null) return null;

return cryptoAmount.convert(rate: rate, to: fiatCurrency) as FiatAmount;
},
(cryptoAmount, rate) => rate == null
? null
: cryptoAmount.convert(rate: rate, to: fiatCurrency) as FiatAmount,
).distinct();
}

Expand All @@ -43,15 +47,27 @@ class TokenFiatBalanceService {
.flatMap(
(tokens) => Rx.combineLatest(
tokens.map(watch),
(values) => values.whereNotNull().fold(
const FiatAmount(value: 0, fiatCurrency: defaultFiatCurrency),
(total, next) => (total + next) as FiatAmount,
),
(values) => values
.whereNotNull()
.fold(_zeroFiat, (total, next) => (total + next) as FiatAmount),
),
)
.distinct();

Stream<FiatAmount> watchMainBalance() => watch(Token.usdc).map(
(it) => it ?? const FiatAmount(value: 0, fiatCurrency: Currency.usd),
);
Stream<FiatAmount> watchMainBalance() =>
watch(Token.usdc).map((it) => it ?? _zeroFiat);

Stream<IList<CryptoFiatAmount>> watchBalances() => _balancesRepository
.watchTokenBalances(ignoreTokens: [Token.usdc])
.flatMap(
(cryptoAmounts) => Rx.combineLatest(
cryptoAmounts.map((c) => watch(c.token).map((fiat) => (c, fiat))),
(values) => values.map((e) => (e.$1, e.$2 ?? _zeroFiat)),
),
)
.map((amount) => amount.toIList())
.map(
(amounts) => amounts.sort((a, b) => b.$2.value.compareTo(a.$2.value)),
)
.distinct();
}

0 comments on commit 046de43

Please sign in to comment.