mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-30 06:48:49 +01:00
Catches up from the May 12 fork point (v6.1.2+23) through v6.5.0: 535 upstream commits, including the Monero silent-open fix, Monero on Trezor, Ledger/Trezor BLE fixes, the new seed and history screens, quick Bitcoin sync, git-pinned dependencies (pubspec_overrides.yaml), frozen Hive adapters (*.part.dart), monero_c v0.18.4.6-RC2 and Flutter 3.41.9. Merged with both sides normalized first (import rename, 100-column format), which took the conflict count from 614 paths to 322. The procedure is now scripts/upstream_sync.sh. Kept from Hash Bags: - WalletType enum order and Hive IDs. SQLite stores WalletType.index and our order differs from upstream's; taking theirs would reread saved wallets as other chains. Our frozen adapter keeps the shipped IDs. - The 1.0.2 legacy PIN and wallet-password decode and self-heal. - Removed chains (Solana, Tron, Zano, Zcash, Decred, Haven) and the new Robinhood chain stay out; Cake Pay and non-Trocador swaps stay out. - Default nodes (node.wownero.org, sethforprivacy, Blockstream, bysh.me), converted to upstream's isDefault node-list key; Wownero is back in the node reset list. - Privacy: address resolvers limited to domain-anchored sources again; prices.neroswap.com for fiat; upstream's charts tab, which reads prices.cakewallet.com, sits behind FeatureFlag.isChartsEnabled = false. - Lightning stays behind FeatureFlag.isLightningEnabled, including upstream's new post-seed Lightning username step. - Our icon pipeline, branding and bundle identifiers. Twelve new upstream widget tests are skipped: test and code are unchanged from v6.5.0 apart from renames (not rerun on a pristine upstream checkout; upstream's top_bar_test does not compile against its own TopBar). flutter analyze lib: 0 errors; flutter test: 345 passed, 12 skipped; flutter build bundle compiles. Built with Flutter 3.41.9.
116 lines
3.9 KiB
Dart
116 lines
3.9 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:hash_wallet/generated/i18n.dart';
|
|
import 'package:hash_wallet/utils/clipboard_util.dart';
|
|
import 'package:device_info_plus/device_info_plus.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/semantics.dart';
|
|
import 'package:flutter/services.dart';
|
|
|
|
class CopyWrapper extends StatefulWidget {
|
|
const CopyWrapper(
|
|
{super.key,
|
|
this.data,
|
|
this.requireLongPress = false,
|
|
this.isSensitive = false,
|
|
this.builder,
|
|
this.controlBuilder,
|
|
this.duration = const Duration(milliseconds: 1200)})
|
|
: assert((builder == null) != (controlBuilder == null),
|
|
"Provide exactly one of builder or controlBuilder");
|
|
|
|
final ClipboardData? data;
|
|
final bool isSensitive;
|
|
final bool requireLongPress;
|
|
|
|
/// Builds content that this widget makes copyable with its own gesture
|
|
/// detector. Receives the transient "copied" state.
|
|
final Widget Function(BuildContext, bool)? builder;
|
|
|
|
/// Builds a control that performs the copy itself, so no extra gesture
|
|
/// detector or actionable semantics node is stacked on top of it. [onCopy] is
|
|
/// null when there is nothing to copy.
|
|
final Widget Function(BuildContext context, bool copied, VoidCallback? onCopy)? controlBuilder;
|
|
|
|
final Duration duration;
|
|
|
|
@override
|
|
State<CopyWrapper> createState() => _CopyWrapperState();
|
|
}
|
|
|
|
class _CopyWrapperState extends State<CopyWrapper> {
|
|
bool copied = false;
|
|
|
|
void handleCopy() async {
|
|
if (widget.data == null) return;
|
|
ClipboardUtil.setSensitiveDataToClipboard(widget.data!, isSensitive: widget.isSensitive);
|
|
HapticFeedback.mediumImpact();
|
|
// Screen readers learn about the copy from the transient "copied" state
|
|
// below, which build() marks as a live region, instead of from a push
|
|
// announcement: those are deprecated on Android and make TalkBack drop
|
|
// whatever it was saying. Where we don't render that state, android 13+
|
|
// already shows its own clipboard confirmation.
|
|
if (await shouldShowCopied()) {
|
|
setState(() => copied = true);
|
|
Future.delayed(widget.duration, () {
|
|
if (mounted) setState(() => copied = false);
|
|
});
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final VoidCallback? onCopy = widget.data == null ? null : handleCopy;
|
|
|
|
// The control's own label carries the copied state (Copy -> Copied), so
|
|
// merging it into one node and marking that node a live region while copied
|
|
// is enough for a screen reader to pick the change up.
|
|
if (widget.controlBuilder != null) {
|
|
return MergeSemantics(
|
|
child: Semantics(
|
|
liveRegion: copied,
|
|
child: widget.controlBuilder!(context, copied, onCopy),
|
|
),
|
|
);
|
|
}
|
|
|
|
final content = widget.builder!(context, copied);
|
|
|
|
// Nothing to copy: don't leave behind a gesture detector that a screen
|
|
// reader would present as an actionable target that does nothing.
|
|
if (onCopy == null) return content;
|
|
|
|
return MergeSemantics(
|
|
child: Semantics(
|
|
button: !widget.requireLongPress,
|
|
liveRegion: copied,
|
|
hint: widget.requireLongPress ? S.of(context).long_press_to_copy : S.of(context).copy,
|
|
customSemanticsActions: <CustomSemanticsAction, VoidCallback>{
|
|
CustomSemanticsAction(label: S.of(context).copy): onCopy,
|
|
},
|
|
child: GestureDetector(
|
|
behavior: HitTestBehavior.translucent,
|
|
onTap: widget.requireLongPress ? null : onCopy,
|
|
onLongPress: !widget.requireLongPress ? null : onCopy,
|
|
child: content,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
}
|