Skip to content

Commit

Permalink
feat(cat-voices): wallet link details (#905)
Browse files Browse the repository at this point in the history
* refactor: select wallet

* feat: add wallet address formatter

* refactor: restructure panels

* feat: implement wallet details

* feat: add loading animation

* chore: revert unintended changes

* chore: revert

* chore: cleanup

* chore: cleanup

---------

Co-authored-by: Damian Moliński <[email protected]>
  • Loading branch information
dtscalac and damian-molinski authored Sep 30, 2024
1 parent a9b3ee6 commit 4bef0d5
Show file tree
Hide file tree
Showing 23 changed files with 504 additions and 85 deletions.
15 changes: 11 additions & 4 deletions catalyst_voices/lib/pages/account/account_popup.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'package:catalyst_cardano_serialization/catalyst_cardano_serialization.dart';
import 'package:catalyst_voices/widgets/widgets.dart';
import 'package:catalyst_voices_assets/catalyst_voices_assets.dart';
import 'package:catalyst_voices_brands/catalyst_voices_brands.dart';
import 'package:catalyst_voices_shared/catalyst_voices_shared.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

Expand Down Expand Up @@ -41,7 +43,12 @@ class AccountPopup extends StatelessWidget {
walletName: 'Wallet name',
walletBalance: '₳ 1,750,000',
accountType: 'Basis',
walletAddress: 'addr1_H4543...45GH',
/* cSpell:disable */
walletAddress: ShelleyAddress.fromBech32(
'addr_test1vzpwq95z3xyum8vqndgdd'
'9mdnmafh3djcxnc6jemlgdmswcve6tkw',
),
/* cSpell:enable */
),
),
const PopupMenuItem(
Expand Down Expand Up @@ -85,7 +92,7 @@ class _Header extends StatelessWidget {
final String walletName;
final String walletBalance;
final String accountType;
final String walletAddress;
final ShelleyAddress walletAddress;

const _Header({
required this.accountLetter,
Expand Down Expand Up @@ -146,14 +153,14 @@ class _Header extends StatelessWidget {
children: [
Expanded(
child: Text(
walletAddress,
WalletAddressFormatter.formatShort(walletAddress),
style: Theme.of(context).textTheme.bodyLarge,
),
),
InkWell(
onTap: () async {
await Clipboard.setData(
ClipboardData(text: walletAddress),
ClipboardData(text: walletAddress.toBech32()),
);
},
child: VoicesAssets.icons.clipboardCopy.buildIcon(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ import 'package:catalyst_voices/widgets/widgets.dart';
import 'package:catalyst_voices_assets/catalyst_voices_assets.dart';
import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart';
import 'package:catalyst_voices_localization/catalyst_voices_localization.dart';
import 'package:catalyst_voices_shared/catalyst_voices_shared.dart';
import 'package:flutter/material.dart';
import 'package:result_type/result_type.dart';

/// Callback called when a [wallet] is selected.
typedef _OnSelectWallet = Future<void> Function(CardanoWallet wallet);

class SelectWalletPanel extends StatefulWidget {
final Result<List<CardanoWallet>, Exception>? walletsResult;

Expand All @@ -22,7 +26,7 @@ class _SelectWalletPanelState extends State<SelectWalletPanel> {
@override
void initState() {
super.initState();
_sendRefreshEvent();
_refreshWallets();
}

@override
Expand All @@ -44,7 +48,8 @@ class _SelectWalletPanelState extends State<SelectWalletPanel> {
Expanded(
child: _Wallets(
result: widget.walletsResult,
onRefreshTap: _sendRefreshEvent,
onRefreshTap: _refreshWallets,
onSelectWallet: _onSelectWallet,
),
),
const SizedBox(height: 24),
Expand All @@ -63,25 +68,31 @@ class _SelectWalletPanelState extends State<SelectWalletPanel> {
);
}

void _sendRefreshEvent() {
RegistrationCubit.of(context).refreshCardanoWallets();
void _refreshWallets() {
RegistrationCubit.of(context).refreshWallets();
}

Future<void> _onSelectWallet(CardanoWallet wallet) async {
return RegistrationCubit.of(context).selectWallet(wallet);
}
}

class _Wallets extends StatelessWidget {
final Result<List<CardanoWallet>, Exception>? result;
final _OnSelectWallet onSelectWallet;
final VoidCallback onRefreshTap;

const _Wallets({
this.result,
required this.onSelectWallet,
required this.onRefreshTap,
});

@override
Widget build(BuildContext context) {
return switch (result) {
Success(:final value) => value.isNotEmpty
? _WalletsList(wallets: value)
? _WalletsList(wallets: value, onSelectWallet: onSelectWallet)
: _WalletsEmpty(onRetry: onRefreshTap),
Failure() => _WalletsError(onRetry: onRefreshTap),
_ => const Center(child: VoicesCircularProgressIndicator()),
Expand All @@ -91,27 +102,70 @@ class _Wallets extends StatelessWidget {

class _WalletsList extends StatelessWidget {
final List<CardanoWallet> wallets;
final _OnSelectWallet onSelectWallet;

const _WalletsList({required this.wallets});
const _WalletsList({
required this.wallets,
required this.onSelectWallet,
});

@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: wallets.length,
itemBuilder: (context, index) {
final wallet = wallets[index];
return VoicesWalletTile(
iconSrc: wallet.icon,
name: Text(wallet.name),
onTap: () {
RegistrationCubit.of(context).nextStep();
},
return _WalletTile(
wallet: wallets[index],
onSelectWallet: onSelectWallet,
);
},
);
}
}

class _WalletTile extends StatefulWidget {
final CardanoWallet wallet;
final _OnSelectWallet onSelectWallet;

const _WalletTile({
required this.wallet,
required this.onSelectWallet,
});

@override
State<_WalletTile> createState() => _WalletTileState();
}

class _WalletTileState extends State<_WalletTile> {
bool _isLoading = false;

@override
Widget build(BuildContext context) {
return VoicesWalletTile(
iconSrc: widget.wallet.icon,
name: Text(widget.wallet.name),
isLoading: _isLoading,
onTap: _onSelectWallet,
);
}

Future<void> _onSelectWallet() async {
try {
setState(() {
_isLoading = true;
});

await widget.onSelectWallet(widget.wallet).withMinimumDelay();
} finally {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
}

class _WalletsEmpty extends StatelessWidget {
final VoidCallback onRetry;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import 'package:catalyst_cardano/catalyst_cardano.dart';
import 'package:catalyst_voices/widgets/widgets.dart';
import 'package:catalyst_voices_assets/catalyst_voices_assets.dart';
import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart';
import 'package:catalyst_voices_brands/catalyst_voices_brands.dart';
import 'package:catalyst_voices_localization/catalyst_voices_localization.dart';
import 'package:catalyst_voices_models/catalyst_voices_models.dart';
import 'package:catalyst_voices_shared/catalyst_voices_shared.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class WalletDetailsPanel extends StatelessWidget {
final CardanoWalletDetails details;

const WalletDetailsPanel({
super.key,
required this.details,
});

@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 24),
Text(
context.l10n.walletLinkWalletDetailsTitle,
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 32),
_WalletExtension(wallet: details.wallet),
const SizedBox(height: 16),
Text(
context.l10n.walletLinkWalletDetailsContent(details.wallet.name),
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 24),
_WalletSummary(details: details),
const Spacer(),
const _Navigation(),
],
);
}
}

class _WalletExtension extends StatelessWidget {
final CardanoWallet wallet;

const _WalletExtension({required this.wallet});

@override
Widget build(BuildContext context) {
return Row(
children: [
const SizedBox(width: 8),
VoicesWalletTileIcon(iconSrc: wallet.icon),
const SizedBox(width: 12),
Text(wallet.name, style: Theme.of(context).textTheme.bodyLarge),
const SizedBox(width: 8),
VoicesAvatar(
radius: 10,
padding: const EdgeInsets.all(4),
icon: VoicesAssets.icons.check.buildIcon(),
foregroundColor: Theme.of(context).colors.success,
backgroundColor: Theme.of(context).colors.successContainer,
),
],
);
}
}

class _WalletSummary extends StatelessWidget {
final CardanoWalletDetails details;

const _WalletSummary({required this.details});

@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
border: Border.all(
width: 1.5,
color: Theme.of(context).colors.outlineBorderVariant!,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
context.l10n.walletDetectionSummary,
style: Theme.of(context).textTheme.titleSmall,
),
const SizedBox(height: 16),
_WalletSummaryItem(
label: Text(context.l10n.walletBalance),
value: Text(CryptocurrencyFormatter.formatAmount(details.balance)),
),
const SizedBox(height: 12),
_WalletSummaryItem(
label: Text(context.l10n.walletAddress),
value: Row(
children: [
Text(WalletAddressFormatter.formatShort(details.address)),
const SizedBox(width: 6),
InkWell(
onTap: () async {
await Clipboard.setData(
ClipboardData(text: details.address.toBech32()),
);
},
child: VoicesAssets.icons.clipboardCopy.buildIcon(size: 16),
),
],
),
),
],
),
);
}
}

class _WalletSummaryItem extends StatelessWidget {
final Widget label;
final Widget value;

const _WalletSummaryItem({
required this.label,
required this.value,
});

@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: DefaultTextStyle(
style: Theme.of(context).textTheme.bodySmall!.copyWith(
fontWeight: FontWeight.bold,
height: 1,
),
child: label,
),
),
Expanded(
child: DefaultTextStyle(
style: Theme.of(context).textTheme.bodySmall!.copyWith(
height: 1,
),
child: value,
),
),
],
);
}
}

class _Navigation extends StatelessWidget {
const _Navigation();

@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: VoicesBackButton(
onTap: () => RegistrationCubit.of(context).previousStep(),
),
),
const SizedBox(width: 10),
Expanded(
child: VoicesNextButton(
onTap: () => RegistrationCubit.of(context).nextStep(),
),
),
],
);
}
}
Loading

0 comments on commit 4bef0d5

Please sign in to comment.