mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-18 05:51:34 +02:00
add copy indicator on tx details screen (#3152)
This commit is contained in:
@@ -15,13 +15,15 @@ class ListItemRegularRow extends ListItem {
|
||||
this.trailingWidget,
|
||||
this.truncateTrailingText = false,
|
||||
this.foregroundColor,
|
||||
this.trailingIconSize
|
||||
this.trailingIconSize,
|
||||
this.copyableText
|
||||
});
|
||||
|
||||
final String? subtitle;
|
||||
final String? trailingText;
|
||||
final String? iconPath;
|
||||
final String? trailingIconPath;
|
||||
final String? copyableText;
|
||||
final VoidCallback? onTap;
|
||||
final bool showArrow;
|
||||
final Widget? bottomWidget;
|
||||
|
||||
@@ -99,10 +99,7 @@ class _TransactionDetailsModalState extends State<TransactionDetailsModal> {
|
||||
item.value.length > 25;
|
||||
|
||||
return ListItemRegularRow(
|
||||
onTap: () {
|
||||
Clipboard.setData(
|
||||
ClipboardData(text: item.value));
|
||||
},
|
||||
copyableText: item.value,
|
||||
showArrow: false,
|
||||
keyValue:
|
||||
((item.key as ValueKey?)?.value as String?) ??
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class CopyWrapper extends StatefulWidget {
|
||||
const CopyWrapper(
|
||||
{super.key,
|
||||
this.data,
|
||||
required this.builder,
|
||||
this.duration = const Duration(milliseconds: 1200)});
|
||||
|
||||
final ClipboardData? data;
|
||||
final Widget Function(BuildContext, bool) builder;
|
||||
final Duration duration;
|
||||
|
||||
@override
|
||||
State<CopyWrapper> createState() => _CopyWrapperState();
|
||||
}
|
||||
|
||||
class _CopyWrapperState extends State<CopyWrapper> {
|
||||
bool copied = false;
|
||||
|
||||
void handleCopy() async {
|
||||
if (widget.data == null) return;
|
||||
Clipboard.setData(widget.data!);
|
||||
if (await shouldShowCopied()) {
|
||||
setState(() => copied = true);
|
||||
Future.delayed(widget.duration, () {
|
||||
if (mounted) setState(() => copied = false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: handleCopy,
|
||||
child: widget.builder(context, copied),
|
||||
);
|
||||
}
|
||||
|
||||
// android 13 (sdk 33) added a built-in "text was copied to clipboard" ui element
|
||||
Future<bool> shouldShowCopied() async {
|
||||
if (!Platform.isAndroid) return true;
|
||||
|
||||
try {
|
||||
final deviceInfo = DeviceInfoPlugin();
|
||||
final androidInfo = await deviceInfo.androidInfo;
|
||||
final sdk = androidInfo.version.sdkInt;
|
||||
|
||||
return sdk < 33;
|
||||
} catch (_) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ class ListItemStyleWrapper extends StatelessWidget {
|
||||
required this.isFirstInSection,
|
||||
required this.isLastInSection,
|
||||
required this.builder,
|
||||
this.backgroundColor,
|
||||
this.onTap,
|
||||
this.iconPath,
|
||||
this.height,
|
||||
@@ -16,6 +17,7 @@ class ListItemStyleWrapper extends StatelessWidget {
|
||||
final bool isLastInSection;
|
||||
final double? height;
|
||||
final VoidCallback? onTap;
|
||||
final Color? backgroundColor;
|
||||
final Widget Function(BuildContext context, TextStyle textStyle, TextStyle labelStyle) builder;
|
||||
|
||||
@override
|
||||
@@ -52,7 +54,7 @@ class ListItemStyleWrapper extends StatelessWidget {
|
||||
shape: RoundedSuperellipseBorder(
|
||||
borderRadius: radius,
|
||||
),
|
||||
color: theme.colorScheme.surfaceContainer,
|
||||
color: backgroundColor ?? theme.colorScheme.surfaceContainer,
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/copy_wrapper.dart';
|
||||
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/new_list_row/list_Item_style_wrapper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class ListItemRegularRowWidget extends StatelessWidget {
|
||||
const ListItemRegularRowWidget({
|
||||
@@ -20,7 +22,8 @@ class ListItemRegularRowWidget extends StatelessWidget {
|
||||
this.foregroundColor,
|
||||
this.trailingIconSize,
|
||||
this.bottomWidget,
|
||||
this.trailingWidget
|
||||
this.trailingWidget,
|
||||
this.copyableText
|
||||
});
|
||||
|
||||
final String keyValue;
|
||||
@@ -38,6 +41,7 @@ class ListItemRegularRowWidget extends StatelessWidget {
|
||||
final bool truncateTrailingText;
|
||||
final Color? foregroundColor;
|
||||
final double? trailingIconSize;
|
||||
final String? copyableText;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -47,82 +51,102 @@ class ListItemRegularRowWidget extends StatelessWidget {
|
||||
? "${trailingText!.substring(0, 17)}..."
|
||||
: trailingText;
|
||||
|
||||
return ListItemStyleWrapper(
|
||||
onTap: onTap,
|
||||
iconPath: iconPath,
|
||||
isFirstInSection: isFirstInSection,
|
||||
isLastInSection: isLastInSection,
|
||||
builder: (context, textStyle, labelStyle) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
return CopyWrapper(
|
||||
data: copyableText != null ? ClipboardData(text: copyableText!) : null,
|
||||
builder: (context, copied) => AnimatedSwitcher(
|
||||
duration: Duration(milliseconds: 150),
|
||||
child: ListItemStyleWrapper(
|
||||
key: ValueKey(copied),
|
||||
backgroundColor: copied ? Theme.of(context).colorScheme.surfaceContainerHigh : null,
|
||||
onTap: onTap,
|
||||
iconPath: iconPath,
|
||||
isFirstInSection: isFirstInSection,
|
||||
isLastInSection: isLastInSection,
|
||||
builder: (context, textStyle, labelStyle) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
if (iconPath != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 12.0),
|
||||
child: CakeImageWidget(imageUrl: iconPath!, width: 24, height: 24,)
|
||||
),
|
||||
Flexible(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(label,
|
||||
style: foregroundColor == null
|
||||
? textStyle
|
||||
: textStyle.copyWith(color: foregroundColor)),
|
||||
if (subtitle != null)
|
||||
Text(
|
||||
subtitle!,
|
||||
style: labelStyle.copyWith(fontSize: 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
if (trailingTextToShow != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8.0),
|
||||
child: Text(
|
||||
trailingTextToShow,
|
||||
style: labelStyle,
|
||||
),
|
||||
),
|
||||
if (trailingWidget != null)
|
||||
trailingWidget!
|
||||
else if (trailingIconPath != null)
|
||||
CakeImageWidget(imageUrl:
|
||||
trailingIconPath!,
|
||||
height: trailingIconSize ?? 18,
|
||||
width:trailingIconSize ?? 18,
|
||||
colorFilter: ColorFilter.mode(foregroundColor ?? Theme.of(context).colorScheme.onSurfaceVariant,BlendMode.srcIn),
|
||||
)
|
||||
else if (showArrow)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 7.0),
|
||||
child: CakeImageWidget(imageUrl:
|
||||
"assets/new-ui/arrow_forward.svg",
|
||||
height: 14,
|
||||
color: theme.colorScheme.onSurfaceVariant
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
if (iconPath != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 12.0),
|
||||
child: CakeImageWidget(
|
||||
imageUrl: iconPath!,
|
||||
width: 24,
|
||||
height: 24,
|
||||
)),
|
||||
Flexible(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (copied)
|
||||
Text(
|
||||
S.of(context).copied,
|
||||
style: textStyle.copyWith(
|
||||
color: Theme.of(context).colorScheme.primary),
|
||||
)
|
||||
else
|
||||
Text(label,
|
||||
style: foregroundColor == null
|
||||
? textStyle
|
||||
: textStyle.copyWith(color: foregroundColor)),
|
||||
if (subtitle != null)
|
||||
Text(
|
||||
subtitle!,
|
||||
style: labelStyle.copyWith(fontSize: 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
if (trailingTextToShow != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8.0),
|
||||
child: Text(
|
||||
trailingTextToShow,
|
||||
style: labelStyle,
|
||||
),
|
||||
),
|
||||
if (trailingWidget != null)
|
||||
trailingWidget!
|
||||
else if (trailingIconPath != null)
|
||||
CakeImageWidget(
|
||||
imageUrl: trailingIconPath!,
|
||||
height: trailingIconSize ?? 18,
|
||||
width: trailingIconSize ?? 18,
|
||||
colorFilter: ColorFilter.mode(
|
||||
foregroundColor ?? Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
BlendMode.srcIn),
|
||||
)
|
||||
else if (showArrow)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 7.0),
|
||||
child: CakeImageWidget(imageUrl:
|
||||
"assets/new-ui/arrow_forward.svg",
|
||||
height: 14,
|
||||
color: theme.colorScheme.onSurfaceVariant
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
if (bottomWidget != null) bottomWidget!
|
||||
],
|
||||
),
|
||||
if (bottomWidget != null) bottomWidget!
|
||||
],
|
||||
);
|
||||
});
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +103,7 @@ class NewListSections extends StatelessWidget {
|
||||
showArrow: item.showArrow,
|
||||
truncateTrailingText: item.truncateTrailingText,
|
||||
foregroundColor: item.foregroundColor,
|
||||
copyableText: item.copyableText,
|
||||
trailingIconSize: item.trailingIconSize,
|
||||
trailingWidget: item.trailingWidget,
|
||||
bottomWidget: item.bottomWidget,
|
||||
|
||||
Reference in New Issue
Block a user