How to Build a Fintech Wallet Home Screen in Flutter (Full Code + Preview)
A wallet home is the screen a neobank user opens ten times a day, and it has to answer one question instantly: how much money do I have, and where did it go? This tutorial walks through FlutterKit's dark Revolut-style wallet home — a gradient balance card with a masked card number, four tinted quick-action circles, a colour-coded recent-transactions list and a five-tab bottom nav — all in a single self-contained Dart file with a bundled Inter font, no packages and no network calls.

What you'll build
- ✓A gradient balance card blending `_brand` #494FDF into `_brandDeep` #2D31A6, with a masked •••• 4821 line and two frosted Send/Receive chips
- ✓Four quick-action circles (Send, Receive, Pay, More) that share one `_surface` disc and differ only by icon tint
- ✓A recent-transactions list driven by a const `_Txn` value class, with soft-tinted category icons and teal credit amounts
- ✓An avatar widget that falls back to a tinted initial whenever its image fails, so the greeting row never renders blank
- ✓A five-tab bottom nav whose tabs are Dart records, wired to an optional `onTabSelected` callback
Step-by-step build
Create the file
Add a new file at lib/fintech_wallet_home/fintech_wallet_home_screen.dart in your Flutter project.
Register the bundled fonts
No external packages — this is pure Flutter. It does bundle its design font (Inter), so drop the font file into fonts/ and declare it in pubspec.yaml:
flutter:
fonts:
- family: Inter
fonts:
- asset: fonts/Inter-Regular.ttfBuild it, piece by piece
Here's how the screen goes together. Each block below is a real slice of the code with a plain-English explanation — paste them in order, or grab the whole file from the next section.
Design tokens and a self-theming scaffold
import 'package:flutter/material.dart';
/// Wallet Home — a dark fintech dashboard (Revolut-inspired design system).
///
/// Self-contained per CONVENTIONS.md: pure Flutter only, the exact design font
/// (Inter) is bundled under `fonts/`, avatars load over the network, and the
/// screen renders standalone when pushed as a route (it forces its own dark
/// theme rather than depending on an app-level one).
class FintechWalletHomeScreen extends StatelessWidget {
const FintechWalletHomeScreen({super.key, this.onTabSelected});
/// Optional bottom-nav tab callback (Home=0, Payments=1, Analytics=2, Hub=3,
/// Profile=4). When null the screen still renders standalone (Rule 7).
final ValueChanged<int>? onTabSelected;
// ── Revolut design tokens ────────────────────────────────────────────────
static const String _font = 'Inter';
static const Color _bg = Color(0xFF191C1F);
static const Color _surface = Color(0xFF242729);
static const Color _brand = Color(0xFF494FDF);
static const Color _brandDeep = Color(0xFF2D31A6);
static const Color _teal = Color(0xFF00A87E);
static const Color _amber = Color(0xFFEC7E00);
static const Color _red = Color(0xFFE23B4A);
static const Color _muted = Color(0xFF8D969E);
static const Color _hairline = Color(0xFF2E3235);
@override
Widget build(BuildContext context) {
return Theme(
data: ThemeData.dark(useMaterial3: true),
child: Scaffold(
backgroundColor: _bg,
body: SafeArea(
bottom: false,
child: Column(
children: <Widget>[
Expanded(
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.fromLTRB(20, 8, 20, 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const <Widget>[
_TopBar(),
SizedBox(height: 24),
_BalanceCard(),
SizedBox(height: 28),
_QuickActions(),
SizedBox(height: 32),
_SectionHeader(),
SizedBox(height: 8),
_TransactionList(),
],
),
),
),
_BottomNav(onTabSelected: onTabSelected),
],
),
),
),
);
}
}The screen is a `StatelessWidget` whose only parameter is an optional `onTabSelected` callback, so it renders standalone with no arguments at all. Ten `static const` tokens hold the palette — near-black `_bg` #191C1F, a lighter `_surface` #242729 for pills and the nav, indigo `_brand` #494FDF, plus `_teal`, `_amber` and `_red` for semantic accents. Wrapping the `Scaffold` in `Theme(data: ThemeData.dark(useMaterial3: true))` forces dark mode locally, so the screen keeps its look even when pushed inside a light app. The layout is an outer `Column`: an `Expanded` `SingleChildScrollView` with `BouncingScrollPhysics` holds the content, and `_BottomNav` sits after it, pinned below the scroll. `SafeArea(bottom: false)` matters here — the nav handles the bottom inset itself so its surface colour can run under the home indicator.
The greeting row and a never-blank avatar
// ── Top bar ──────────────────────────────────────────────────────────────────
class _TopBar extends StatelessWidget {
const _TopBar();
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
const ClipOval(
child: _Avatar(
url: 'lib/screens/fintech/fintech_wallet_home/images/avatar_3.jpg',
tint: FintechWalletHomeScreen._brand,
initial: 'R',
size: 40,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const <Widget>[
Text(
'Good morning',
style: TextStyle(
fontFamily: FintechWalletHomeScreen._font,
fontSize: 12,
fontWeight: FontWeight.w400,
letterSpacing: 0.24,
color: FintechWalletHomeScreen._muted,
),
),
SizedBox(height: 2),
Text(
'Rohan',
style: TextStyle(
fontFamily: FintechWalletHomeScreen._font,
fontSize: 16,
fontWeight: FontWeight.w500,
letterSpacing: 0.24,
color: Colors.white,
),
),
],
),
),
_IconPill(
icon: Icons.notifications_none_rounded,
onTap: () {},
),
],
);
}
}
/// Network avatar that never renders blank: shows a tinted initial while the
/// photo loads and falls back to it permanently if the request fails.
class _Avatar extends StatelessWidget {
const _Avatar({
required this.url,
required this.tint,
required this.initial,
required this.size,
});
final String url;
final Color tint;
final String initial;
final double size;
Widget _fallback() {
return Container(
width: size,
height: size,
color: tint.withValues(alpha: 0.22),
alignment: Alignment.center,
child: Text(
initial.toUpperCase(),
style: TextStyle(
fontFamily: FintechWalletHomeScreen._font,
fontSize: size * 0.40,
fontWeight: FontWeight.w500,
color: tint,
),
),
);
}
@override
Widget build(BuildContext context) {
return Image.asset(
url,
width: size,
height: size,
fit: BoxFit.cover,
gaplessPlayback: true,
errorBuilder: (BuildContext context, Object error, StackTrace? stack) =>
_fallback(),
);
}
}
class _IconPill extends StatelessWidget {
const _IconPill({required this.icon, required this.onTap});
final IconData icon;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
width: 40,
height: 40,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: FintechWalletHomeScreen._surface,
),
child: Icon(
icon,
size: 20,
color: Colors.white,
),
),
);
}
}`_TopBar` is a plain `Row`: a `ClipOval` avatar, an `Expanded` two-line greeting ('Good morning' at 12px `_muted` over the name at 16px `w500`), and a notification `_IconPill` pushed to the trailing edge. `_Avatar` loads via `Image.asset` with `gaplessPlayback: true`, and its `errorBuilder` swaps in `_fallback()` — a square tinted `tint.withValues(alpha: 0.22)` with the initial drawn at `size * 0.40` in the full tint — so a missing or corrupt asset degrades to a monogram instead of a broken-image icon (the `ClipOval` outside rounds either result). `_IconPill` is a 40px `_surface` circle in a `GestureDetector`, sized to match the avatar so the row's two endpoints balance.
The gradient balance card
// ── Balance card ─────────────────────────────────────────────────────────────
class _BalanceCard extends StatelessWidget {
const _BalanceCard();
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: <Color>[
FintechWalletHomeScreen._brand,
FintechWalletHomeScreen._brandDeep,
],
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
'Total Balance',
style: TextStyle(
fontFamily: FintechWalletHomeScreen._font,
fontSize: 14,
fontWeight: FontWeight.w400,
letterSpacing: 0.24,
color: Colors.white.withValues(alpha: 0.70),
),
),
const SizedBox(height: 6),
const Text(
r'$12,485.50',
style: TextStyle(
fontFamily: FintechWalletHomeScreen._font,
fontSize: 32,
fontWeight: FontWeight.w500,
letterSpacing: 0.24,
color: Colors.white,
),
),
const SizedBox(height: 10),
Text(
'•••• •••• •••• 4821',
style: TextStyle(
fontFamily: FintechWalletHomeScreen._font,
fontSize: 12,
fontWeight: FontWeight.w400,
letterSpacing: 1.2,
color: Colors.white.withValues(alpha: 0.60),
),
),
const SizedBox(height: 20),
Row(
children: const <Widget>[
_CardChip(icon: Icons.north_east_rounded, label: 'Send'),
SizedBox(width: 12),
_CardChip(icon: Icons.south_west_rounded, label: 'Receive'),
],
),
],
),
);
}
}
class _CardChip extends StatelessWidget {
const _CardChip({required this.icon, required this.label});
final IconData icon;
final String label;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.16),
borderRadius: BorderRadius.circular(9999),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(icon, size: 14, color: Colors.white),
const SizedBox(width: 6),
Text(
label,
style: const TextStyle(
fontFamily: FintechWalletHomeScreen._font,
fontSize: 13,
fontWeight: FontWeight.w500,
letterSpacing: 0.24,
color: Colors.white,
),
),
],
),
);
}
}`_BalanceCard` is a full-width `Container` with `borderRadius.circular(20)` and a top-left-to-bottom-right `LinearGradient` from `_brand` to the darker `_brandDeep`, which gives the card depth without a shadow. The type scale does the ranking: 'Total Balance' at 14px in 70%-alpha white, the `$12,485.50` figure at 32px `w500` full white, then the masked '•••• •••• •••• 4821' at 12px, 60% alpha, with `letterSpacing: 1.2` to spread the dot groups like an embossed card number. The Send/Receive `_CardChip`s use `Colors.white.withValues(alpha: 0.16)` — a frost that works anywhere on the gradient instead of committing to one background colour — and `borderRadius.circular(9999)` with `MainAxisSize.min` makes each pill a stadium that hugs its icon and 13px label.
Quick actions in four tints
// ── Quick actions ────────────────────────────────────────────────────────────
class _QuickActions extends StatelessWidget {
const _QuickActions();
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const <Widget>[
_QuickAction(
icon: Icons.arrow_upward_rounded,
tint: FintechWalletHomeScreen._brand,
label: 'Send',
),
_QuickAction(
icon: Icons.arrow_downward_rounded,
tint: FintechWalletHomeScreen._teal,
label: 'Receive',
),
_QuickAction(
icon: Icons.qr_code_scanner_rounded,
tint: FintechWalletHomeScreen._amber,
label: 'Pay',
),
_QuickAction(
icon: Icons.grid_view_rounded,
tint: Colors.white,
label: 'More',
),
],
);
}
}
class _QuickAction extends StatelessWidget {
const _QuickAction({
required this.icon,
required this.tint,
required this.label,
});
final IconData icon;
final Color tint;
final String label;
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Container(
width: 56,
height: 56,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: FintechWalletHomeScreen._surface,
),
child: Icon(icon, size: 22, color: tint),
),
const SizedBox(height: 8),
Text(
label,
style: const TextStyle(
fontFamily: FintechWalletHomeScreen._font,
fontSize: 11,
fontWeight: FontWeight.w400,
letterSpacing: 0.24,
color: FintechWalletHomeScreen._muted,
),
),
],
);
}
}`_QuickActions` spreads four `_QuickAction`s with `MainAxisAlignment.spaceBetween`, so spacing adapts to any phone width without hard-coded gaps. Every action is the same 56px `_surface` circle; only the 22px icon changes colour — Send in `_brand`, Receive in `_teal`, Pay's QR scanner in `_amber`, More in white. Colour-coding the glyph rather than filling the discs keeps four adjacent buttons from shouting at once, and the 11px `_muted` labels stay deliberately quiet beneath them.
Section header and transactions as data
// ── Section header ───────────────────────────────────────────────────────────
class _SectionHeader extends StatelessWidget {
const _SectionHeader();
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const <Widget>[
Text(
'Recent Transactions',
style: TextStyle(
fontFamily: FintechWalletHomeScreen._font,
fontSize: 16,
fontWeight: FontWeight.w500,
letterSpacing: 0.24,
color: Colors.white,
),
),
Text(
'See all',
style: TextStyle(
fontFamily: FintechWalletHomeScreen._font,
fontSize: 14,
fontWeight: FontWeight.w500,
letterSpacing: 0.24,
color: FintechWalletHomeScreen._brand,
),
),
],
);
}
}
// ── Transactions ─────────────────────────────────────────────────────────────
class _TransactionList extends StatelessWidget {
const _TransactionList();
@override
Widget build(BuildContext context) {
const List<_Txn> txns = <_Txn>[
_Txn(
icon: Icons.movie_outlined,
tint: FintechWalletHomeScreen._red,
name: 'Netflix',
date: 'Jun 9',
amount: r'-$15.99',
credit: false,
),
_Txn(
icon: Icons.work_outline_rounded,
tint: FintechWalletHomeScreen._teal,
name: 'Salary',
date: 'Jun 8',
amount: r'+$3,200.00',
credit: true,
),
_Txn(
icon: Icons.restaurant_rounded,
tint: FintechWalletHomeScreen._amber,
name: 'Uber Eats',
date: 'Jun 7',
amount: r'-$24.50',
credit: false,
),
_Txn(
icon: Icons.shopping_bag_outlined,
tint: FintechWalletHomeScreen._brand,
name: 'Amazon',
date: 'Jun 6',
amount: r'-$89.99',
credit: false,
),
];
return Column(
children: <Widget>[
for (final _Txn t in txns)
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: _TransactionRow(txn: t),
),
],
);
}`_SectionHeader` is another `spaceBetween` row — 'Recent Transactions' in white on the left, a 'See all' affordance in `_brand` on the right, the same indigo the nav uses for the active tab. The list itself is a `const List<_Txn>` of four entries (Netflix, Salary, Uber Eats, Amazon), each pairing an outlined Material icon with one of the semantic tints. Amounts are raw strings like `r'-$15.99'` — the `r` prefix stops Dart treating `$` as interpolation — and carry their own sign, so the row widget never formats money. A collection-`for` wraps each row in `EdgeInsets.symmetric(vertical: 8)`, producing an even 16px rhythm between rows with no divider widgets.
The transaction row and the two-layer tint trick
class _Txn {
const _Txn({
required this.icon,
required this.tint,
required this.name,
required this.date,
required this.amount,
required this.credit,
});
final IconData icon;
final Color tint;
final String name;
final String date;
final String amount;
final bool credit;
}
class _TransactionRow extends StatelessWidget {
const _TransactionRow({required this.txn});
final _Txn txn;
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: txn.tint.withValues(alpha: 0.16),
),
child: Icon(txn.icon, size: 20, color: txn.tint),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
txn.name,
style: const TextStyle(
fontFamily: FintechWalletHomeScreen._font,
fontSize: 15,
fontWeight: FontWeight.w500,
letterSpacing: 0.24,
color: Colors.white,
),
),
const SizedBox(height: 2),
Text(
txn.date,
style: const TextStyle(
fontFamily: FintechWalletHomeScreen._font,
fontSize: 12,
fontWeight: FontWeight.w400,
letterSpacing: 0.24,
color: FintechWalletHomeScreen._muted,
),
),
],
),
),
const SizedBox(width: 12),
Text(
txn.amount,
style: TextStyle(
fontFamily: FintechWalletHomeScreen._font,
fontSize: 15,
fontWeight: FontWeight.w500,
letterSpacing: 0.24,
color:
txn.credit ? FintechWalletHomeScreen._teal : Colors.white,
),
),
],
);
}
}`_Txn` is a six-field const value class — icon, tint, name, date, amount and a `credit` flag — so adding a transaction means adding data, not widgets. `_TransactionRow` opens with a 40px circle at `txn.tint.withValues(alpha: 0.16)` holding the icon in the full tint: the soft disc plus saturated glyph is what makes four different colours sit together calmly. The name/date column lives in an `Expanded`, so a long merchant name compresses before it can push the amount off-screen. The `credit` bool drives exactly one thing: amount colour, `_teal` for money in and plain white for money out — debits are the normal case, so they are not alarmed in red.
A bottom nav built from Dart records
// ── Bottom nav ───────────────────────────────────────────────────────────────
class _BottomNav extends StatelessWidget {
const _BottomNav({this.onTabSelected});
final ValueChanged<int>? onTabSelected;
static const List<({IconData icon, String label})> _tabs =
<({IconData icon, String label})>[
(icon: Icons.home_rounded, label: 'Home'),
(icon: Icons.swap_horiz_rounded, label: 'Payments'),
(icon: Icons.bar_chart_rounded, label: 'Analytics'),
(icon: Icons.grid_view_rounded, label: 'Hub'),
(icon: Icons.person_outline_rounded, label: 'Profile'),
];
@override
Widget build(BuildContext context) {
return Container(
decoration: const BoxDecoration(
color: FintechWalletHomeScreen._surface,
border: Border(
top: BorderSide(color: FintechWalletHomeScreen._hairline),
),
),
child: SafeArea(
top: false,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
for (int i = 0; i < _tabs.length; i++)
_NavItem(
icon: _tabs[i].icon,
label: _tabs[i].label,
active: i == 0,
onTap: () => onTabSelected?.call(i),
),
],
),
),
),
);
}
}
class _NavItem extends StatelessWidget {
const _NavItem({
required this.icon,
required this.label,
required this.active,
required this.onTap,
});
final IconData icon;
final String label;
final bool active;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
final Color color =
active ? FintechWalletHomeScreen._brand : FintechWalletHomeScreen._muted;
return GestureDetector(
onTap: onTap,
behavior: HitTestBehavior.opaque,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(icon, size: 22, color: color),
const SizedBox(height: 4),
Text(
label,
style: TextStyle(
fontFamily: FintechWalletHomeScreen._font,
fontSize: 11,
fontWeight: active ? FontWeight.w500 : FontWeight.w400,
letterSpacing: 0.24,
color: color,
),
),
],
),
);
}
}
`_BottomNav._tabs` is a `static const` list of `({IconData icon, String label})` records — named tuples that spare you a class for two fields. The bar is a `_surface` `Container` with a 1px `_hairline` top border, and its `SafeArea(top: false)` sits inside the Container so the surface colour extends beneath the home indicator while the icons stay above it. An index-based `for` builds each `_NavItem` with `active: i == 0` and `onTap: () => onTabSelected?.call(i)` — the null-aware call is why the screen still runs with no callback wired. Activation flips both signals at once: colour from `_muted` to `_brand` and weight from `w400` to `w500`, and `HitTestBehavior.opaque` makes the whole icon-plus-label column tappable, not just the glyph.
Full code
The complete, ready-to-paste source. Free to use in your projects — one click copies it all.
import 'package:flutter/material.dart';
/// Wallet Home — a dark fintech dashboard (Revolut-inspired design system).
///
/// Self-contained per CONVENTIONS.md: pure Flutter only, the exact design font
/// (Inter) is bundled under `fonts/`, avatars load over the network, and the
/// screen renders standalone when pushed as a route (it forces its own dark
/// theme rather than depending on an app-level one).
class FintechWalletHomeScreen extends StatelessWidget {
const FintechWalletHomeScreen({super.key, this.onTabSelected});
/// Optional bottom-nav tab callback (Home=0, Payments=1, Analytics=2, Hub=3,
/// Profile=4). When null the screen still renders standalone (Rule 7).
final ValueChanged<int>? onTabSelected;
// ── Revolut design tokens ────────────────────────────────────────────────
static const String _font = 'Inter';
static const Color _bg = Color(0xFF191C1F);
static const Color _surface = Color(0xFF242729);
static const Color _brand = Color(0xFF494FDF);
static const Color _brandDeep = Color(0xFF2D31A6);
static const Color _teal = Color(0xFF00A87E);
static const Color _amber = Color(0xFFEC7E00);
static const Color _red = Color(0xFFE23B4A);
static const Color _muted = Color(0xFF8D969E);
static const Color _hairline = Color(0xFF2E3235);
@override
Widget build(BuildContext context) {
return Theme(
data: ThemeData.dark(useMaterial3: true),
child: Scaffold(
backgroundColor: _bg,
body: SafeArea(
bottom: false,
child: Column(
children: <Widget>[
Expanded(
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.fromLTRB(20, 8, 20, 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const <Widget>[
_TopBar(),
SizedBox(height: 24),
_BalanceCard(),
SizedBox(height: 28),
_QuickActions(),
SizedBox(height: 32),
_SectionHeader(),
SizedBox(height: 8),
_TransactionList(),
],
),
),
),
_BottomNav(onTabSelected: onTabSelected),
],
),
),
),
);
}
}
// ── Top bar ──────────────────────────────────────────────────────────────────
class _TopBar extends StatelessWidget {
const _TopBar();
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
const ClipOval(
child: _Avatar(
url: 'lib/screens/fintech/fintech_wallet_home/images/avatar_3.jpg',
tint: FintechWalletHomeScreen._brand,
initial: 'R',
size: 40,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const <Widget>[
Text(
'Good morning',
style: TextStyle(
fontFamily: FintechWalletHomeScreen._font,
fontSize: 12,
fontWeight: FontWeight.w400,
letterSpacing: 0.24,
color: FintechWalletHomeScreen._muted,
),
),
SizedBox(height: 2),
Text(
'Rohan',
style: TextStyle(
fontFamily: FintechWalletHomeScreen._font,
fontSize: 16,
fontWeight: FontWeight.w500,
letterSpacing: 0.24,
color: Colors.white,
),
),
],
),
),
_IconPill(
icon: Icons.notifications_none_rounded,
onTap: () {},
),
],
);
}
}
/// Network avatar that never renders blank: shows a tinted initial while the
/// photo loads and falls back to it permanently if the request fails.
class _Avatar extends StatelessWidget {
const _Avatar({
required this.url,
required this.tint,
required this.initial,
required this.size,
});
final String url;
final Color tint;
final String initial;
final double size;
Widget _fallback() {
return Container(
width: size,
height: size,
color: tint.withValues(alpha: 0.22),
alignment: Alignment.center,
child: Text(
initial.toUpperCase(),
style: TextStyle(
fontFamily: FintechWalletHomeScreen._font,
fontSize: size * 0.40,
fontWeight: FontWeight.w500,
color: tint,
),
),
);
}
@override
Widget build(BuildContext context) {
return Image.asset(
url,
width: size,
height: size,
fit: BoxFit.cover,
gaplessPlayback: true,
errorBuilder: (BuildContext context, Object error, StackTrace? stack) =>
_fallback(),
);
}
}
class _IconPill extends StatelessWidget {
const _IconPill({required this.icon, required this.onTap});
final IconData icon;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
width: 40,
height: 40,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: FintechWalletHomeScreen._surface,
),
child: Icon(
icon,
size: 20,
color: Colors.white,
),
),
);
}
}
// ── Balance card ─────────────────────────────────────────────────────────────
class _BalanceCard extends StatelessWidget {
const _BalanceCard();
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: <Color>[
FintechWalletHomeScreen._brand,
FintechWalletHomeScreen._brandDeep,
],
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
'Total Balance',
style: TextStyle(
fontFamily: FintechWalletHomeScreen._font,
fontSize: 14,
fontWeight: FontWeight.w400,
letterSpacing: 0.24,
color: Colors.white.withValues(alpha: 0.70),
),
),
const SizedBox(height: 6),
const Text(
r'$12,485.50',
style: TextStyle(
fontFamily: FintechWalletHomeScreen._font,
fontSize: 32,
fontWeight: FontWeight.w500,
letterSpacing: 0.24,
color: Colors.white,
),
),
const SizedBox(height: 10),
Text(
'•••• •••• •••• 4821',
style: TextStyle(
fontFamily: FintechWalletHomeScreen._font,
fontSize: 12,
fontWeight: FontWeight.w400,
letterSpacing: 1.2,
color: Colors.white.withValues(alpha: 0.60),
),
),
const SizedBox(height: 20),
Row(
children: const <Widget>[
_CardChip(icon: Icons.north_east_rounded, label: 'Send'),
SizedBox(width: 12),
_CardChip(icon: Icons.south_west_rounded, label: 'Receive'),
],
),
],
),
);
}
}
class _CardChip extends StatelessWidget {
const _CardChip({required this.icon, required this.label});
final IconData icon;
final String label;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.16),
borderRadius: BorderRadius.circular(9999),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(icon, size: 14, color: Colors.white),
const SizedBox(width: 6),
Text(
label,
style: const TextStyle(
fontFamily: FintechWalletHomeScreen._font,
fontSize: 13,
fontWeight: FontWeight.w500,
letterSpacing: 0.24,
color: Colors.white,
),
),
],
),
);
}
}
// ── Quick actions ────────────────────────────────────────────────────────────
class _QuickActions extends StatelessWidget {
const _QuickActions();
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const <Widget>[
_QuickAction(
icon: Icons.arrow_upward_rounded,
tint: FintechWalletHomeScreen._brand,
label: 'Send',
),
_QuickAction(
icon: Icons.arrow_downward_rounded,
tint: FintechWalletHomeScreen._teal,
label: 'Receive',
),
_QuickAction(
icon: Icons.qr_code_scanner_rounded,
tint: FintechWalletHomeScreen._amber,
label: 'Pay',
),
_QuickAction(
icon: Icons.grid_view_rounded,
tint: Colors.white,
label: 'More',
),
],
);
}
}
class _QuickAction extends StatelessWidget {
const _QuickAction({
required this.icon,
required this.tint,
required this.label,
});
final IconData icon;
final Color tint;
final String label;
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Container(
width: 56,
height: 56,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: FintechWalletHomeScreen._surface,
),
child: Icon(icon, size: 22, color: tint),
),
const SizedBox(height: 8),
Text(
label,
style: const TextStyle(
fontFamily: FintechWalletHomeScreen._font,
fontSize: 11,
fontWeight: FontWeight.w400,
letterSpacing: 0.24,
color: FintechWalletHomeScreen._muted,
),
),
],
);
}
}
// ── Section header ───────────────────────────────────────────────────────────
class _SectionHeader extends StatelessWidget {
const _SectionHeader();
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const <Widget>[
Text(
'Recent Transactions',
style: TextStyle(
fontFamily: FintechWalletHomeScreen._font,
fontSize: 16,
fontWeight: FontWeight.w500,
letterSpacing: 0.24,
color: Colors.white,
),
),
Text(
'See all',
style: TextStyle(
fontFamily: FintechWalletHomeScreen._font,
fontSize: 14,
fontWeight: FontWeight.w500,
letterSpacing: 0.24,
color: FintechWalletHomeScreen._brand,
),
),
],
);
}
}
// ── Transactions ─────────────────────────────────────────────────────────────
class _TransactionList extends StatelessWidget {
const _TransactionList();
@override
Widget build(BuildContext context) {
const List<_Txn> txns = <_Txn>[
_Txn(
icon: Icons.movie_outlined,
tint: FintechWalletHomeScreen._red,
name: 'Netflix',
date: 'Jun 9',
amount: r'-$15.99',
credit: false,
),
_Txn(
icon: Icons.work_outline_rounded,
tint: FintechWalletHomeScreen._teal,
name: 'Salary',
date: 'Jun 8',
amount: r'+$3,200.00',
credit: true,
),
_Txn(
icon: Icons.restaurant_rounded,
tint: FintechWalletHomeScreen._amber,
name: 'Uber Eats',
date: 'Jun 7',
amount: r'-$24.50',
credit: false,
),
_Txn(
icon: Icons.shopping_bag_outlined,
tint: FintechWalletHomeScreen._brand,
name: 'Amazon',
date: 'Jun 6',
amount: r'-$89.99',
credit: false,
),
];
return Column(
children: <Widget>[
for (final _Txn t in txns)
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: _TransactionRow(txn: t),
),
],
);
}
}
class _Txn {
const _Txn({
required this.icon,
required this.tint,
required this.name,
required this.date,
required this.amount,
required this.credit,
});
final IconData icon;
final Color tint;
final String name;
final String date;
final String amount;
final bool credit;
}
class _TransactionRow extends StatelessWidget {
const _TransactionRow({required this.txn});
final _Txn txn;
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: txn.tint.withValues(alpha: 0.16),
),
child: Icon(txn.icon, size: 20, color: txn.tint),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
txn.name,
style: const TextStyle(
fontFamily: FintechWalletHomeScreen._font,
fontSize: 15,
fontWeight: FontWeight.w500,
letterSpacing: 0.24,
color: Colors.white,
),
),
const SizedBox(height: 2),
Text(
txn.date,
style: const TextStyle(
fontFamily: FintechWalletHomeScreen._font,
fontSize: 12,
fontWeight: FontWeight.w400,
letterSpacing: 0.24,
color: FintechWalletHomeScreen._muted,
),
),
],
),
),
const SizedBox(width: 12),
Text(
txn.amount,
style: TextStyle(
fontFamily: FintechWalletHomeScreen._font,
fontSize: 15,
fontWeight: FontWeight.w500,
letterSpacing: 0.24,
color:
txn.credit ? FintechWalletHomeScreen._teal : Colors.white,
),
),
],
);
}
}
// ── Bottom nav ───────────────────────────────────────────────────────────────
class _BottomNav extends StatelessWidget {
const _BottomNav({this.onTabSelected});
final ValueChanged<int>? onTabSelected;
static const List<({IconData icon, String label})> _tabs =
<({IconData icon, String label})>[
(icon: Icons.home_rounded, label: 'Home'),
(icon: Icons.swap_horiz_rounded, label: 'Payments'),
(icon: Icons.bar_chart_rounded, label: 'Analytics'),
(icon: Icons.grid_view_rounded, label: 'Hub'),
(icon: Icons.person_outline_rounded, label: 'Profile'),
];
@override
Widget build(BuildContext context) {
return Container(
decoration: const BoxDecoration(
color: FintechWalletHomeScreen._surface,
border: Border(
top: BorderSide(color: FintechWalletHomeScreen._hairline),
),
),
child: SafeArea(
top: false,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
for (int i = 0; i < _tabs.length; i++)
_NavItem(
icon: _tabs[i].icon,
label: _tabs[i].label,
active: i == 0,
onTap: () => onTabSelected?.call(i),
),
],
),
),
),
);
}
}
class _NavItem extends StatelessWidget {
const _NavItem({
required this.icon,
required this.label,
required this.active,
required this.onTap,
});
final IconData icon;
final String label;
final bool active;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
final Color color =
active ? FintechWalletHomeScreen._brand : FintechWalletHomeScreen._muted;
return GestureDetector(
onTap: onTap,
behavior: HitTestBehavior.opaque,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(icon, size: 22, color: color),
const SizedBox(height: 4),
Text(
label,
style: TextStyle(
fontFamily: FintechWalletHomeScreen._font,
fontSize: 11,
fontWeight: active ? FontWeight.w500 : FontWeight.w400,
letterSpacing: 0.24,
color: color,
),
),
],
),
);
}
}
Plus bundled 2 binary assets (fonts/images). The CLI and MCP install those for you automatically.
Two faster ways to add it
Copy-paste works, but you can skip it entirely.
1. FlutterKit CLI
One command drops this screen — and its fonts — straight into your project.
$ flutterkit add fintech-wallet-home2. AI agent (MCP)
Connect FlutterKit's MCP server in Claude or Cursor and just ask your agent to install fintech-wallet-home — it fetches and writes the files for you.
FAQ
Is this wallet home screen free to use commercially?
Yes. FlutterKit screens are free to use, including in commercial apps — copy the code from this page or install it with the CLI command and ship it in a client project or your own product. No attribution required.
Does this screen need any packages or fonts?
No pub packages at all — the vendored code declares an empty package list. The only asset is the Inter font, which ships bundled with the screen and is referenced through the `_font` constant; every icon is a built-in Material icon, and the avatar is a local asset with a monogram fallback, so nothing loads over the network.
Which Flutter version does this need?
Flutter 3.27 or newer as written, because the tints use `Color.withValues(alpha: …)`. On an older SDK, replace each `withValues(alpha: x)` with `withOpacity(x)` — but note the bottom nav's `({IconData icon, String label})` records still require Dart 3, so Flutter 3.10 is the hard floor.
How do I show real transactions instead of the hard-coded four?
Replace the `const List<_Txn>` inside `_TransactionList.build` with a list passed in from your state layer, mapping each API record to a `_Txn` — pick the icon and tint from your category enum, format the amount with a sign, and set `credit` from the transaction direction. Because the row only reads `_Txn` fields, nothing else changes; for long histories, swap the collection-`for` `Column` for a `ListView.builder`.
How do I wire the bottom nav to real navigation?
Pass `onTabSelected` when you construct `FintechWalletHomeScreen` and switch on the index it hands you (Home=0, Payments=1, Analytics=2, Hub=3, Profile=4). In a shell layout, forward it to your `IndexedStack` or router-branch index; you'd also lift the hard-coded `active: i == 0` into a `currentIndex` parameter so the highlight follows the selected tab.