From cff10f3a515dbf6de3fc278845adc254f3ddbe4f Mon Sep 17 00:00:00 2001 From: Serhii Date: Mon, 21 Jul 2025 19:49:08 +0300 Subject: [PATCH 1/2] ui: add copy button --- ...ake_pay_transaction_sent_bottom_sheet.dart | 161 +++++++++++++++--- .../confirm_sending_bottom_sheet_widget.dart | 91 ++++++++-- 2 files changed, 210 insertions(+), 42 deletions(-) diff --git a/lib/src/widgets/bottom_sheet/cake_pay_transaction_sent_bottom_sheet.dart b/lib/src/widgets/bottom_sheet/cake_pay_transaction_sent_bottom_sheet.dart index 6be6d389f..358ad93d8 100644 --- a/lib/src/widgets/bottom_sheet/cake_pay_transaction_sent_bottom_sheet.dart +++ b/lib/src/widgets/bottom_sheet/cake_pay_transaction_sent_bottom_sheet.dart @@ -4,6 +4,7 @@ import 'package:cake_wallet/utils/image_utill.dart'; import 'package:cake_wallet/view_model/send/output.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; class CakePayTransactionSentBottomSheet extends StatelessWidget { const CakePayTransactionSentBottomSheet({ @@ -39,18 +40,36 @@ class CakePayTransactionSentBottomSheet extends StatelessWidget { final String paymentId; final String paymentIdValue; - TextStyle _titleStyle(BuildContext ctx) => Theme.of(ctx).textTheme.bodyLarge!; + TextStyle _titleStyle(BuildContext ctx) => + Theme + .of(ctx) + .textTheme + .bodyLarge!; - TextStyle _valueStyle(BuildContext ctx) => Theme.of(ctx).textTheme.titleMedium!.copyWith( + TextStyle _valueStyle(BuildContext ctx) => + Theme + .of(ctx) + .textTheme + .titleMedium! + .copyWith( fontSize: 18, fontWeight: FontWeight.w600, ); - TextStyle _subStyle(BuildContext ctx) => Theme.of(ctx).textTheme.labelSmall!.copyWith( - color: Theme.of(ctx).colorScheme.onSurfaceVariant, + TextStyle _subStyle(BuildContext ctx) => + Theme + .of(ctx) + .textTheme + .labelSmall! + .copyWith( + color: Theme + .of(ctx) + .colorScheme + .onSurfaceVariant, ); - Widget _buildHeader(BuildContext ctx) => Column( + Widget _buildHeader(BuildContext ctx) => + Column( children: [ const SizedBox(height: 12), Container( @@ -58,7 +77,10 @@ class CakePayTransactionSentBottomSheet extends StatelessWidget { height: 5, decoration: BoxDecoration( borderRadius: BorderRadius.circular(4), - color: Theme.of(ctx).colorScheme.onSurface, + color: Theme + .of(ctx) + .colorScheme + .onSurface, ), ), const SizedBox(height: 20), @@ -69,11 +91,15 @@ class CakePayTransactionSentBottomSheet extends StatelessWidget { const SizedBox(width: 6), Text( titleText, - style: Theme.of(ctx).textTheme.titleLarge!.copyWith( - fontSize: 20, - fontWeight: FontWeight.w600, - decoration: TextDecoration.none, - ), + style: Theme + .of(ctx) + .textTheme + .titleLarge! + .copyWith( + fontSize: 20, + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), ), ], ), @@ -81,7 +107,8 @@ class CakePayTransactionSentBottomSheet extends StatelessWidget { ], ); - Widget _buildBody(BuildContext context) => Padding( + Widget _buildBody(BuildContext context) => + Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: Column( children: [ @@ -112,25 +139,49 @@ class CakePayTransactionSentBottomSheet extends StatelessWidget { itemSubTitleStyle: _subStyle(context), imagePath: output.parsedAddress.profileImageUrl, ), + const SizedBox(height: 8), + _StandardTile( + itemTitle: paymentId + ': \n' + paymentIdValue, + titleStyle: _titleStyle(context).copyWith( + fontSize: 14 + ), + itemValue: '', + itemValueStyle: _valueStyle(context), + copyButton: true, + copyButtonOnPressed: () async => + await Clipboard.setData(ClipboardData(text: paymentIdValue)), + ), + Padding( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), child: Column( children: [ - Text(paymentId + ': ' + paymentIdValue, - style: Theme.of(context).textTheme.bodyLarge, textAlign: TextAlign.center), const SizedBox(height: 18), Image.asset('assets/images/envelope.png'), const SizedBox(height: 18), Text( - S.of(context).cake_pay_card_email_delivered_message, - style: Theme.of(context).textTheme.bodyLarge, + S + .of(context) + .cake_pay_card_email_delivered_message, + style: Theme + .of(context) + .textTheme + .bodyLarge, textAlign: TextAlign.center, ), const SizedBox(height: 24), PrimaryButton( - text: S.of(context).close, - color: Theme.of(context).colorScheme.primary, - textColor: Theme.of(context).colorScheme.onPrimaryContainer, + text: S + .of(context) + .close, + color: Theme + .of(context) + .colorScheme + .primary, + textColor: Theme + .of(context) + .colorScheme + .onPrimaryContainer, onPressed: onClose, ), ], @@ -142,12 +193,18 @@ class CakePayTransactionSentBottomSheet extends StatelessWidget { @override Widget build(BuildContext context) { - final maxHeight = MediaQuery.of(context).size.height * 0.9; + final maxHeight = MediaQuery + .of(context) + .size + .height * 0.9; return ClipRRect( borderRadius: const BorderRadius.vertical(top: Radius.circular(30)), child: Material( - color: Theme.of(context).colorScheme.surface, + color: Theme + .of(context) + .colorScheme + .surface, child: ConstrainedBox( constraints: BoxConstraints(maxHeight: maxHeight), child: SingleChildScrollView( @@ -174,6 +231,8 @@ class _StandardTile extends StatelessWidget { this.itemSubTitle, this.itemSubTitleStyle, this.imagePath, + this.copyButton = false, + this.copyButtonOnPressed, }); final String itemTitle; @@ -183,6 +242,8 @@ class _StandardTile extends StatelessWidget { final String? itemSubTitle; final TextStyle? itemSubTitleStyle; final String? imagePath; + final bool copyButton; + final VoidCallback? copyButtonOnPressed; @override Widget build(BuildContext context) { @@ -193,12 +254,17 @@ class _StandardTile extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), - color: Theme.of(context).colorScheme.surfaceContainerLowest.withAlpha(80)), + color: Theme + .of(context) + .colorScheme + .surfaceContainerLowest + .withAlpha(80)), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Row( + mainAxisAlignment: copyButton ? MainAxisAlignment.spaceBetween : MainAxisAlignment.start, children: [ if (imagePath != null) Padding( @@ -216,10 +282,19 @@ class _StandardTile extends StatelessWidget { itemTitle, style: titleStyle, overflow: TextOverflow.ellipsis, - maxLines: 1, - softWrap: false, + maxLines: 2, + softWrap: true, )), ), + SizedBox(width: 8), + if (copyButton) + RoundedIconButton( + icon: Icons.copy_all_outlined, + onPressed: copyButtonOnPressed ?? () {}, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + ), ], ), ), @@ -237,3 +312,41 @@ class _StandardTile extends StatelessWidget { ); } } + +class RoundedIconButton extends StatelessWidget { + const RoundedIconButton({this.icon, + this.iconWidget, + required this.onPressed, + this.shape, + this.width, + this.height, + this.iconSize, + this.fillColor}); + + final IconData? icon; + final Widget? iconWidget; + final VoidCallback onPressed; + final ShapeBorder? shape; + final double? width; + final double? height; + final double? iconSize; + final Color? fillColor; + + @override + Widget build(BuildContext context) { + final colorScheme = Theme + .of(context) + .colorScheme; + return RawMaterialButton( + onPressed: onPressed, + fillColor: fillColor ?? colorScheme.surfaceContainerHighest, + elevation: 0, + constraints: BoxConstraints.tightFor(width: width ?? 30, height: height ?? 30), + padding: EdgeInsets.zero, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: shape ?? const CircleBorder(), + child: icon != null + ? Icon(icon, size: iconSize ?? 14, color: colorScheme.onSurface) + : iconWidget); + } +} diff --git a/lib/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart b/lib/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart index a8c643ac4..e71cd76af 100644 --- a/lib/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart +++ b/lib/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart @@ -10,6 +10,7 @@ import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/pending_transaction.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'base_bottom_sheet_widget.dart'; @@ -45,7 +46,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet { _currentTheme = currentTheme, super( titleText: titleText, - maxHeight: 900, + maxHeight: 900, titleIconPath: titleIconPath, currentTheme: currentTheme, footerType: footerType, @@ -115,6 +116,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet { itemSubTitleTextStyle: itemSubTitleTextStyle, tileBackgroundColor: tileBackgroundColor, applyAddressFormatting: false, + copyButton: true, )), ), if (explanation != null) @@ -307,20 +309,20 @@ class StandardInfoTile extends StatelessWidget { } class AddressTile extends StatelessWidget { - const AddressTile({ - super.key, - required this.itemTitle, - required this.itemTitleTextStyle, - required this.address, - required this.itemSubTitleTextStyle, - required this.tileBackgroundColor, - required this.walletType, - this.amountTextStyle, - this.applyAddressFormatting = true, - this.imagePath, - this.amount, - this.itemSubTitle, - }); + const AddressTile( + {super.key, + required this.itemTitle, + required this.itemTitleTextStyle, + required this.address, + required this.itemSubTitleTextStyle, + required this.tileBackgroundColor, + required this.walletType, + this.amountTextStyle, + this.applyAddressFormatting = true, + this.imagePath, + this.amount, + this.itemSubTitle, + this.copyButton = false}); final String itemTitle; final TextStyle itemTitleTextStyle; @@ -333,6 +335,7 @@ class AddressTile extends StatelessWidget { final bool applyAddressFormatting; final String? imagePath; final String? itemSubTitle; + final bool copyButton; @override Widget build(BuildContext context) { @@ -387,9 +390,24 @@ class AddressTile extends StatelessWidget { fontWeight: FontWeight.w600, decoration: TextDecoration.none, )) - : Text( - address, - style: itemTitleTextStyle, + : Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(address, + style: copyButton + ? itemTitleTextStyle.copyWith(fontSize: 12) + : itemTitleTextStyle), + SizedBox(width: 8), + if (copyButton) + RoundedIconButton( + icon: Icons.copy_all_outlined, + onPressed: () async => + await Clipboard.setData(ClipboardData(text: address)), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + ), + ], ), itemSubTitle == null ? Container() @@ -490,3 +508,40 @@ class ExpansionAddressTile extends StatelessWidget { ); } } + +class RoundedIconButton extends StatelessWidget { + const RoundedIconButton( + {this.icon, + this.iconWidget, + required this.onPressed, + this.shape, + this.width, + this.height, + this.iconSize, + this.fillColor}); + + final IconData? icon; + final Widget? iconWidget; + final VoidCallback onPressed; + final ShapeBorder? shape; + final double? width; + final double? height; + final double? iconSize; + final Color? fillColor; + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + return RawMaterialButton( + onPressed: onPressed, + fillColor: fillColor ?? colorScheme.surfaceContainerHighest, + elevation: 0, + constraints: BoxConstraints.tightFor(width: width ?? 30, height: height ?? 30), + padding: EdgeInsets.zero, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: shape ?? const CircleBorder(), + child: icon != null + ? Icon(icon, size: iconSize ?? 14, color: colorScheme.onSurface) + : iconWidget); + } +} From 37e9d2c6e362f19a8a106375acad499733fefb7b Mon Sep 17 00:00:00 2001 From: Serhii Date: Tue, 22 Jul 2025 11:46:26 +0300 Subject: [PATCH 2/2] ui: extract RoundedIconButton --- ...ake_pay_transaction_sent_bottom_sheet.dart | 39 +------------------ .../confirm_sending_bottom_sheet_widget.dart | 38 +----------------- lib/src/widgets/rounded_icon_button.dart | 38 ++++++++++++++++++ 3 files changed, 40 insertions(+), 75 deletions(-) create mode 100644 lib/src/widgets/rounded_icon_button.dart diff --git a/lib/src/widgets/bottom_sheet/cake_pay_transaction_sent_bottom_sheet.dart b/lib/src/widgets/bottom_sheet/cake_pay_transaction_sent_bottom_sheet.dart index 358ad93d8..da91926b2 100644 --- a/lib/src/widgets/bottom_sheet/cake_pay_transaction_sent_bottom_sheet.dart +++ b/lib/src/widgets/bottom_sheet/cake_pay_transaction_sent_bottom_sheet.dart @@ -1,5 +1,6 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/src/widgets/rounded_icon_button.dart'; import 'package:cake_wallet/utils/image_utill.dart'; import 'package:cake_wallet/view_model/send/output.dart'; import 'package:cw_core/crypto_currency.dart'; @@ -312,41 +313,3 @@ class _StandardTile extends StatelessWidget { ); } } - -class RoundedIconButton extends StatelessWidget { - const RoundedIconButton({this.icon, - this.iconWidget, - required this.onPressed, - this.shape, - this.width, - this.height, - this.iconSize, - this.fillColor}); - - final IconData? icon; - final Widget? iconWidget; - final VoidCallback onPressed; - final ShapeBorder? shape; - final double? width; - final double? height; - final double? iconSize; - final Color? fillColor; - - @override - Widget build(BuildContext context) { - final colorScheme = Theme - .of(context) - .colorScheme; - return RawMaterialButton( - onPressed: onPressed, - fillColor: fillColor ?? colorScheme.surfaceContainerHighest, - elevation: 0, - constraints: BoxConstraints.tightFor(width: width ?? 30, height: height ?? 30), - padding: EdgeInsets.zero, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - shape: shape ?? const CircleBorder(), - child: icon != null - ? Icon(icon, size: iconSize ?? 14, color: colorScheme.onSurface) - : iconWidget); - } -} diff --git a/lib/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart b/lib/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart index e71cd76af..c5c59d80e 100644 --- a/lib/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart +++ b/lib/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/widgets/rounded_icon_button.dart'; import 'package:cake_wallet/src/widgets/standard_slide_button_widget.dart'; import 'package:cake_wallet/themes/core/material_base_theme.dart'; import 'package:cake_wallet/themes/utils/custom_theme_colors.dart'; @@ -508,40 +509,3 @@ class ExpansionAddressTile extends StatelessWidget { ); } } - -class RoundedIconButton extends StatelessWidget { - const RoundedIconButton( - {this.icon, - this.iconWidget, - required this.onPressed, - this.shape, - this.width, - this.height, - this.iconSize, - this.fillColor}); - - final IconData? icon; - final Widget? iconWidget; - final VoidCallback onPressed; - final ShapeBorder? shape; - final double? width; - final double? height; - final double? iconSize; - final Color? fillColor; - - @override - Widget build(BuildContext context) { - final colorScheme = Theme.of(context).colorScheme; - return RawMaterialButton( - onPressed: onPressed, - fillColor: fillColor ?? colorScheme.surfaceContainerHighest, - elevation: 0, - constraints: BoxConstraints.tightFor(width: width ?? 30, height: height ?? 30), - padding: EdgeInsets.zero, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - shape: shape ?? const CircleBorder(), - child: icon != null - ? Icon(icon, size: iconSize ?? 14, color: colorScheme.onSurface) - : iconWidget); - } -} diff --git a/lib/src/widgets/rounded_icon_button.dart b/lib/src/widgets/rounded_icon_button.dart new file mode 100644 index 000000000..72d5491f3 --- /dev/null +++ b/lib/src/widgets/rounded_icon_button.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; + +class RoundedIconButton extends StatelessWidget { + const RoundedIconButton( + {this.icon, + this.iconWidget, + required this.onPressed, + this.shape, + this.width, + this.height, + this.iconSize, + this.fillColor}); + + final IconData? icon; + final Widget? iconWidget; + final VoidCallback onPressed; + final ShapeBorder? shape; + final double? width; + final double? height; + final double? iconSize; + final Color? fillColor; + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + return RawMaterialButton( + onPressed: onPressed, + fillColor: fillColor ?? colorScheme.surfaceContainerHighest, + elevation: 0, + constraints: BoxConstraints.tightFor(width: width ?? 30, height: height ?? 30), + padding: EdgeInsets.zero, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: shape ?? const CircleBorder(), + child: icon != null + ? Icon(icon, size: iconSize ?? 14, color: colorScheme.onSurface) + : iconWidget); + } +}