Skip to content

Commit

Permalink
feat: redesign investment widget (#1248)
Browse files Browse the repository at this point in the history
  • Loading branch information
justinenerio authored Jan 26, 2024
1 parent 9e73020 commit ab83ec2
Show file tree
Hide file tree
Showing 5 changed files with 262 additions and 32 deletions.
Binary file modified packages/espressocash_app/assets/images/investing-banner.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class _RecentActivityWidgetState extends State<RecentActivityWidget> {
style: dashboardSectionTitleTextStyle,
),
),
const SizedBox(height: 12),
const SizedBox(height: 16),
if (data.isEmpty)
const Center(child: _NoActivity())
else ...[
Expand All @@ -77,7 +77,7 @@ class _RecentActivityWidgetState extends State<RecentActivityWidget> {
.toList(),
),
),
const SizedBox(height: 4),
const SizedBox(height: 8),
// TODO(KB): Check if needed
// ignore: avoid-single-child-column-or-row
Row(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,54 @@
import 'package:auto_route/auto_route.dart';
import 'package:decimal/decimal.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart';

import '../../../core/amount.dart';
import '../../../core/presentation/value_stream_builder.dart';
import '../../../di.dart';
import '../../../gen/assets.gen.dart';
import '../../../l10n/l10n.dart';
import '../../../ui/button.dart';
import '../../../ui/theme.dart';
import '../../conversion_rates/services/watch_user_total_fiat_balance.dart';
import '../../tokens/token.dart';
import '../data/repository.dart';
import '../screens/investments_screen.dart';
import 'home_widget.dart';
import 'portfolio_tile.dart';

class InvestingWidget extends StatelessWidget {
const InvestingWidget({super.key});

@override
Widget build(BuildContext context) => ValueStreamBuilder<Amount>(
create: () => sl<WatchUserTotalFiatBalance>().call(
ignoreTokens: [Token.usdc],
),
builder: (context, balance) {
final displayEmptyBalances = context
.watch<InvestmentSettingsRepository>()
.displayEmptyBalances;

final hasNoInvestments =
balance.decimal == Decimal.zero && !displayEmptyBalances;

return hasNoInvestments
? const _StartInvestmentBanner()
: PortfolioTile(
balance: balance,
displayEmptyBalances: displayEmptyBalances,
);
},
);
}

class _StartInvestmentBanner extends StatelessWidget {
const _StartInvestmentBanner();

@override
Widget build(BuildContext context) => HomeTile(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
Expand All @@ -25,37 +60,28 @@ class InvestingWidget extends StatelessWidget {
style: dashboardSectionTitleTextStyle,
),
),
const SizedBox(height: 12),
const SizedBox(height: 16),
GestureDetector(
onTap: () => context.router.push(InvestmentsScreen.route()),
child: const _InvestmentBanner(),
child: Stack(
alignment: Alignment.bottomCenter,
children: [
Assets.images.investingBanner.image(),
Padding(
padding: const EdgeInsets.only(bottom: 24),
child: IgnorePointer(
child: CpButton(
text: context.l10n.startInvestingTitle,
minWidth: 250,
size: CpButtonSize.wide,
onPressed: () {},
),
),
),
],
),
),
],
),
);
}

