add haptic feedback on copy (#3401)
* add haptic feedback on copy * revert gradle
malik1004x committed
Jul 14, 2026 at 15:42 UTC
723ee479fcc9b5e2ce7431f0d1fbf9e0a26792f3
4 files changed
+36
-51
android/gradle.properties
+2
-1
@@ -1,4 +1,5 @@
1
org.gradle.jvmargs=-Xmx6144M
2
android.useAndroidX=true
3
android.enableJetifier=true
4
-android.ndk.suppressMinSdkVersionError=21
\ No newline at end of file
4
+
5
+android.ndk.suppressMinSdkVersionError=21
lib/new-ui/pages/receive_page.dart
+6
-7
@@ -271,6 +271,12 @@ class _NewReceivePageState extends State<NewReceivePage> {
271
largeQrMode: _largeQrMode,
272
showAccountsButton: widget.addressListViewModel.hasAddressList,
273
showLabelButton: widget.addressListViewModel.hasAddressList && !hasLabel,
274
+ copyData: widget.addressListViewModel.hasPayjoin
275
+ ? null
276
+ : ClipboardData(
277
+ text: widget.addressListViewModel.displayAmount.isEmpty
278
+ ? widget.addressListViewModel.uri.address
279
+ : widget.addressListViewModel.uri.toString()),
280
onCopyButtonPressed: () {
281
if (widget.addressListViewModel.hasPayjoin) {
282
showModalBottomSheet(
@@ -278,13 +284,6 @@ class _NewReceivePageState extends State<NewReceivePage> {
284
context: context,
285
builder: (context) =>
286
PayjoinCopyModal(uri: widget.addressListViewModel.uri));
281
- } else {
282
- Clipboard.setData(
283
- ClipboardData(
284
- text: widget.addressListViewModel.displayAmount.isEmpty
285
- ? widget.addressListViewModel.uri.address
286
- : widget.addressListViewModel.uri.toString()),
287
- );
287
}
288
},
289
onAmountButtonPressed: () {
lib/new-ui/widgets/copy_wrapper.dart
+1
@@ -30,6 +30,7 @@ class _CopyWrapperState extends State<CopyWrapper> {
30
void handleCopy() async {
31
if (widget.data == null) return;
32
ClipboardUtil.setSensitiveDataToClipboard(widget.data!, isSensitive: widget.isSensitive);
33
+ HapticFeedback.mediumImpact();
34
if (await shouldShowCopied()) {
35
setState(() => copied = true);
36
Future.delayed(widget.duration, () {
lib/new-ui/widgets/receive_page/receive_bottom_buttons.dart
+27
-43
@@ -4,9 +4,12 @@ import 'package:cake_wallet/generated/i18n.dart';
4
import 'package:cake_wallet/new-ui/widgets/modern_button.dart';
5
import 'package:device_info_plus/device_info_plus.dart';
6
import 'package:flutter/material.dart';
7
+import 'package:flutter/services.dart';
8
+import 'package:cake_wallet/new-ui/widgets/copy_wrapper.dart';
9
10
class ReceiveBottomButtons extends StatefulWidget {
11
final bool largeQrMode;
12
+ final ClipboardData? copyData;
13
final VoidCallback onCopyButtonPressed;
14
final VoidCallback onAmountButtonPressed;
15
final VoidCallback onLabelButtonPressed;
@@ -15,7 +18,6 @@ class ReceiveBottomButtons extends StatefulWidget {
18
final bool showAccountsButton;
19
20
const ReceiveBottomButtons({
18
- super.key,
21
required this.largeQrMode,
22
required this.onCopyButtonPressed,
23
required this.onAccountsButtonPressed,
@@ -23,6 +25,8 @@ class ReceiveBottomButtons extends StatefulWidget {
25
required this.onLabelButtonPressed,
26
required this.showLabelButton,
27
required this.showAccountsButton,
28
+ required this.copyData,
29
+ super.key,
30
});
31
32
@override
@@ -30,17 +34,6 @@ class ReceiveBottomButtons extends StatefulWidget {
34
}
35
36
class _ReceiveBottomButtonsState extends State<ReceiveBottomButtons> {
33
- bool copied = false;
34
-
35
- void handleCopy() async {
36
- widget.onCopyButtonPressed();
37
- if (await shouldShowCopied()) {
38
- setState(() => copied = true);
39
- Future.delayed(const Duration(milliseconds: 1200), () {
40
- if (mounted) setState(() => copied = false);
41
- });
42
- }
43
- }
37
38
@override
39
Widget build(BuildContext context) {
@@ -63,19 +56,28 @@ class _ReceiveBottomButtonsState extends State<ReceiveBottomButtons> {
56
mainAxisAlignment: MainAxisAlignment.center,
57
spacing: 16,
58
children: [
66
- AnimatedSwitcher(
67
- duration: Duration(milliseconds: 200),
68
- child: ModernButton.svg(
69
- key: ValueKey(copied),
70
- size: 60,
71
- iconSize: 32,
72
- svgPath: "assets/new-ui/copy.svg",
73
- onPressed: handleCopy,
74
- label: copied ? S.of(context).copied : S.of(context).copy,
75
- iconColor: copied ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.surfaceContainer,
76
- backgroundColor: copied ? Theme.of(context).colorScheme.surfaceContainerHighest : Theme.of(context).colorScheme.primary,
77
- ),
78
- ),
59
+ GestureDetector(
60
+ onTap: widget.copyData == null ? widget.onCopyButtonPressed : null,
61
+ child: IgnorePointer(
62
+ ignoring: widget.copyData == null,
63
+ child: CopyWrapper(
64
+ data: widget.copyData,
65
+ builder: (context, copied) => AnimatedSwitcher(
66
+ duration: Duration(milliseconds: 200),
67
+ child: IgnorePointer(
68
+ child: ModernButton.svg(
69
+ key: ValueKey(copied),
70
+ size: 60,
71
+ iconSize: 32,
72
+ svgPath: "assets/new-ui/copy.svg",
73
+ onPressed: () {},
74
+ label: copied ? S.of(context).copied : S.of(context).copy,
75
+ iconColor: copied ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.surfaceContainer,
76
+ backgroundColor: copied ? Theme.of(context).colorScheme.surfaceContainerHighest : Theme.of(context).colorScheme.primary,
77
+ ),
78
+ ),
79
+ ),
80
+ ),),),
81
ModernButton.svg(
82
size: 60,
83
iconSize: 32,
@@ -103,22 +105,4 @@ class _ReceiveBottomButtonsState extends State<ReceiveBottomButtons> {
105
),
106
);
107
}
106
-
107
-
108
-
109
- // android 13 (sdk 33) added a built-in "text was copied to clipboard" ui element
110
- // older android and iphone still needs an indicator though
111
- Future<bool> shouldShowCopied() async {
112
- if (!Platform.isAndroid) return true;
113
-
114
- try {
115
- final deviceInfo = DeviceInfoPlugin();
116
- final androidInfo = await deviceInfo.androidInfo;
117
- final sdk = androidInfo.version.sdkInt;
118
-
119
- return sdk < 33;
120
- } catch (_) {
121
- return true;
122
- }
123
- }
108
}
\ No newline at end of file