Skip to content
This repository has been archived by the owner on Dec 12, 2024. It is now read-only.

Commit

Permalink
feat: add transaction details page (#42)
Browse files Browse the repository at this point in the history
* add localizations for txn details

* add `TransactionDetailsPage`

* add tests for `TransactionDetailsPage`

* remove todo

* use `Grid` constants

* replace with another `Grid` constant

* add `StateProvider` for transactions

* add const

* add override for empty transaction state

* fix import

* use `find.widgetWithText()`

* nit: replace `EdgeInsets.only` padding with `SizedBox` padding

* remove in-progress code

* remove unused import

* address pr comments
  • Loading branch information
ethanwlee authored Feb 2, 2024
1 parent d6acdb5 commit 386bb86
Show file tree
Hide file tree
Showing 13 changed files with 587 additions and 36 deletions.
2 changes: 1 addition & 1 deletion frontend/lib/features/app/app_tabs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class AppTabs extends HookConsumerWidget {
_TabItem(
'Home',
const Icon(Icons.home_outlined),
HomePage(),
const HomePage(),
),
_TabItem(
'Send',
Expand Down
41 changes: 22 additions & 19 deletions frontend/lib/features/home/home_page.dart
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_starter/features/deposit/deposit_page.dart';
import 'package:flutter_starter/features/home/transaction_details_page.dart';
import 'package:flutter_starter/features/withdraw/withdraw_page.dart';
import 'package:flutter_starter/l10n/app_localizations.dart';
import 'package:flutter_starter/shared/grid.dart';
import 'package:flutter_starter/shared/transaction.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

class HomePage extends HookWidget {
HomePage({super.key});

final List<Transaction> txns = [];
class HomePage extends HookConsumerWidget {
const HomePage({super.key});

@override
Widget build(BuildContext context) {
Widget build(BuildContext context, WidgetRef ref) {
final txns = ref.watch(transactionsProvider);

return Scaffold(
appBar: AppBar(title: Text(Loc.of(context).home)),
body: SafeArea(
Expand All @@ -30,7 +32,7 @@ class HomePage extends HookWidget {
Expanded(
child: txns.isEmpty
? _buildEmptyState(context)
: _buildTransactionsList(context),
: _buildTransactionsList(context, txns),
),
],
),
Expand Down Expand Up @@ -122,19 +124,19 @@ class HomePage extends HookWidget {
);
}

Widget _buildTransactionsList(BuildContext context) {
Widget _buildTransactionsList(BuildContext context, List<Transaction> txns) {
return ListView(
children: txns.map((txn) {
return ListTile(
// TODO: display name for payments and type for deposits/withdrawals
title: Text(
txn.title,
txn.type,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.bold,
),
),
// TODO: display status from txn
subtitle: Text(
'status',
txn.status,
style: Theme.of(context).textTheme.labelMedium?.copyWith(
fontWeight: FontWeight.w300,
),
Expand All @@ -152,16 +154,17 @@ class HomePage extends HookWidget {
child: Text('\$'),
),
),
onTap: () => Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
return TransactionDetailsPage(
txn: txn,
);
},
),
),
);
}).toList(),
);
}
}

// Will be replaced with FTL-generated types later
class Transaction {
final String title;
final double amount;

Transaction({required this.title, required this.amount});
}
244 changes: 244 additions & 0 deletions frontend/lib/features/home/transaction_details_page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_starter/l10n/app_localizations.dart';
import 'package:flutter_starter/shared/grid.dart';
import 'package:flutter_starter/shared/transaction.dart';

class TransactionDetailsPage extends HookWidget {
final Transaction txn;
const TransactionDetailsPage({required this.txn, super.key});

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(Loc.of(context).transactionDetails)),
body: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Column(
children: [
_buildHeader(context),
_buildAmount(context),
_buildStatus(context),
if (txn.status != Status.failed) _buildDetails(context)
],
),
),
),
txn.status == Status.quoted
? _buildResponseButtons(context)
: Padding(
padding: const EdgeInsets.symmetric(horizontal: Grid.side),
child: FilledButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(Loc.of(context).done),
),
),
],
),
),
);
}

