mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-26 06:08:40 +01:00
Merge branch 'cake-tech:dev' into dev
This commit is contained in:
@@ -34,7 +34,7 @@ class ZcashTaddressRotation {
|
||||
static bool _isStarted = false;
|
||||
static zkool_coin.Coin get c => ZcashWalletBase.c;
|
||||
static const int _sweepThreshold = 30000;
|
||||
static const int _minSpendableNote = 5000;
|
||||
static const int minSpendableNote = 5000;
|
||||
static const int _lookahead = 5;
|
||||
// Matches transparentLimit in ZcashWalletBase._oneshotSync.
|
||||
static const int _transparentSyncLimit = 100;
|
||||
@@ -415,7 +415,7 @@ class ZcashTaddressRotation {
|
||||
if (note.pool != NotePool.transparent.index || note.locked) {
|
||||
continue;
|
||||
}
|
||||
if (note.value < BigInt.from(_minSpendableNote)) {
|
||||
if (note.value < BigInt.from(minSpendableNote)) {
|
||||
continue;
|
||||
}
|
||||
if (note.height > height) {
|
||||
|
||||
@@ -83,6 +83,12 @@ abstract class ZcashWalletBase
|
||||
|
||||
static const int _autoShieldMinSweep = 30000;
|
||||
|
||||
// zkool's migrate::MIN_SD.
|
||||
static const int _ironwoodMigrateMinNote = 500000;
|
||||
|
||||
static int _minSweepThreshold({required final bool ironwood}) =>
|
||||
ironwood ? _ironwoodMigrateMinNote : _autoShieldMinSweep;
|
||||
|
||||
Money _feeFromTxPlan(
|
||||
final zkool_pay.PcztPackage txPlan,
|
||||
final TransactionPriority priority,
|
||||
@@ -469,8 +475,7 @@ abstract class ZcashWalletBase
|
||||
}
|
||||
|
||||
// pools parameter: bitmask for which pools to use for sending
|
||||
// 1=Transparent, 2=Sapling, 4=Orchard, 7=All pools
|
||||
// Using 7 (all pools) allows spending from any pool type
|
||||
// 1=Transparent, 2=Sapling, 4=Orchard, 8=Ironwood
|
||||
try {
|
||||
return await runWithCoin(
|
||||
accountId: accountId,
|
||||
@@ -479,7 +484,7 @@ abstract class ZcashWalletBase
|
||||
final txPlan = await zkool_pay.prepare(
|
||||
recipients: recipients,
|
||||
options: zkool_pay.PaymentOptions(
|
||||
srcPools: ironwood ? 15 : 7,
|
||||
srcPools: ironwood ? 8 : 4,
|
||||
recipientPaysFee: receipientPaysFee,
|
||||
smartTransparent: false,
|
||||
mode: 0,
|
||||
@@ -1175,6 +1180,40 @@ abstract class ZcashWalletBase
|
||||
}
|
||||
}
|
||||
|
||||
/// Total of transparent + sapling notes at or above the per-note spendable floor.
|
||||
static Future<BigInt> _sweepableTotal(final zkool_coin.Coin coin) async {
|
||||
final notes = await zkool_account.listNotes(c: coin);
|
||||
BigInt sweepable = BigInt.zero;
|
||||
for (int i = 0; i < notes.length; i++) {
|
||||
final note = notes[i];
|
||||
if (note.pool < 0 || note.pool >= NotePool.values.length) {
|
||||
continue;
|
||||
}
|
||||
final noteType = NotePool.values[note.pool];
|
||||
if ((noteType == NotePool.transparent || noteType == NotePool.sapling) &&
|
||||
note.value >= BigInt.from(ZcashTaddressRotation.minSpendableNote)) {
|
||||
sweepable += note.value;
|
||||
}
|
||||
}
|
||||
return sweepable;
|
||||
}
|
||||
|
||||
/// Orchard notes that migration will actually split or move to Ironwood.
|
||||
static Future<BigInt> _migratableOrchardTotal(final zkool_coin.Coin coin) async {
|
||||
final notes = await zkool_account.listNotes(c: coin);
|
||||
BigInt migratable = BigInt.zero;
|
||||
for (int i = 0; i < notes.length; i++) {
|
||||
final note = notes[i];
|
||||
if (note.pool != NotePool.orchard.index || note.locked) {
|
||||
continue;
|
||||
}
|
||||
if (note.value >= BigInt.from(_ironwoodMigrateMinNote)) {
|
||||
migratable += note.value;
|
||||
}
|
||||
}
|
||||
return migratable;
|
||||
}
|
||||
|
||||
Future<void> _$autoShield() async {
|
||||
if (syncStatus is! SyncedSyncStatus) {
|
||||
return;
|
||||
@@ -1182,24 +1221,12 @@ abstract class ZcashWalletBase
|
||||
final txId = await runWithCoin(
|
||||
accountId: accountId,
|
||||
func: (coin) async {
|
||||
final _notes = await zkool_account.listNotes(c: coin);
|
||||
BigInt sweepable = BigInt.zero;
|
||||
for (int i = 0; i < _notes.length; i++) {
|
||||
final note = _notes[i];
|
||||
if (note.pool < 0 || note.pool >= NotePool.values.length) {
|
||||
continue;
|
||||
}
|
||||
final noteType = NotePool.values[note.pool];
|
||||
if (noteType == NotePool.transparent || noteType == NotePool.sapling) {
|
||||
sweepable += note.value;
|
||||
}
|
||||
}
|
||||
final sweepable = await _sweepableTotal(coin);
|
||||
final ironwood = await zkool_network.isIronwoodActive(c: coin);
|
||||
|
||||
if (sweepable <= BigInt.from(_autoShieldMinSweep)) {
|
||||
if (sweepable <= BigInt.from(_minSweepThreshold(ironwood: ironwood))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final ironwood = await zkool_network.isIronwoodActive(c: coin);
|
||||
final txPlan = await zkool_pay.prepare(
|
||||
recipients: [
|
||||
zkool_paydart.Recipient(
|
||||
@@ -1332,17 +1359,6 @@ abstract class ZcashWalletBase
|
||||
}) async {
|
||||
try {
|
||||
await _updateIronwoodActive();
|
||||
final bal = await runWithCoin(
|
||||
accountId: accountId,
|
||||
func: (final coin) async => zkool_sync.balance(c: coin),
|
||||
);
|
||||
|
||||
// 0 - transparent, 1 - sapling, 2 - orchard, 3 - ironwood
|
||||
final transparent = bal.field0[0];
|
||||
final sapling = bal.field0.length > 1 ? bal.field0[1] : BigInt.zero;
|
||||
final orchard = bal.field0.length > 2 ? bal.field0[2] : BigInt.zero;
|
||||
final ironwood = bal.field0.length > 3 ? bal.field0[3] : BigInt.zero;
|
||||
|
||||
if (runAutoShield) {
|
||||
await _autoShield();
|
||||
}
|
||||
@@ -1350,18 +1366,36 @@ abstract class ZcashWalletBase
|
||||
await _ironwoodMigrate();
|
||||
}
|
||||
|
||||
final (bal, sweepable, migratableOrchard) = await runWithCoin(
|
||||
accountId: accountId,
|
||||
func: (final coin) async => (
|
||||
await zkool_sync.balance(c: coin),
|
||||
await _sweepableTotal(coin),
|
||||
await _migratableOrchardTotal(coin),
|
||||
),
|
||||
);
|
||||
|
||||
// 0 - transparent, 1 - sapling, 2 - orchard, 3 - ironwood
|
||||
final orchard = bal.field0.length > 2 ? bal.field0[2] : BigInt.zero;
|
||||
final ironwood = bal.field0.length > 3 ? bal.field0[3] : BigInt.zero;
|
||||
|
||||
// After NU6.3, Orchard notes are migrated to Ironwood - show them as unconfirmed.
|
||||
// Unavailable uses the same per-note totals and thresholds as auto-shield/migration guards.
|
||||
final BigInt availableAmount;
|
||||
final BigInt unavailableAmount;
|
||||
if (ironwoodActive == true && orchard > BigInt.zero) {
|
||||
availableAmount = sapling + ironwood;
|
||||
unavailableAmount = transparent + orchard;
|
||||
final sweepableUnavailable = sweepable <= BigInt.from(_ironwoodMigrateMinNote)
|
||||
? BigInt.zero
|
||||
: sweepable;
|
||||
availableAmount = ironwood;
|
||||
unavailableAmount = migratableOrchard + sweepableUnavailable;
|
||||
} else {
|
||||
availableAmount = sapling + orchard + ironwood;
|
||||
unavailableAmount = transparent;
|
||||
final minSweep = _minSweepThreshold(ironwood: ironwoodActive == true);
|
||||
availableAmount = orchard + ironwood;
|
||||
unavailableAmount = sweepable <= BigInt.from(minSweep) ? BigInt.zero : sweepable;
|
||||
}
|
||||
|
||||
balance[CryptoCurrency.zec] = ZcashBalance(
|
||||
balance[currency] = ZcashBalance(
|
||||
Money(availableAmount, currency),
|
||||
Money(unavailableAmount, currency),
|
||||
frozen: Money.zero(currency),
|
||||
|
||||
+12
-1
@@ -30,13 +30,24 @@ security channel, so reports will not be missed.
|
||||
### What to include
|
||||
|
||||
- A clear description of the issue and its security impact.
|
||||
- Step-by-step reproduction, ideally with a proof of concept.
|
||||
- A working proof of concept is required. It must run directly against Cake
|
||||
Wallet itself, using a release build or locally built version, and demonstrate
|
||||
the reported behavior and security impact. Standalone Python code,
|
||||
mathematical examples, or simulations that only reproduce the theory without
|
||||
exercising Cake Wallet do not satisfy this requirement.
|
||||
- Step-by-step reproduction instructions.
|
||||
- Affected platforms (iOS, Android, macOS, Linux, Windows) and app version.
|
||||
- Affected wallet types / chains, if applicable.
|
||||
- Any relevant logs, addresses, or transaction IDs (for on-chain issues).
|
||||
|
||||
If you used AI tooling to find or write up the report, please say so.
|
||||
|
||||
### Communication expectations
|
||||
|
||||
AI should not be used to generate comments when communicating with maintainers
|
||||
and other contributors. Comments are expected to be written by humans. Comments
|
||||
that are believed to be written by AI may be moderated.
|
||||
|
||||
## Our commitment (safe harbor)
|
||||
|
||||
We consider security research conducted in good faith under this policy to be
|
||||
|
||||
@@ -90,14 +90,18 @@ class _NewDashboardState extends State<NewDashboard> {
|
||||
),
|
||||
),
|
||||
),
|
||||
SafeArea(
|
||||
bottom: !(Platform.isIOS),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
height: NewMainNavBar.barHeight + NewMainNavBar.barBottomPadding,
|
||||
child: AbsorbPointer(
|
||||
absorbing: true,
|
||||
child: Container(color: Colors.transparent),
|
||||
// Invisible pointer-absorbing strip behind the nav bar: it must not
|
||||
// be reachable by screen-reader traversal either.
|
||||
ExcludeSemantics(
|
||||
child: SafeArea(
|
||||
bottom: !(Platform.isIOS),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
height: NewMainNavBar.barHeight + NewMainNavBar.barBottomPadding,
|
||||
child: AbsorbPointer(
|
||||
absorbing: true,
|
||||
child: Container(color: Colors.transparent),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -174,17 +174,29 @@ class _AccountCustomizerState extends State<AccountCustomizer> {
|
||||
itemCount: _items.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final card = _items[index].card;
|
||||
// The stack is ordered bottom to top, so the last item
|
||||
// is the account currently in front — the selected one.
|
||||
final selectedItemIndex = _items.length - 1;
|
||||
|
||||
return Container(
|
||||
key: ValueKey(index),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
reorder(index, _items.length);
|
||||
},
|
||||
child: Align(
|
||||
alignment: Alignment.topCenter,
|
||||
heightFactor: _kStackVisibleFactor,
|
||||
child: card,
|
||||
// One labeled, selectable node per account; the card's own
|
||||
// texts stay reachable underneath it.
|
||||
child: Semantics(
|
||||
button: true,
|
||||
selected: selectedItemIndex == index,
|
||||
label: _items[index].accountListItem.label,
|
||||
onTap: () => reorder(index, _items.length),
|
||||
child: GestureDetector(
|
||||
excludeFromSemantics: true,
|
||||
onTap: () {
|
||||
reorder(index, _items.length);
|
||||
},
|
||||
child: Align(
|
||||
alignment: Alignment.topCenter,
|
||||
heightFactor: _kStackVisibleFactor,
|
||||
child: card,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -199,31 +211,36 @@ class _AccountCustomizerState extends State<AccountCustomizer> {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(999999),
|
||||
onTap: _showAddAccountModal,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(999999)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 18.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 8,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.add,
|
||||
size: 28,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
Text(
|
||||
S.of(context).add_account,
|
||||
style: TextStyle(
|
||||
child: MergeSemantics(
|
||||
child: Semantics(
|
||||
button: true,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(999999),
|
||||
onTap: _showAddAccountModal,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(999999)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 18.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 8,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.add,
|
||||
size: 28,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
fontWeight: FontWeight.w500),
|
||||
)
|
||||
],
|
||||
),
|
||||
Text(
|
||||
S.of(context).add_account,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
fontWeight: FontWeight.w500),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -406,6 +423,8 @@ class _AccountCreationModalState extends State<AccountCreationModal> {
|
||||
final TextEditingController _controller = TextEditingController();
|
||||
bool _loading = false;
|
||||
|
||||
Future<void> _generateAccountName() async => _controller.text = await generateName();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
@@ -441,18 +460,23 @@ class _AccountCreationModalState extends State<AccountCreationModal> {
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
_controller.text = await generateName();
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(5)),
|
||||
child: CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/randomize.svg",
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context).colorScheme.primary, BlendMode.srcIn),
|
||||
child: Semantics(
|
||||
button: true,
|
||||
label: S.of(context).generate_name,
|
||||
onTap: _generateAccountName,
|
||||
child: ExcludeSemantics(
|
||||
child: GestureDetector(
|
||||
onTap: _generateAccountName,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(5)),
|
||||
child: CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/randomize.svg",
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context).colorScheme.primary, BlendMode.srcIn),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
|
||||
import 'package:cake_wallet/themes/core/theme_extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -60,71 +61,86 @@ class AppsWidget extends StatelessWidget {
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
),
|
||||
child: TextButton(
|
||||
onPressed: onTap,
|
||||
style: TextButton.styleFrom(
|
||||
shape: RoundedSuperellipseBorder(
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
side: BorderSide(
|
||||
width: 1.25, color: Theme.of(context).colorScheme.surfaceContainerHigh),
|
||||
),
|
||||
padding: EdgeInsets.all(24),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
// The card is one node; the arrow glyph's "leaves the app" meaning
|
||||
// becomes a hint instead of an unlabeled icon.
|
||||
child: MergeSemantics(
|
||||
child: Semantics(
|
||||
hint: isLink == true ? S.of(context).opens_externally : null,
|
||||
child: TextButton(
|
||||
onPressed: onTap,
|
||||
style: TextButton.styleFrom(
|
||||
shape: RoundedSuperellipseBorder(
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
side: BorderSide(
|
||||
width: 1.25, color: Theme.of(context).colorScheme.surfaceContainerHigh),
|
||||
),
|
||||
padding: EdgeInsets.all(24),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 20),
|
||||
child: CakeImageWidget(imageUrl: image, height: 54, width: 54),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
spacing: 6.0,
|
||||
Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 20),
|
||||
child: ExcludeSemantics(
|
||||
child: CakeImageWidget(imageUrl: image, height: 54, width: 54),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
spacing: 6.0,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 20,
|
||||
),
|
||||
softWrap: true,
|
||||
),
|
||||
isCake == true
|
||||
? ExcludeSemantics(
|
||||
child: CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/cakelabs-icon.svg",
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurfaceVariant),
|
||||
)
|
||||
: SizedBox(),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 20,
|
||||
subTitle,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
softWrap: true,
|
||||
),
|
||||
isCake == true
|
||||
? CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/cakelabs-icon.svg",
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant)
|
||||
: SizedBox(),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
Text(
|
||||
subTitle,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
softWrap: true,
|
||||
),
|
||||
ExcludeSemantics(
|
||||
child: Icon(
|
||||
isLink == true ? Icons.arrow_outward : Icons.arrow_forward_ios,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
size: 20,
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
Icon(
|
||||
isLink == true ? Icons.arrow_outward : Icons.arrow_forward_ios,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
size: 20,
|
||||
)
|
||||
if (hint != null) ...[
|
||||
SizedBox(height: 10),
|
||||
hint!,
|
||||
]
|
||||
],
|
||||
),
|
||||
if (hint != null) ...[
|
||||
SizedBox(height: 10),
|
||||
hint!,
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -132,10 +148,16 @@ class AppsWidget extends StatelessWidget {
|
||||
Positioned(
|
||||
top: 10,
|
||||
right: 10,
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.close),
|
||||
onPressed: onClose,
|
||||
//color: Theme.of(context).colorScheme.onSurface,
|
||||
// Label the icon-only button without adding a visible tooltip.
|
||||
child: MergeSemantics(
|
||||
child: Semantics(
|
||||
label: S.of(context).close,
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.close),
|
||||
onPressed: onClose,
|
||||
//color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -39,125 +39,138 @@ class AssetTile extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final iconPath = balance.asset.iconPath ?? "";
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (context) {
|
||||
return AssetDetailsModal(
|
||||
showSwap: showSwap,
|
||||
showBridgeButton: showBridgeButton,
|
||||
asset: balance.asset,
|
||||
title: title ?? balance.asset.fullName ?? balance.asset.name,
|
||||
chainTitle: "",
|
||||
subtitle: trailingText ?? _getChainTitle(),
|
||||
amount: showSecondary ? balance.secondAvailableBalance : balance.availableBalance,
|
||||
currencyTitle: balance.asset.title,
|
||||
fiatAmount: showSecondary
|
||||
? balance.fiatSecondAvailableBalance
|
||||
: balance.fiatAvailableBalance,
|
||||
iconPath: balance.asset.iconPath ?? "",
|
||||
chainIconPath: chainIconPath,
|
||||
mode: modalMode,
|
||||
wallet: wallet,
|
||||
);
|
||||
});
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18.0),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
height: 72,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: isFirst ? Radius.circular(18) : Radius.zero,
|
||||
bottom: isLast ? Radius.circular(18) : Radius.zero,
|
||||
),
|
||||
),
|
||||
// The row is one control: name, amount and fiat value merge into a single
|
||||
// button node that opens the asset details sheet.
|
||||
return MergeSemantics(
|
||||
child: Semantics(
|
||||
button: true,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (context) {
|
||||
return AssetDetailsModal(
|
||||
showSwap: showSwap,
|
||||
showBridgeButton: showBridgeButton,
|
||||
asset: balance.asset,
|
||||
title: title ?? balance.asset.fullName ?? balance.asset.name,
|
||||
chainTitle: "",
|
||||
subtitle: trailingText ?? _getChainTitle(),
|
||||
amount:
|
||||
showSecondary ? balance.secondAvailableBalance : balance.availableBalance,
|
||||
currencyTitle: balance.asset.title,
|
||||
fiatAmount: showSecondary
|
||||
? balance.fiatSecondAvailableBalance
|
||||
: balance.fiatAvailableBalance,
|
||||
iconPath: balance.asset.iconPath ?? "",
|
||||
chainIconPath: chainIconPath,
|
||||
mode: modalMode,
|
||||
wallet: wallet,
|
||||
);
|
||||
});
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
iconPath.isNotEmpty
|
||||
? TokenImageWidget(
|
||||
imageUrl: iconPath,
|
||||
size: 36,
|
||||
)
|
||||
: Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
balance.asset.name
|
||||
.substring(0, min(2, balance.asset.name.length)),
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18.0),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
height: 72,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: isFirst ? Radius.circular(18) : Radius.zero,
|
||||
bottom: isLast ? Radius.circular(18) : Radius.zero,
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Decorative: the asset name is already in the row text.
|
||||
ExcludeSemantics(
|
||||
child: iconPath.isNotEmpty
|
||||
? TokenImageWidget(
|
||||
imageUrl: iconPath,
|
||||
size: 36,
|
||||
)
|
||||
: Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
balance.asset.name
|
||||
.substring(0, min(2, balance.asset.name.length)),
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 12.0),
|
||||
Expanded(
|
||||
child: Column(
|
||||
spacing: 4.0,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
spacing: 4,
|
||||
),
|
||||
SizedBox(width: 12.0),
|
||||
Expanded(
|
||||
child: Column(
|
||||
spacing: 4.0,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title ?? balance.asset.fullName ?? balance.asset.name,
|
||||
style: TextStyle(fontWeight: FontWeight.w500),
|
||||
Row(
|
||||
spacing: 4,
|
||||
children: [
|
||||
Text(
|
||||
title ?? balance.asset.fullName ?? balance.asset.name,
|
||||
style: TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
if (trailingText != null)
|
||||
Text(
|
||||
trailingText!,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (trailingText != null)
|
||||
Text(
|
||||
trailingText!,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 4.0),
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
"${showSecondary ? balance.secondAvailableBalance : balance.availableBalance} ${balance.formattedAssetTitle.safeSubString(0, 6)}",
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 4.0),
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
"${showSecondary ? balance.secondAvailableBalance : balance.availableBalance} ${balance.formattedAssetTitle.safeSubString(0, 6)}",
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Text(
|
||||
showSecondary
|
||||
? balance.fiatSecondAvailableBalance
|
||||
: balance.fiatAvailableBalance,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
showSecondary ? balance.fiatSecondAvailableBalance : balance.fiatAvailableBalance,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -22,6 +22,8 @@ class AssetsTopBar extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final actionButton = tabs[selectedTab].actionButton;
|
||||
|
||||
return SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 32.0, bottom: 0.0, left: 12.0, right: 18.0),
|
||||
@@ -49,40 +51,49 @@ class AssetsTopBar extends StatelessWidget {
|
||||
key: ValueKey(selectedTab),
|
||||
spacing: 8,
|
||||
children: [
|
||||
Opacity(
|
||||
opacity: tabs[selectedTab].actionButton != null ? 1 : 0,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (tabs[selectedTab].actionButton != null) {
|
||||
tabs[selectedTab].actionButton?.onPressed();
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(999999),
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: Row(
|
||||
spacing: 6,
|
||||
children: [
|
||||
if ((tabs[selectedTab].actionButton?.title ?? "").isNotEmpty)
|
||||
Text(
|
||||
tabs[selectedTab].actionButton?.title ?? "",
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
CakeImageWidget(
|
||||
imageUrl: tabs[selectedTab].actionButton?.iconPath,
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context).colorScheme.primary, BlendMode.srcIn)),
|
||||
],
|
||||
// Built conditionally rather than rendered at opacity 0: an
|
||||
// invisible chip stayed focusable for screen readers. The
|
||||
// SizedBox keeps the 40px header height the invisible chip
|
||||
// used to occupy.
|
||||
if (actionButton == null)
|
||||
const SizedBox(height: 40)
|
||||
else
|
||||
MergeSemantics(
|
||||
child: Semantics(
|
||||
button: true,
|
||||
child: GestureDetector(
|
||||
onTap: actionButton.onPressed,
|
||||
child: Container(
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(999999),
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: Row(
|
||||
spacing: 6,
|
||||
children: [
|
||||
if (actionButton.title.isNotEmpty)
|
||||
Text(
|
||||
actionButton.title,
|
||||
style:
|
||||
TextStyle(color: Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
ExcludeSemantics(
|
||||
child: CakeImageWidget(
|
||||
imageUrl: actionButton.iconPath,
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context).colorScheme.primary,
|
||||
BlendMode.srcIn)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -36,6 +36,15 @@ class HistorySection extends StatelessWidget {
|
||||
final bool roundedTopSection;
|
||||
final bool detailsAsPage;
|
||||
|
||||
/// A history row is a single button node: every text inside it (direction,
|
||||
/// date, amounts) merges into one label.
|
||||
Widget _historyRow({required VoidCallback onTap, required Widget child}) => MergeSemantics(
|
||||
child: Semantics(
|
||||
button: true,
|
||||
child: GestureDetector(onTap: onTap, child: child),
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SliverPadding(
|
||||
@@ -90,7 +99,7 @@ class HistorySection extends StatelessWidget {
|
||||
else
|
||||
asset = item.assetOfTransaction;
|
||||
|
||||
return GestureDetector(
|
||||
return _historyRow(
|
||||
onTap: () {
|
||||
final page =
|
||||
getIt.get<TransactionDetailsModal>(param1: transaction);
|
||||
@@ -125,7 +134,7 @@ class HistorySection extends StatelessWidget {
|
||||
final tradeFrom = trade.from;
|
||||
final tradeTo = trade.to;
|
||||
|
||||
return GestureDetector(
|
||||
return _historyRow(
|
||||
onTap: () => Navigator.of(context)
|
||||
.pushNamed(Routes.tradeDetails, arguments: trade),
|
||||
child: HistoryTradeTile(
|
||||
@@ -161,7 +170,7 @@ class HistorySection extends StatelessWidget {
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant)));
|
||||
} else if (item is OrderListItem) {
|
||||
return GestureDetector(
|
||||
return _historyRow(
|
||||
onTap: () => Navigator.of(context)
|
||||
.pushNamed(Routes.orderDetails, arguments: item.order),
|
||||
child: HistoryOrderTile(
|
||||
@@ -176,7 +185,7 @@ class HistorySection extends StatelessWidget {
|
||||
} else if (item is PayjoinTransactionListItem) {
|
||||
final session = item.session;
|
||||
|
||||
return GestureDetector(
|
||||
return _historyRow(
|
||||
onTap: () => Navigator.of(context).pushNamed(
|
||||
Routes.payjoinDetails,
|
||||
arguments: [item.sessionId, item.transaction],
|
||||
@@ -196,7 +205,7 @@ class HistorySection extends StatelessWidget {
|
||||
} else if (item is AnonpayTransactionListItem) {
|
||||
final transactionInfo = item.transaction;
|
||||
|
||||
return GestureDetector(
|
||||
return _historyRow(
|
||||
onTap: () => Navigator.of(context).pushNamed(
|
||||
Routes.anonPayDetailsPage,
|
||||
arguments: transactionInfo),
|
||||
|
||||
@@ -142,7 +142,8 @@ class HistoryTile extends StatelessWidget {
|
||||
date: date,
|
||||
amount: amount,
|
||||
amountFiat: amountFiat,
|
||||
leadingIcon: _getLeadingIcon(context),
|
||||
// Decorative: `title` already reads out sent/received/pending.
|
||||
leadingIcon: ExcludeSemantics(child: _getLeadingIcon(context)),
|
||||
primaryTextColor: _getPrimaryTextColor(),
|
||||
roundedTop: roundedTop,
|
||||
roundedBottom: roundedBottom,
|
||||
|
||||
@@ -11,46 +11,53 @@ class HistoryTopBar extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SliverToBoxAdapter(
|
||||
child: GestureDetector(
|
||||
child: Semantics(
|
||||
button: true,
|
||||
label: S.of(context).history,
|
||||
onTap: onTap,
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 16, right: 16, top: 24),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(18),
|
||||
bottom: roundedBottom ? Radius.circular(18) : Radius.zero),
|
||||
color: Theme.of(context).colorScheme.surfaceContainer),
|
||||
child: ExcludeSemantics(
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: 4,
|
||||
horizontal: 12,
|
||||
),
|
||||
child: Column(
|
||||
spacing: 12,
|
||||
children: [
|
||||
SizedBox.shrink(),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
padding: const EdgeInsets.only(left: 16, right: 16, top: 24),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(18),
|
||||
bottom: roundedBottom ? Radius.circular(18) : Radius.zero),
|
||||
color: Theme.of(context).colorScheme.surfaceContainer),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: 4,
|
||||
horizontal: 12,
|
||||
),
|
||||
child: Column(
|
||||
spacing: 12,
|
||||
children: [
|
||||
Text(S.of(context).history),
|
||||
CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/arrow_right.svg",
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context).colorScheme.onSurfaceVariant, BlendMode.srcIn),
|
||||
)
|
||||
SizedBox.shrink(),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(S.of(context).history),
|
||||
CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/arrow_right.svg",
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context).colorScheme.onSurfaceVariant, BlendMode.srcIn),
|
||||
)
|
||||
],
|
||||
),
|
||||
if (!roundedBottom)
|
||||
Container(
|
||||
height: 1,
|
||||
width: double.infinity,
|
||||
color: Theme.of(context).colorScheme.outlineVariant.withAlpha(175),
|
||||
)
|
||||
else
|
||||
Container(height: 2)
|
||||
],
|
||||
),
|
||||
if (!roundedBottom)
|
||||
Container(
|
||||
height: 1,
|
||||
width: double.infinity,
|
||||
color: Theme.of(context).colorScheme.outlineVariant.withAlpha(175),
|
||||
)
|
||||
else
|
||||
Container(height: 2)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -91,11 +91,14 @@ class BalanceCard extends StatelessWidget {
|
||||
? ClipRSuperellipse(
|
||||
borderRadius: BorderRadius.circular(borderRadius),
|
||||
key: ValueKey(design.imagePath),
|
||||
child: CakeImageWidget(
|
||||
imageUrl: design.imagePath,
|
||||
width: width,
|
||||
height: height,
|
||||
fit: BoxFit.fill,
|
||||
// Purely decorative card artwork.
|
||||
child: ExcludeSemantics(
|
||||
child: CakeImageWidget(
|
||||
imageUrl: design.imagePath,
|
||||
width: width,
|
||||
height: height,
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(
|
||||
@@ -146,9 +149,14 @@ class BalanceCard extends StatelessWidget {
|
||||
AnimatedOpacity(
|
||||
opacity: selected ? 0 : 1,
|
||||
duration: textFadeDuration,
|
||||
child: Text(
|
||||
accountBalance,
|
||||
style: TextStyle(color: design.colors.textColor, fontSize: 14),
|
||||
// Opacity alone keeps the text readable by screen
|
||||
// readers, so drop it while it is invisible.
|
||||
child: ExcludeSemantics(
|
||||
excluding: selected,
|
||||
child: Text(
|
||||
accountBalance,
|
||||
style: TextStyle(color: design.colors.textColor, fontSize: 14),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -156,40 +164,45 @@ class BalanceCard extends StatelessWidget {
|
||||
AnimatedOpacity(
|
||||
opacity: selected ? 1 : 0,
|
||||
duration: textFadeDuration,
|
||||
child: AnimatedSwitcher(
|
||||
duration: designSwitchDuration,
|
||||
layoutBuilder: (currentChild, previousChildren) {
|
||||
return Stack(
|
||||
alignment: Alignment.centerLeft,
|
||||
children: <Widget>[
|
||||
...previousChildren,
|
||||
if (currentChild != null) currentChild,
|
||||
// Only the selected card's balance is visible, so only it
|
||||
// may be announced.
|
||||
child: ExcludeSemantics(
|
||||
excluding: !selected,
|
||||
child: AnimatedSwitcher(
|
||||
duration: designSwitchDuration,
|
||||
layoutBuilder: (currentChild, previousChildren) {
|
||||
return Stack(
|
||||
alignment: Alignment.centerLeft,
|
||||
children: <Widget>[
|
||||
...previousChildren,
|
||||
if (currentChild != null) currentChild,
|
||||
],
|
||||
);
|
||||
},
|
||||
child: Row(
|
||||
key: ValueKey("$balance ${resolvedAssetName.toUpperCase()}"),
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
AnimatedDefaultTextStyle(
|
||||
duration: designSwitchDuration,
|
||||
style: DefaultTextStyle.of(context).style.copyWith(
|
||||
color: design.colors.textColor,
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.w500,
|
||||
letterSpacing: -0.4),
|
||||
child: Text(fiatFirst ? fiatBalance : balance),
|
||||
),
|
||||
AnimatedDefaultTextStyle(
|
||||
duration: designSwitchDuration,
|
||||
style: DefaultTextStyle.of(context).style.copyWith(
|
||||
color: design.colors.textColorSecondary,
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.w400,
|
||||
letterSpacing: -0.4),
|
||||
child: Text(resolvedAssetName),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
child: Row(
|
||||
key: ValueKey("$balance ${resolvedAssetName.toUpperCase()}"),
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
AnimatedDefaultTextStyle(
|
||||
duration: designSwitchDuration,
|
||||
style: DefaultTextStyle.of(context).style.copyWith(
|
||||
color: design.colors.textColor,
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.w500,
|
||||
letterSpacing: -0.4),
|
||||
child: Text(fiatFirst ? fiatBalance : balance),
|
||||
),
|
||||
AnimatedDefaultTextStyle(
|
||||
duration: designSwitchDuration,
|
||||
style: DefaultTextStyle.of(context).style.copyWith(
|
||||
color: design.colors.textColorSecondary,
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.w400,
|
||||
letterSpacing: -0.4),
|
||||
child: Text(resolvedAssetName),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -250,7 +263,10 @@ class BalanceCard extends StatelessWidget {
|
||||
switchInCurve: Curves.easeInOut,
|
||||
switchOutCurve: Curves.easeInOut,
|
||||
child: design.backgroundType == CardDesignBackgroundTypes.svgIcon
|
||||
? _CornerSvgIcon(design: design, iconWidth: iconWidth)
|
||||
// Purely decorative card artwork.
|
||||
? ExcludeSemantics(
|
||||
child: _CornerSvgIcon(design: design, iconWidth: iconWidth),
|
||||
)
|
||||
: const SizedBox.shrink(
|
||||
key: ValueKey('svgIconOff'),
|
||||
),
|
||||
@@ -266,18 +282,28 @@ class BalanceCard extends StatelessWidget {
|
||||
child: AnimatedOpacity(
|
||||
duration: designSwitchDuration,
|
||||
opacity: onCustomizeTapped == null ? 0 : 1,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: onCustomizeTapped,
|
||||
child: Container(
|
||||
height: 40,
|
||||
width: 40,
|
||||
child: Center(
|
||||
child: CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/3dots_vertical.svg",
|
||||
alignment: Alignment.topRight,
|
||||
colorFilter:
|
||||
ColorFilter.mode(design.colors.textColorSecondary, BlendMode.srcIn),
|
||||
// Faded out and inert: must not be a focusable phantom control.
|
||||
child: ExcludeSemantics(
|
||||
excluding: onCustomizeTapped == null,
|
||||
child: Semantics(
|
||||
button: true,
|
||||
label: S.of(context).wallet_menu,
|
||||
onTap: onCustomizeTapped,
|
||||
child: GestureDetector(
|
||||
excludeFromSemantics: true,
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: onCustomizeTapped,
|
||||
child: Container(
|
||||
height: 40,
|
||||
width: 40,
|
||||
child: Center(
|
||||
child: CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/3dots_vertical.svg",
|
||||
alignment: Alignment.topRight,
|
||||
colorFilter:
|
||||
ColorFilter.mode(design.colors.textColorSecondary, BlendMode.srcIn),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -289,27 +315,34 @@ class BalanceCard extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget getBalanceCardActionButton(BalanceCardAction action) => GestureDetector(
|
||||
Widget getBalanceCardActionButton(BalanceCardAction action) => Semantics(
|
||||
button: true,
|
||||
label: action.label,
|
||||
onTap: action.onTap,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: design.colors.backgroundImageColor.withAlpha(75),
|
||||
borderRadius: BorderRadius.circular(10000000),
|
||||
),
|
||||
margin: const EdgeInsets.only(right: 10),
|
||||
padding: const EdgeInsets.only(left: 10, right: 5, top: 5, bottom: 5),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 6),
|
||||
child: Text(
|
||||
action.label,
|
||||
style: TextStyle(color: design.colors.textColor, fontSize: 16),
|
||||
),
|
||||
child: ExcludeSemantics(
|
||||
child: GestureDetector(
|
||||
onTap: action.onTap,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: design.colors.backgroundImageColor.withAlpha(75),
|
||||
borderRadius: BorderRadius.circular(10000000),
|
||||
),
|
||||
Icon(action.icon, color: design.colors.textColorSecondary, size: action.iconSize),
|
||||
],
|
||||
margin: const EdgeInsets.only(right: 10),
|
||||
padding: const EdgeInsets.only(left: 10, right: 5, top: 5, bottom: 5),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 6),
|
||||
child: Text(
|
||||
action.label,
|
||||
style: TextStyle(color: design.colors.textColor, fontSize: 16),
|
||||
),
|
||||
),
|
||||
Icon(action.icon, color: design.colors.textColorSecondary, size: action.iconSize),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -76,6 +76,32 @@ class _CardsViewState extends State<CardsView> {
|
||||
|
||||
final left = (parentWidth - effectiveCardWidth) / 2.0;
|
||||
|
||||
final isSelected = _selectedIndex == visualIndex;
|
||||
final accounts = widget.accountListViewModel?.accounts;
|
||||
final cardLabel = (accounts != null && realIndex < accounts.length)
|
||||
? accounts[realIndex].label
|
||||
: S.of(context).balance;
|
||||
|
||||
void onCardTap() {
|
||||
// printV(visualIndex);
|
||||
if (compactMode && visualIndex != 0) {
|
||||
widget.onCompactModeBackgroundCardsTapped();
|
||||
} else if (!compactMode) {
|
||||
setState(() {
|
||||
if (widget.accountListViewModel != null)
|
||||
widget.accountListViewModel!.select(widget.accountListViewModel!.accounts[realIndex]);
|
||||
_selectedIndex = visualIndex;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void onCardLongPress() {
|
||||
if (_selectedIndex == visualIndex) {
|
||||
widget.dashboardViewModel.balanceViewModel.switchBalanceValue();
|
||||
}
|
||||
HapticFeedback.heavyImpact();
|
||||
}
|
||||
|
||||
return AnimatedPositioned(
|
||||
key: ValueKey("$visualIndex $realIndex"),
|
||||
duration: animDuration,
|
||||
@@ -86,124 +112,122 @@ class _CardsViewState extends State<CardsView> {
|
||||
duration: animDuration,
|
||||
curve: Curves.easeOut,
|
||||
scale: scale,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
// printV(visualIndex);
|
||||
if (compactMode && visualIndex != 0) {
|
||||
widget.onCompactModeBackgroundCardsTapped();
|
||||
} else if (!compactMode) {
|
||||
setState(() {
|
||||
if (widget.accountListViewModel != null)
|
||||
widget.accountListViewModel!
|
||||
.select(widget.accountListViewModel!.accounts[realIndex]);
|
||||
_selectedIndex = visualIndex;
|
||||
});
|
||||
}
|
||||
},
|
||||
onLongPress: () {
|
||||
if (_selectedIndex == visualIndex) {
|
||||
widget.dashboardViewModel.balanceViewModel.switchBalanceValue();
|
||||
}
|
||||
;
|
||||
HapticFeedback.heavyImpact();
|
||||
},
|
||||
child: Observer(builder: (_) {
|
||||
if (realIndex >= (widget.accountListViewModel?.accounts.length ?? 1)) {
|
||||
return Container();
|
||||
}
|
||||
final account = widget.accountListViewModel?.accounts[realIndex];
|
||||
|
||||
// The second balance should always be the lightning balance
|
||||
// printV(widget.dashboardViewModel.balanceViewModel.formattedBalances.first.availableBalance);
|
||||
final walletBalanceRecord = widget.dashboardViewModel.balanceViewModel
|
||||
.getMainBalanceRecord(widget.lightningMode);
|
||||
|
||||
late final String walletBalance;
|
||||
late final String walletFiatBalance;
|
||||
if (widget.dashboardViewModel.mwebEnabled && widget.dashboardViewModel.hasMweb) {
|
||||
if (widget.dashboardViewModel.balanceViewModel.displayMode ==
|
||||
BalanceDisplayMode.hiddenBalance) {
|
||||
walletBalance = '●●●●●●';
|
||||
walletFiatBalance = '●●●●●●';
|
||||
} else {
|
||||
walletBalance = walletBalanceRecord?.combinedAvailableBalance ?? "0";
|
||||
walletFiatBalance = walletBalanceRecord?.combinedFiatAvailableBalance ?? "0.00";
|
||||
// The card is the tap target; the balances and the card's own buttons stay
|
||||
// reachable as children of this node.
|
||||
child: Semantics(
|
||||
button: true,
|
||||
selected: isSelected,
|
||||
label: cardLabel,
|
||||
hint: isSelected
|
||||
? (widget.dashboardViewModel.balanceViewModel.displayMode ==
|
||||
BalanceDisplayMode.hiddenBalance
|
||||
? S.of(context).long_press_show_balance
|
||||
: S.of(context).long_press_hide_balance)
|
||||
: null,
|
||||
onTap: onCardTap,
|
||||
onLongPress: isSelected ? onCardLongPress : null,
|
||||
child: GestureDetector(
|
||||
excludeFromSemantics: true,
|
||||
onTap: onCardTap,
|
||||
onLongPress: onCardLongPress,
|
||||
child: Observer(builder: (_) {
|
||||
if (realIndex >= (widget.accountListViewModel?.accounts.length ?? 1)) {
|
||||
return Container();
|
||||
}
|
||||
} else if (widget.dashboardViewModel.balanceViewModel.showCombinedBalance) {
|
||||
walletBalance = "";
|
||||
walletFiatBalance = widget.dashboardViewModel.balanceViewModel.combinedFiatBalance;
|
||||
} else {
|
||||
walletBalance = walletBalanceRecord?.availableBalance ?? "0";
|
||||
walletFiatBalance = walletBalanceRecord?.fiatAvailableBalance ?? "0.00";
|
||||
}
|
||||
final account = widget.accountListViewModel?.accounts[realIndex];
|
||||
|
||||
// the card designs is empty if widget gets built before it loads.
|
||||
// should get populated before user sees anything
|
||||
final CardDesign cardDesign;
|
||||
if (widget.dashboardViewModel.cardDesigns.isEmpty ||
|
||||
realIndex >= widget.dashboardViewModel.cardDesigns.length)
|
||||
cardDesign = CardDesign.genericDefault;
|
||||
else if (widget.lightningMode)
|
||||
cardDesign = widget.dashboardViewModel.cardDesigns[realIndex + 1];
|
||||
else
|
||||
cardDesign = widget.dashboardViewModel.cardDesigns[realIndex];
|
||||
// The second balance should always be the lightning balance
|
||||
// printV(widget.dashboardViewModel.balanceViewModel.formattedBalances.first.availableBalance);
|
||||
final walletBalanceRecord = widget.dashboardViewModel.balanceViewModel
|
||||
.getMainBalanceRecord(widget.lightningMode);
|
||||
|
||||
final String accountName;
|
||||
final String accountBalance;
|
||||
if (account == null) {
|
||||
accountName = "";
|
||||
accountBalance = "";
|
||||
} else {
|
||||
accountName = account.label;
|
||||
accountBalance = account.balance ?? "0.00";
|
||||
}
|
||||
late final String walletBalance;
|
||||
late final String walletFiatBalance;
|
||||
if (widget.dashboardViewModel.mwebEnabled && widget.dashboardViewModel.hasMweb) {
|
||||
if (widget.dashboardViewModel.balanceViewModel.displayMode ==
|
||||
BalanceDisplayMode.hiddenBalance) {
|
||||
walletBalance = '●●●●●●';
|
||||
walletFiatBalance = '●●●●●●';
|
||||
} else {
|
||||
walletBalance = walletBalanceRecord?.combinedAvailableBalance ?? "0";
|
||||
walletFiatBalance = walletBalanceRecord?.combinedFiatAvailableBalance ?? "0.00";
|
||||
}
|
||||
} else if (widget.dashboardViewModel.balanceViewModel.showCombinedBalance) {
|
||||
walletBalance = "";
|
||||
walletFiatBalance = widget.dashboardViewModel.balanceViewModel.combinedFiatBalance;
|
||||
} else {
|
||||
walletBalance = walletBalanceRecord?.availableBalance ?? "0";
|
||||
walletFiatBalance = walletBalanceRecord?.fiatAvailableBalance ?? "0.00";
|
||||
}
|
||||
|
||||
final assetName = widget.dashboardViewModel.balanceViewModel.showCombinedBalance
|
||||
? ""
|
||||
: walletBalanceRecord?.formattedAssetTitle ?? assetTitleFallback;
|
||||
// the card designs is empty if widget gets built before it loads.
|
||||
// should get populated before user sees anything
|
||||
final CardDesign cardDesign;
|
||||
if (widget.dashboardViewModel.cardDesigns.isEmpty ||
|
||||
realIndex >= widget.dashboardViewModel.cardDesigns.length)
|
||||
cardDesign = CardDesign.genericDefault;
|
||||
else if (widget.lightningMode)
|
||||
cardDesign = widget.dashboardViewModel.cardDesigns[realIndex + 1];
|
||||
else
|
||||
cardDesign = widget.dashboardViewModel.cardDesigns[realIndex];
|
||||
|
||||
final List<BalanceCardAction> actions = widget.lightningMode
|
||||
? [
|
||||
BalanceCardAction(
|
||||
label: S.current.bitcoin_lightning_deposit,
|
||||
icon: Icons.arrow_downward,
|
||||
onTap: depositToL2,
|
||||
),
|
||||
BalanceCardAction(
|
||||
label: S.current.bitcoin_lightning_withdraw,
|
||||
icon: Icons.arrow_upward,
|
||||
onTap: withdrawFromL2,
|
||||
)
|
||||
]
|
||||
: widget.dashboardViewModel.isEnabledTradeAction
|
||||
? [
|
||||
BalanceCardAction(
|
||||
label: S.current.buy,
|
||||
icon: Icons.arrow_forward_ios_rounded,
|
||||
iconSize: 12,
|
||||
onTap: () => Navigator.of(context).pushNamed(Routes.buySellPage),
|
||||
)
|
||||
]
|
||||
: [];
|
||||
final String accountName;
|
||||
final String accountBalance;
|
||||
if (account == null) {
|
||||
accountName = "";
|
||||
accountBalance = "";
|
||||
} else {
|
||||
accountName = account.label;
|
||||
accountBalance = account.balance ?? "0.00";
|
||||
}
|
||||
|
||||
return BalanceCard(
|
||||
width: effectiveCardWidth,
|
||||
accountName: accountName,
|
||||
accountBalance: accountBalance,
|
||||
designSwitchDuration: Duration(milliseconds: 150),
|
||||
assetName: assetName,
|
||||
capitalizeAssetName: _shouldCapitalizeAssetName(),
|
||||
balance: walletBalance,
|
||||
fiatCurrencyTitle: walletBalanceRecord?.fiatCurrency?.title ??
|
||||
widget.dashboardViewModel.settingsStore.fiatCurrency.title,
|
||||
fiatFirst: widget.dashboardViewModel.balanceViewModel.showCombinedBalance,
|
||||
fiatBalance: walletFiatBalance,
|
||||
selected: _selectedIndex == visualIndex,
|
||||
onCustomizeTapped: _selectedIndex == visualIndex ? widget.onCustomizeTapped : null,
|
||||
design: cardDesign,
|
||||
actions: actions,
|
||||
);
|
||||
}),
|
||||
final assetName = widget.dashboardViewModel.balanceViewModel.showCombinedBalance
|
||||
? ""
|
||||
: walletBalanceRecord?.formattedAssetTitle ?? assetTitleFallback;
|
||||
|
||||
final List<BalanceCardAction> actions = widget.lightningMode
|
||||
? [
|
||||
BalanceCardAction(
|
||||
label: S.current.bitcoin_lightning_deposit,
|
||||
icon: Icons.arrow_downward,
|
||||
onTap: depositToL2,
|
||||
),
|
||||
BalanceCardAction(
|
||||
label: S.current.bitcoin_lightning_withdraw,
|
||||
icon: Icons.arrow_upward,
|
||||
onTap: withdrawFromL2,
|
||||
)
|
||||
]
|
||||
: widget.dashboardViewModel.isEnabledTradeAction
|
||||
? [
|
||||
BalanceCardAction(
|
||||
label: S.current.buy,
|
||||
icon: Icons.arrow_forward_ios_rounded,
|
||||
iconSize: 12,
|
||||
onTap: () => Navigator.of(context).pushNamed(Routes.buySellPage),
|
||||
)
|
||||
]
|
||||
: [];
|
||||
|
||||
return BalanceCard(
|
||||
width: effectiveCardWidth,
|
||||
accountName: accountName,
|
||||
accountBalance: accountBalance,
|
||||
designSwitchDuration: Duration(milliseconds: 150),
|
||||
assetName: assetName,
|
||||
capitalizeAssetName: _shouldCapitalizeAssetName(),
|
||||
balance: walletBalance,
|
||||
fiatCurrencyTitle: walletBalanceRecord?.fiatCurrency?.title ??
|
||||
widget.dashboardViewModel.settingsStore.fiatCurrency.title,
|
||||
fiatFirst: widget.dashboardViewModel.balanceViewModel.showCombinedBalance,
|
||||
fiatBalance: walletFiatBalance,
|
||||
selected: _selectedIndex == visualIndex,
|
||||
onCustomizeTapped: _selectedIndex == visualIndex ? widget.onCustomizeTapped : null,
|
||||
design: cardDesign,
|
||||
actions: actions,
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -21,51 +21,63 @@ class MwebAd extends StatelessWidget {
|
||||
child: Column(
|
||||
spacing: 12,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () => Navigator.of(context).pushNamed(Routes.mwebSettings),
|
||||
child: Container(
|
||||
height: 64,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
color: Theme.of(context).colorScheme.surfaceContainer),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/settings_row_icons/mweb.svg",
|
||||
width: 24,
|
||||
height: 24,
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: Text(
|
||||
S.of(context).mweb_ad,
|
||||
softWrap: true,
|
||||
style: TextStyle(fontSize: 12),
|
||||
MergeSemantics(
|
||||
child: Semantics(
|
||||
button: true,
|
||||
child: GestureDetector(
|
||||
onTap: () => Navigator.of(context).pushNamed(Routes.mwebSettings),
|
||||
child: Container(
|
||||
height: 64,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
color: Theme.of(context).colorScheme.surfaceContainer),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
ExcludeSemantics(
|
||||
child: CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/settings_row_icons/mweb.svg",
|
||||
width: 24,
|
||||
height: 24,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: Text(
|
||||
S.of(context).mweb_ad,
|
||||
softWrap: true,
|
||||
style: TextStyle(fontSize: 12),
|
||||
),
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
size: 16,
|
||||
Icons.arrow_forward_ios,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
)
|
||||
],
|
||||
),
|
||||
Icon(
|
||||
size: 16,
|
||||
Icons.arrow_forward_ios,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () => dashboardViewModel.dismissMwebAd(false),
|
||||
child: Text(
|
||||
S.of(context).do_not_show_anymore,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.primary),
|
||||
MergeSemantics(
|
||||
child: Semantics(
|
||||
button: true,
|
||||
child: GestureDetector(
|
||||
onTap: () => dashboardViewModel.dismissMwebAd(false),
|
||||
child: Text(
|
||||
S.of(context).do_not_show_anymore,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -27,10 +28,16 @@ class ChainIcon extends StatelessWidget {
|
||||
AnimatedOpacity(
|
||||
duration: Duration(milliseconds: 100),
|
||||
opacity: done ? 0 : 1,
|
||||
child: CircularProgressIndicator(
|
||||
value: progress,
|
||||
color: Color(0xFFFFB84E),
|
||||
strokeWidth: 2,
|
||||
// Faded out means "nothing to report", so it must leave the tree too.
|
||||
child: ExcludeSemantics(
|
||||
excluding: done,
|
||||
child: CircularProgressIndicator(
|
||||
value: progress,
|
||||
color: Color(0xFFFFB84E),
|
||||
strokeWidth: 2,
|
||||
semanticsLabel: S.of(context).synchronizing,
|
||||
semanticsValue: "${(progress * 100).round()}%",
|
||||
),
|
||||
),
|
||||
),
|
||||
AnimatedScale(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -12,76 +13,84 @@ class LightningSwitcher extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
HapticFeedback.mediumImpact();
|
||||
onLightningSwitchPress();
|
||||
},
|
||||
child: Container(
|
||||
decoration: ShapeDecoration(
|
||||
shape: RoundedSuperellipseBorder(borderRadius: BorderRadiusGeometry.circular(900.0)),
|
||||
color: Theme.of(context).colorScheme.surfaceContainer),
|
||||
width: 70,
|
||||
height: 36,
|
||||
padding: EdgeInsets.symmetric(vertical: 2),
|
||||
child: Stack(
|
||||
children: [
|
||||
AnimatedContainer(
|
||||
alignment: Alignment.centerRight,
|
||||
margin: EdgeInsets.only(left: lightningMode ? 36 : 2),
|
||||
duration: Duration(milliseconds: 250),
|
||||
curve: Curves.easeOutCubic,
|
||||
width: 32,
|
||||
height: 32,
|
||||
// height: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(9999990.0)),
|
||||
color: Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
Container(
|
||||
child: Row(
|
||||
spacing: 2.0,
|
||||
children: [
|
||||
SizedBox(),
|
||||
AnimatedSwitcher(
|
||||
duration: Duration(milliseconds: 150),
|
||||
transitionBuilder: (child, animation) =>
|
||||
FadeTransition(opacity: animation, child: child),
|
||||
child: CakeImageWidget(
|
||||
imageUrl: 'assets/new-ui/switcher-bitcoin.svg',
|
||||
key: ValueKey(lightningMode),
|
||||
width: 32,
|
||||
height: 32,
|
||||
colorFilter: ColorFilter.mode(
|
||||
lightningMode
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.surfaceContainer,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
),
|
||||
AnimatedSwitcher(
|
||||
duration: Duration(milliseconds: 150),
|
||||
transitionBuilder: (child, animation) =>
|
||||
FadeTransition(opacity: animation, child: child),
|
||||
child: CakeImageWidget(
|
||||
imageUrl: 'assets/new-ui/switcher-lightning.svg',
|
||||
key: ValueKey(lightningMode),
|
||||
width: 32,
|
||||
height: 32,
|
||||
colorFilter: ColorFilter.mode(
|
||||
lightningMode
|
||||
? Theme.of(context).colorScheme.surfaceContainer
|
||||
: Theme.of(context).colorScheme.primary,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
// One toggle node: the knob position and the coloured glyphs are the only
|
||||
// visual cue for which mode is active.
|
||||
return Semantics(
|
||||
button: true,
|
||||
toggled: lightningMode,
|
||||
label: S.of(context).lightning_mode,
|
||||
child: SizedBox(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
HapticFeedback.mediumImpact();
|
||||
onLightningSwitchPress();
|
||||
},
|
||||
child: Container(
|
||||
decoration: ShapeDecoration(
|
||||
shape:
|
||||
RoundedSuperellipseBorder(borderRadius: BorderRadiusGeometry.circular(900.0)),
|
||||
color: Theme.of(context).colorScheme.surfaceContainer),
|
||||
width: 70,
|
||||
height: 36,
|
||||
padding: EdgeInsets.symmetric(vertical: 2),
|
||||
child: Stack(
|
||||
children: [
|
||||
AnimatedContainer(
|
||||
alignment: Alignment.centerRight,
|
||||
margin: EdgeInsets.only(left: lightningMode ? 36 : 2),
|
||||
duration: Duration(milliseconds: 250),
|
||||
curve: Curves.easeOutCubic,
|
||||
width: 32,
|
||||
height: 32,
|
||||
// height: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(9999990.0)),
|
||||
color: Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
),
|
||||
],
|
||||
Container(
|
||||
child: Row(
|
||||
spacing: 2.0,
|
||||
children: [
|
||||
SizedBox(),
|
||||
AnimatedSwitcher(
|
||||
duration: Duration(milliseconds: 150),
|
||||
transitionBuilder: (child, animation) =>
|
||||
FadeTransition(opacity: animation, child: child),
|
||||
child: CakeImageWidget(
|
||||
imageUrl: 'assets/new-ui/switcher-bitcoin.svg',
|
||||
key: ValueKey(lightningMode),
|
||||
width: 32,
|
||||
height: 32,
|
||||
colorFilter: ColorFilter.mode(
|
||||
lightningMode
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.surfaceContainer,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
),
|
||||
AnimatedSwitcher(
|
||||
duration: Duration(milliseconds: 150),
|
||||
transitionBuilder: (child, animation) =>
|
||||
FadeTransition(opacity: animation, child: child),
|
||||
child: CakeImageWidget(
|
||||
imageUrl: 'assets/new-ui/switcher-lightning.svg',
|
||||
key: ValueKey(lightningMode),
|
||||
width: 32,
|
||||
height: 32,
|
||||
colorFilter: ColorFilter.mode(
|
||||
lightningMode
|
||||
? Theme.of(context).colorScheme.surfaceContainer
|
||||
: Theme.of(context).colorScheme.primary,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:cake_wallet/core/sync_status_title.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/top_bar_widget/pulsing_dot.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
|
||||
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
|
||||
@@ -47,65 +48,55 @@ class SyncBar extends StatelessWidget {
|
||||
child: Stack(
|
||||
alignment: Alignment.centerLeft,
|
||||
children: [
|
||||
if (!_showFullBar())
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 6,
|
||||
children: [
|
||||
if (dashboardViewModel.isTorEnabled)
|
||||
CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/tor.svg",
|
||||
width: 20,
|
||||
height: 20,
|
||||
),
|
||||
if (_showDot()) PulsingDot(),
|
||||
],
|
||||
),
|
||||
if (!_showFullBar()) _buildCompactBar(context),
|
||||
if (_showFullBar())
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
CupertinoScaffold.showCupertinoModalBottomSheet(
|
||||
context: context,
|
||||
barrierColor: Colors.black.withAlpha(85),
|
||||
builder: (context) => FractionallySizedBox(
|
||||
child: Material(
|
||||
child: getIt.get<ManageNodesPage>(param1: false),
|
||||
)));
|
||||
},
|
||||
child: AnimatedSwitcher(
|
||||
duration: Duration(milliseconds: 100),
|
||||
child: Container(
|
||||
key: ValueKey(status.runtimeType),
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(9999),
|
||||
border: _getBorder(context, status.runtimeType),
|
||||
color: _getBackgroundColor(context, status.runtimeType),
|
||||
),
|
||||
child: Row(
|
||||
spacing: 10,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
if (icon != null) icon,
|
||||
// if (dashboardViewModel.silentPaymentsScanningActive &&
|
||||
// progressStatuses.contains(status.runtimeType)) ...[
|
||||
// Text(
|
||||
// "${(status.progress() * 100).toInt()}%",
|
||||
// style: TextStyle(fontSize: 12, color: Color(0xFFEFBA5E)),
|
||||
// ),
|
||||
// Text(
|
||||
// "·",
|
||||
// style: TextStyle(fontSize: 12),
|
||||
// )
|
||||
// ],
|
||||
Text(
|
||||
syncStatusTitle(
|
||||
status, dashboardViewModel.settingsStore.syncStatusDisplayMode),
|
||||
style: _getTextStyle(context, status.runtimeType),
|
||||
// A single node: the localized status text (plus any active
|
||||
// Tor/MWEB/Silent Payments badge) is the label, and the hint says
|
||||
// where tapping leads. Everything inside is redundant with it.
|
||||
Semantics(
|
||||
button: true,
|
||||
label: _statusSemanticsLabel(context, status),
|
||||
hint: S.of(context).manage_nodes,
|
||||
onTap: () => _openNodeManagement(context),
|
||||
child: ExcludeSemantics(
|
||||
child: GestureDetector(
|
||||
onTap: () => _openNodeManagement(context),
|
||||
child: AnimatedSwitcher(
|
||||
duration: Duration(milliseconds: 100),
|
||||
child: Container(
|
||||
key: ValueKey(status.runtimeType),
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(9999),
|
||||
border: _getBorder(context, status.runtimeType),
|
||||
color: _getBackgroundColor(context, status.runtimeType),
|
||||
),
|
||||
],
|
||||
child: Row(
|
||||
spacing: 10,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
if (icon != null) icon,
|
||||
// if (dashboardViewModel.silentPaymentsScanningActive &&
|
||||
// progressStatuses.contains(status.runtimeType)) ...[
|
||||
// Text(
|
||||
// "${(status.progress() * 100).toInt()}%",
|
||||
// style: TextStyle(fontSize: 12, color: Color(0xFFEFBA5E)),
|
||||
// ),
|
||||
// Text(
|
||||
// "·",
|
||||
// style: TextStyle(fontSize: 12),
|
||||
// )
|
||||
// ],
|
||||
Text(
|
||||
syncStatusTitle(status,
|
||||
dashboardViewModel.settingsStore.syncStatusDisplayMode),
|
||||
style: _getTextStyle(context, status.runtimeType),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -117,6 +108,55 @@ class SyncBar extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
void _openNodeManagement(BuildContext context) =>
|
||||
CupertinoScaffold.showCupertinoModalBottomSheet(
|
||||
context: context,
|
||||
barrierColor: Colors.black.withAlpha(85),
|
||||
builder: (context) => FractionallySizedBox(
|
||||
child: Material(
|
||||
child: getIt.get<ManageNodesPage>(param1: false),
|
||||
)));
|
||||
|
||||
/// Compact mode shows sync state with a pulsing dot (and a Tor glyph) only, so
|
||||
/// the whole row needs a text equivalent.
|
||||
Widget _buildCompactBar(BuildContext context) {
|
||||
final row = Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 6,
|
||||
children: [
|
||||
if (dashboardViewModel.isTorEnabled)
|
||||
CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/tor.svg",
|
||||
width: 20,
|
||||
height: 20,
|
||||
),
|
||||
if (_showDot()) PulsingDot(),
|
||||
],
|
||||
);
|
||||
|
||||
final label = _joinLabels([
|
||||
if (dashboardViewModel.isTorEnabled) S.of(context).tor_connection,
|
||||
if (_showDot()) S.of(context).synchronizing,
|
||||
]);
|
||||
|
||||
if (label.isEmpty) return row;
|
||||
|
||||
return Semantics(label: label, child: ExcludeSemantics(child: row));
|
||||
}
|
||||
|
||||
String _statusSemanticsLabel(BuildContext context, SyncStatus status) {
|
||||
final isFailure = failStatuses.contains(status.runtimeType);
|
||||
|
||||
return _joinLabels([
|
||||
syncStatusTitle(status, dashboardViewModel.settingsStore.syncStatusDisplayMode),
|
||||
if (!isFailure && dashboardViewModel.isTorEnabled) S.of(context).tor_connection,
|
||||
if (!isFailure && dashboardViewModel.hasMweb) S.of(context).litecoin_mweb,
|
||||
if (!isFailure && dashboardViewModel.hasSilentPayments) S.of(context).silent_payments,
|
||||
]);
|
||||
}
|
||||
|
||||
String _joinLabels(List<String> parts) => parts.where((part) => part.isNotEmpty).join(", ");
|
||||
|
||||
Color? _getBackgroundColor(BuildContext context, Type status) {
|
||||
if (failStatuses.contains(status)) {
|
||||
return Theme.of(context).colorScheme.errorContainer.withAlpha(64);
|
||||
|
||||
@@ -21,15 +21,22 @@ class WalletInfoBar extends StatelessWidget {
|
||||
final bool hasCustomize;
|
||||
final VoidCallback onCustomizeButtonTap;
|
||||
|
||||
void _openAccountCustomizer() {
|
||||
if (hasCustomize) {
|
||||
onCustomizeButtonTap();
|
||||
HapticFeedback.mediumImpact();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
if (hasCustomize) {
|
||||
onCustomizeButtonTap();
|
||||
HapticFeedback.mediumImpact();
|
||||
}
|
||||
},
|
||||
// The row, the hardware-wallet glyph and the inner accounts button are one
|
||||
// control for a screen reader: a single labeled node opening the customizer.
|
||||
final semanticsLabel =
|
||||
hardwareWalletType == null ? name : "$name, ${S.of(context).hardware_wallet}";
|
||||
|
||||
final row = GestureDetector(
|
||||
onTap: _openAccountCustomizer,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
@@ -70,12 +77,7 @@ class WalletInfoBar extends StatelessWidget {
|
||||
SizedBox(width: 8),
|
||||
ModernButton.svg(
|
||||
size: 24,
|
||||
onPressed: () {
|
||||
if (hasCustomize) {
|
||||
onCustomizeButtonTap();
|
||||
HapticFeedback.mediumImpact();
|
||||
}
|
||||
},
|
||||
onPressed: _openAccountCustomizer,
|
||||
svgPath: "assets/new-ui/icon-accounts.svg",
|
||||
semanticLabel: S.of(context).wallet_accounts,
|
||||
)
|
||||
@@ -83,6 +85,18 @@ class WalletInfoBar extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (!hasCustomize) {
|
||||
return Semantics(label: semanticsLabel, child: ExcludeSemantics(child: row));
|
||||
}
|
||||
|
||||
return Semantics(
|
||||
button: true,
|
||||
label: semanticsLabel,
|
||||
hint: S.of(context).wallet_accounts,
|
||||
onTap: _openAccountCustomizer,
|
||||
child: ExcludeSemantics(child: row),
|
||||
);
|
||||
}
|
||||
|
||||
String? get hardwareWalletIcon {
|
||||
|
||||
@@ -93,33 +93,42 @@ class _LineTabSwitcherState extends State<LineTabSwitcher> {
|
||||
children: widget.tabs.map((item) {
|
||||
final index = widget.tabs.indexOf(item);
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
widget.onTabChange(index);
|
||||
},
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
AnimatedDefaultTextStyle(
|
||||
duration: Duration(milliseconds: 150),
|
||||
style: DefaultTextStyle.of(context).style.copyWith(
|
||||
inherit: true,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: widget.selectedTab == index
|
||||
? Theme.of(context).colorScheme.onSurface
|
||||
: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
// One tab = one node: the visible label merges into a button node
|
||||
// that also carries the selected state of this tab.
|
||||
return MergeSemantics(
|
||||
child: Semantics(
|
||||
button: true,
|
||||
selected: widget.selectedTab == index,
|
||||
inMutuallyExclusiveGroup: true,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
widget.onTabChange(index);
|
||||
},
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
AnimatedDefaultTextStyle(
|
||||
duration: Duration(milliseconds: 150),
|
||||
style: DefaultTextStyle.of(context).style.copyWith(
|
||||
inherit: true,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: widget.selectedTab == index
|
||||
? Theme.of(context).colorScheme.onSurface
|
||||
: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: itemPadding / 2),
|
||||
child: Text(
|
||||
item,
|
||||
key: textWidgetKeys[index],
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: itemPadding / 2),
|
||||
child: Text(
|
||||
item,
|
||||
key: textWidgetKeys[index],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList())),
|
||||
|
||||
@@ -60,31 +60,38 @@ class _LongPressMenuState extends State<LongPressMenu> {
|
||||
final color = item.color ?? Theme.of(context).colorScheme.onSurface;
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: item.onSelected,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 16,
|
||||
right: 16,
|
||||
top: 12,
|
||||
bottom: 12,
|
||||
),
|
||||
child: Container(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
spacing: 8,
|
||||
children: [
|
||||
CakeImageWidget(
|
||||
imageUrl: item.iconPath,
|
||||
height: 20,
|
||||
width: 20,
|
||||
colorFilter: ColorFilter.mode(color, BlendMode.srcIn),
|
||||
child: MergeSemantics(
|
||||
child: Semantics(
|
||||
button: true,
|
||||
child: InkWell(
|
||||
onTap: item.onSelected,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 16,
|
||||
right: 16,
|
||||
top: 12,
|
||||
bottom: 12,
|
||||
),
|
||||
child: Container(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
spacing: 8,
|
||||
children: [
|
||||
ExcludeSemantics(
|
||||
child: CakeImageWidget(
|
||||
imageUrl: item.iconPath,
|
||||
height: 20,
|
||||
width: 20,
|
||||
colorFilter: ColorFilter.mode(color, BlendMode.srcIn),
|
||||
),
|
||||
),
|
||||
Text(item.label,
|
||||
style: TextStyle(
|
||||
color: color, fontSize: 14, fontWeight: FontWeight.w500)),
|
||||
],
|
||||
),
|
||||
Text(item.label,
|
||||
style: TextStyle(
|
||||
color: color, fontSize: 14, fontWeight: FontWeight.w500)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -187,50 +187,56 @@ class _NEWNewMainNavBarState extends State<NewMainNavBar> {
|
||||
? iconHorizontalPadding / 100
|
||||
: 0),
|
||||
curve: Curves.easeOutCubic,
|
||||
child: InkWell(
|
||||
splashFactory: NoSplash.splashFactory,
|
||||
splashColor: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(pillBorderRadius),
|
||||
onTap: () => _onItemTap(i),
|
||||
child: AnimatedContainer(
|
||||
duration:
|
||||
_firstFrame ? Duration.zero : inactiveIconMoveDuration,
|
||||
curve: Curves.easeOutCubic,
|
||||
width: i == widget.selectedIndex ? pillWidth : iconBoxWidth,
|
||||
alignment: Alignment.center,
|
||||
child: AnimatedAlign(
|
||||
duration: inactiveIconFadeDuration,
|
||||
child: Semantics(
|
||||
button: true,
|
||||
selected: i == widget.selectedIndex,
|
||||
inMutuallyExclusiveGroup: true,
|
||||
label: visibleActions[i].name(context),
|
||||
child: InkWell(
|
||||
splashFactory: NoSplash.splashFactory,
|
||||
splashColor: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(pillBorderRadius),
|
||||
onTap: () => _onItemTap(i),
|
||||
child: AnimatedContainer(
|
||||
duration:
|
||||
_firstFrame ? Duration.zero : inactiveIconMoveDuration,
|
||||
curve: Curves.easeOutCubic,
|
||||
width: i == widget.selectedIndex ? pillWidth : iconBoxWidth,
|
||||
alignment: Alignment.center,
|
||||
child: AnimatedScale(
|
||||
duration: inactiveIconAppearDuration,
|
||||
child: AnimatedAlign(
|
||||
duration: inactiveIconFadeDuration,
|
||||
curve: Curves.easeOutCubic,
|
||||
scale: (i == widget.selectedIndex) ? 0.857 : 1.0,
|
||||
child: TweenAnimationBuilder<Color?>(
|
||||
tween: ColorTween(
|
||||
begin: (i == widget.selectedIndex)
|
||||
? inactiveColor
|
||||
: activeColor,
|
||||
end: (i == widget.selectedIndex)
|
||||
? activeColor
|
||||
: inactiveColor,
|
||||
),
|
||||
duration: iconColorChangeDuration,
|
||||
builder: (context, value, child) {
|
||||
return Container(
|
||||
height: NewMainNavBar.barHeight,
|
||||
child: CakeImageWidget(
|
||||
imageUrl: visibleActions[i].image,
|
||||
width: iconWidth,
|
||||
height: iconHeight,
|
||||
//fit: BoxFit.scaleDown,
|
||||
colorFilter: ColorFilter.mode(
|
||||
value ?? inactiveColor,
|
||||
BlendMode.srcIn,
|
||||
alignment: Alignment.center,
|
||||
child: AnimatedScale(
|
||||
duration: inactiveIconAppearDuration,
|
||||
curve: Curves.easeOutCubic,
|
||||
scale: (i == widget.selectedIndex) ? 0.857 : 1.0,
|
||||
child: TweenAnimationBuilder<Color?>(
|
||||
tween: ColorTween(
|
||||
begin: (i == widget.selectedIndex)
|
||||
? inactiveColor
|
||||
: activeColor,
|
||||
end: (i == widget.selectedIndex)
|
||||
? activeColor
|
||||
: inactiveColor,
|
||||
),
|
||||
duration: iconColorChangeDuration,
|
||||
builder: (context, value, child) {
|
||||
return Container(
|
||||
height: NewMainNavBar.barHeight,
|
||||
child: CakeImageWidget(
|
||||
imageUrl: visibleActions[i].image,
|
||||
width: iconWidth,
|
||||
height: iconHeight,
|
||||
//fit: BoxFit.scaleDown,
|
||||
colorFilter: ColorFilter.mode(
|
||||
value ?? inactiveColor,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -306,11 +312,15 @@ class AnimatedPill extends StatelessWidget {
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: pillIconWidth + 2),
|
||||
child: Text(
|
||||
currentAction.name(context),
|
||||
style: pillTextStyle.copyWith(color: contentColor),
|
||||
overflow: TextOverflow.fade,
|
||||
softWrap: false,
|
||||
// The selected tab's InkWell already announces this name, so the
|
||||
// pill text must not become a second stop for screen readers.
|
||||
child: ExcludeSemantics(
|
||||
child: Text(
|
||||
currentAction.name(context),
|
||||
style: pillTextStyle.copyWith(color: contentColor),
|
||||
overflow: TextOverflow.fade,
|
||||
softWrap: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -528,6 +528,7 @@
|
||||
"got_it": "Got it",
|
||||
"gross_balance": "Gross Balance",
|
||||
"group_by_type": "Group by type",
|
||||
"hardware_wallet": "Hardware wallet",
|
||||
"haven_app": "Haven by Cake Wallet",
|
||||
"haven_app_wallet_text": "Awesome wallet for Haven",
|
||||
"help": "help",
|
||||
@@ -588,6 +589,7 @@
|
||||
"lightning_deposit_desc": "When you deposit to Lightning, you are swapping your on-chain Bitcoin from this wallet to your Lightning account.",
|
||||
"lightning_deposit_disclaimer": "The new Lightning balance will not be available until the Bitcoin transaction is fully confirmed.",
|
||||
"lightning_external_disclaimer": "Don't close this page until you send the funds, or else the deposit won't proceed.",
|
||||
"lightning_mode": "Lightning mode",
|
||||
"lightning_username_desc": "Easily receive Lightning payments with a Lightning username by choosing a custom name or using a randomly generated one below.",
|
||||
"lightning_username_desc_completed": "You can use this username to receive payments from any Lightning wallet.",
|
||||
"lightning_username_setup_later": "If you skip now, you can set this up later in the Settings.",
|
||||
@@ -618,6 +620,7 @@
|
||||
"login": "Login",
|
||||
"logout": "Logout",
|
||||
"long_press_edit_address": "Long press to edit address",
|
||||
"long_press_hide_balance": "Long press card to hide balance",
|
||||
"long_press_show_balance": "Long press card to show balance",
|
||||
"low_fee": "Low fee",
|
||||
"low_fee_alert": "You currently are using a low network fee priority. This could cause long waits, different rates, or canceled trades. We recommend setting a higher fee for a better experience.",
|
||||
@@ -726,6 +729,7 @@
|
||||
"onramper_option_description": "Quickly buy crypto with many payment methods. Available in most countries. Spreads and fees vary.",
|
||||
"open_gift_card": "Open Gift Card",
|
||||
"open_wallet": "Open Wallet",
|
||||
"opens_externally": "Opens externally",
|
||||
"optional_description": "Optional description",
|
||||
"optional_email_hint": "Optional payee notification email",
|
||||
"optional_name": "Optional recipient name",
|
||||
|
||||
Reference in New Issue
Block a user