class _InvestmentBanner extends StatelessWidget {
const _InvestmentBanner();

@override
Widget build(BuildContext context) => Stack(
alignment: Alignment.bottomCenter,
children: [
Assets.images.investingBanner.image(),
Padding(
padding: const EdgeInsets.all(14.0),
child: Text(
context.l10n.investingBannerTitle,
textAlign: TextAlign.center,
style: const TextStyle(
color: Color(0xFF2D2B2C),
fontSize: 19,
fontWeight: FontWeight.w600,
letterSpacing: 0.41,
),
),
),
],
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
import 'package:auto_route/auto_route.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:flutter/material.dart';

import '../../../core/amount.dart';
import '../../../core/presentation/format_amount.dart';
import '../../../core/presentation/value_stream_builder.dart';
import '../../../di.dart';
import '../../../l10n/device_locale.dart';
import '../../../l10n/l10n.dart';
import '../../../ui/button.dart';
import '../../../ui/colors.dart';
import '../../conversion_rates/services/watch_user_fiat_balance.dart';
import '../../token_details/screens/token_details_screen.dart';
import '../../tokens/token.dart';
import '../../tokens/widgets/token_icon.dart';
import '../screens/investments_screen.dart';
import '../services/watch_investments.dart';
import 'home_widget.dart';

const int _maxTokens = 3;

class PortfolioTile extends StatelessWidget {
const PortfolioTile({
super.key,
required this.balance,
required this.displayEmptyBalances,
});

final Amount balance;
final bool displayEmptyBalances;

@override
Widget build(BuildContext context) => HomeTile(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Padding(
padding: const EdgeInsets.only(right: 4.0),
child: Text(
context.l10n.cryptoInvestments,
style: const TextStyle(
fontSize: 17,
fontWeight: FontWeight.w500,
letterSpacing: 0.17,
color: CpColors.menuPrimaryTextColor,
),
),
),
const SizedBox(width: 8),
Flexible(
child: FittedBox(
child: Text(
balance.format(DeviceLocale.localeOf(context)),
style:
Theme.of(context).textTheme.displayMedium?.copyWith(
color: CpColors.menuPrimaryTextColor,
fontSize: 17,
fontWeight: FontWeight.w900,
letterSpacing: 0.17,
),
),
),
),
],
),
),
const SizedBox(height: 16),
_PortfolioWidget(displayEmptyBalances: displayEmptyBalances),
const SizedBox(height: 8),
// TODO(KB): Check if needed
// ignore: avoid-single-child-column-or-row
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
CpButton(
text: context.l10n.recentActivitySeeAll,
size: CpButtonSize.micro,
variant: CpButtonVariant.black,
onPressed: () =>
context.router.push(InvestmentsScreen.route()),
),
],
),
],
),
);
}

class _PortfolioWidget extends StatelessWidget {
const _PortfolioWidget({required this.displayEmptyBalances});

final bool displayEmptyBalances;

@override
Widget build(BuildContext context) => ValueStreamBuilder<IList<Token>>(
create: () => (
sl<WatchInvestments>()
.call(displayEmptyBalances: displayEmptyBalances),
const IListConst([]),
),
builder: (context, data) {
final tokens = data.take(_maxTokens);

final children = tokens
.map((token) => _BalanceItem(token: token))
.expand(
(widget) => [
widget,
const _Divider(),
],
)
.toList();

if (data.isNotEmpty) {
children.removeLast();
}

return _Card(
child: Column(children: children),
);
},
);
}

class _BalanceItem extends StatelessWidget {
const _BalanceItem({required this.token});

final Token token;

@override
Widget build(BuildContext context) {
final locale = DeviceLocale.localeOf(context);

return ValueStreamBuilder<Amount?>(
create: () => sl<WatchUserFiatBalance>().call(token),
builder: (context, fiatAmount) => Material(
color: Colors.transparent,
child: ListTile(
onTap: () =>
context.router.push(TokenDetailsScreen.route(token: token)),
leading: TokenIcon(token: token, size: 36),
title: Text(
token.name,
style: const TextStyle(
color: Color(0xFF2D2B2C),
fontSize: 16,
fontWeight: FontWeight.w600,
),
overflow: TextOverflow.ellipsis,
),
trailing: Text(
fiatAmount?.format(locale) ?? '-',
style: const TextStyle(
color: Color(0xFF2D2B2C),
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
),
);
}
}

class _Card extends StatelessWidget {
const _Card({required this.child});
final Widget child;

@override
Widget build(BuildContext context) => Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
decoration: const ShapeDecoration(
color: Color(0xFFFBC728),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(28),
),
),
),
child: child,
);
}

class _Divider extends StatelessWidget {
const _Divider();

@override
Widget build(BuildContext context) => const Divider(
color: Color(0xFFF4BF1D),
height: 6,
thickness: 4,
indent: 8,
endIndent: 8,
);
}
2 changes: 1 addition & 1 deletion packages/espressocash_app/lib/l10n/intl_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
"@crypto": {},
"cryptoCashBalance": "Your Cash Balance",
"@cryptoCashBalance": {},
"cryptoInvestments": "Your Crypto",
"cryptoInvestments": "Your Investments",
"@cryptoInvestments": {},
"decline": "Decline",
"@decline": {},
Expand Down

0 comments on commit ab83ec2

Please sign in to comment.