Widget _buildHeader(BuildContext context) {
return Column(
children: [
const SizedBox(height: Grid.md),
ExcludeSemantics(
child: Center(
child: Container(
width: Grid.xxl,
height: Grid.xxl,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceVariant,
shape: BoxShape.circle,
),
child: Center(
// TODO: use $ or first letter of name based on txn type
child: Text(
'\$',
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
),
),
),
),
),
),
const SizedBox(height: Grid.xxs),
Text(
txn.type,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
],
);
}

Widget _buildAmount(BuildContext context) {
return Column(
children: [
const SizedBox(height: Grid.xxl),
Text(
'${txn.amount} USD',
style: Theme.of(context).textTheme.displayMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
// TODO: replace with createdAt time
const Text('Mar 1 at 10:00 am'),
],
);
}

Widget _buildStatus(BuildContext context) {
return Column(
children: [
const SizedBox(height: Grid.lg),
Icon(_getStatusIcon(txn.status),
size: Grid.md, color: _getStatusColor(context, txn.status)),
const SizedBox(height: Grid.xxs),
Text(
txn.status,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
],
);
}

IconData _getStatusIcon(String status) {
switch (status) {
case Status.quoted:
return Icons.pending;
case Status.failed:
return Icons.error;
case Status.completed:
return Icons.check_circle;
default:
return Icons.help;
}
}

Color _getStatusColor(BuildContext context, String status) {
var colorScheme = Theme.of(context).colorScheme;
switch (status) {
case Status.quoted:
return colorScheme.secondary;
case Status.failed:
return colorScheme.error;
case Status.completed:
return colorScheme.primary;
default:
return colorScheme.outlineVariant;
}
}

Widget _buildDetails(BuildContext context) {
final paymentLabel = txn.status == Status.quoted
? Loc.of(context).youPay
: txn.type == Type.deposit
? Loc.of(context).youPaid
: Loc.of(context).youReceived;

final balanceLabel = txn.status == Status.quoted
? Loc.of(context).txnTypeQuote(txn.type)
: Loc.of(context).accountBalance;

final amount = txn.status == Status.completed
? '${txn.type == Type.deposit ? '+' : '-'}${txn.amount}'
: txn.amount.toString();

return Padding(
padding: const EdgeInsets.symmetric(horizontal: Grid.side),
child: Column(
children: [
const SizedBox(height: Grid.xxl),
Padding(
padding: const EdgeInsets.symmetric(vertical: Grid.xxs),
child: Row(
children: [
Expanded(
flex: 1,
child: Text(
paymentLabel,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: Theme.of(context).colorScheme.secondary,
),
textAlign: TextAlign.left,
),
),
Expanded(
flex: 2,
child: Text(
'${txn.amount} USD',
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: Theme.of(context).colorScheme.secondary,
),
textAlign: TextAlign.right,
),
),
],
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: Grid.xxs),
child: Row(
children: [
Expanded(
child: Text(
balanceLabel,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: Theme.of(context).colorScheme.secondary,
),
textAlign: TextAlign.left,
),
),
Expanded(
child: Text(
'$amount USD',
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: Theme.of(context).colorScheme.secondary,
),
textAlign: TextAlign.right,
),
),
],
),
),
],
),
);
}

Widget _buildResponseButtons(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: Grid.sm),
child: Row(
children: [
Expanded(
child: FilledButton(
onPressed: () {},
child: Text(Loc.of(context).reject),
),
),
const SizedBox(width: Grid.sm),
Expanded(
child: FilledButton(
onPressed: () {},
child: Text(Loc.of(context).accept),
),
),
],
),
);
}
}
17 changes: 16 additions & 1 deletion frontend/lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,20 @@
"usd": "USD",
"activity": "Activity",
"noTransactionsYet": "No transactions yet",
"startByAdding": "Start by adding funds to your account!"
"startByAdding": "Start by adding funds to your account!",
"transactionDetails": "Transaction details",
"youPay": "You pay",
"youPaid": "You paid",
"youReceived": "You received",
"txnTypeQuote": "{txnType} quote",
"@txnTypeQuote": {
"placeholders": {
"txnType": {
"type": "String"
}
}
},
"accountBalance": "Account balance",
"accept": "Accept",
"reject": "Reject"
}
Loading

0 comments on commit 386bb86

Please sign in to comment.