Compare commits

...

396 commits

Author SHA1 Message Date
GLaDOS
c7909049dc
Merge pull request #7694 from BlueWallet/handled
FIX: Dleeting wallet would not popToTop after deleting
2025-03-13 13:44:07 +00:00
Marcos Rodriguez Velez
b5270d0a07 FIX: Dleeting wallet would not popToTop after deleting 2025-03-13 08:10:06 -04:00
GLaDOS
4f3b828990
Merge pull request #7689 from BlueWallet/sle
ADD: Allow quick tap to copy
2025-03-13 11:04:17 +00:00
GLaDOS
26720e8284
Merge pull request #7692 from BlueWallet/f
FIX: Issue - Function to broadcast transaction from *.final PSBT not …
2025-03-13 10:46:24 +00:00
Marcos Rodriguez Velez
a80bacc0f4 FIX: Issue - Function to broadcast transaction from *.final PSBT not available 7.1.4 #7688 2025-03-12 22:13:09 -04:00
Marcos Rodriguez Velez
5f18540ca7 FIX: UX in ManageWallets 2025-03-12 20:42:29 -04:00
Marcos Rodriguez Velez
c14cb3508c ADD: Allow quick tap to copy 2025-03-12 19:00:08 -04:00
GLaDOS
751c7d6f45
Merge pull request #7682 from BlueWallet/menud
REF: MenuItem memory
2025-03-10 18:39:40 +00:00
GLaDOS
0b1c3dd9f7
Merge pull request #7684 from BlueWallet/man
FIX: DIscard changes alert was not working
2025-03-09 19:00:23 +00:00
Marcos Rodriguez Velez
ae89a59794 FIX: DIscard changes alert was not working 2025-03-09 10:23:56 -04:00
Marcos Rodriguez Velez
10b3432e0e OPS: Version bump 2025-03-09 10:17:04 -04:00
Marcos Rodriguez Velez
c67eea8155 REF: Use bottom tabs 2025-03-09 07:45:19 -04:00
Marcos Rodriguez Velez
9421511f74 Update useMenuElements.ts 2025-03-08 19:11:51 -04:00
Marcos Rodriguez Velez
9ec0ef51e4 Update useMenuElements.ios.ts 2025-03-08 19:09:41 -04:00
Marcos Rodriguez Velez
1cada11c50 REF: MenuItem memory 2025-03-08 11:06:55 -04:00
Marcos Rodriguez Velez
d2cebde6ad OPS: Version bump 2025-03-08 09:38:46 -04:00
GLaDOS
1a940971bc
Merge pull request #7677 from BlueWallet/scanln
REF: ScanLNDInvoice to TSX
2025-03-08 09:22:24 +00:00
GLaDOS
28316b4d73
Merge pull request #7654 from BlueWallet/renovate/androidx.constraintlayout-constraintlayout-2.x
Update dependency androidx.constraintlayout:constraintlayout to v2.2.1
2025-03-08 08:35:37 +00:00
GLaDOS
4670eea38a
Merge pull request #7681 from BlueWallet/ele
FIX: Can not turn Electrum Server on. #7680
2025-03-08 08:15:05 +00:00
Marcos Rodriguez Vélez
b2552bdc71
Merge branch 'master' into ele 2025-03-07 22:29:36 -04:00
Marcos Rodriguez Vélez
dbd4066f7e
Merge branch 'master' into scanln 2025-03-07 22:29:25 -04:00
Marcos Rodriguez Vélez
4cdd952f90
FIX: Update use of CoinDesk API (#7678) 2025-03-07 22:29:12 -04:00
Marcos Rodriguez Velez
ddee4cdaaf FIX: Can not turn Electrum Server on. #7680 2025-03-07 21:58:33 -04:00
Marcos Rodriguez Velez
0aa6b96e4b w 2025-03-07 19:07:15 -04:00
Marcos Rodriguez Velez
8d49aff279 Merge branch 'master' into scanln 2025-03-07 19:03:43 -04:00
GLaDOS
18a187b120
Merge pull request #7663 from BlueWallet/virw
REF: View Edit Multisig navigation
2025-03-06 10:17:44 +00:00
Marcos Rodriguez Vélez
1f77a852a8
Rename LazyLoadScanLndInvoiceStack.tsx to LazyLoadScanLNDInvoiceStack.tsx 2025-03-05 23:12:45 -04:00
Marcos Rodriguez Vélez
9d899d672d
Rename ScanLndInvoiceStack.tsx to ScanLNDInvoiceStack.tsx 2025-03-05 23:09:28 -04:00
Marcos Rodriguez Velez
e7b81e5517 wi 2025-03-05 21:50:50 -04:00
Marcos Rodriguez Velez
f8af06e2ae REF: ScanLNDInvoice to TSX 2025-03-05 21:43:02 -04:00
Marcos Rodriguez Velez
8b81472fa4 OPS: Master was broken for catalyst 2025-03-05 20:54:50 -04:00
Marcos Rodriguez Velez
4ad2b15070 OPS: Lock file 2025-03-05 20:34:29 -04:00
Marcos Rodriguez Velez
dd118af993 Update project.pbxproj 2025-03-05 20:31:25 -04:00
GLaDOS
d375bd9780
Merge pull request #7672 from BlueWallet/cc
REF: CompanionDelegate to hook
2025-03-05 22:54:21 +00:00
GLaDOS
c967f6701a
Merge pull request #7675 from BlueWallet/revert-7631-headr
Revert "REF: Wallet tranaction header animation"
2025-03-04 11:13:29 +00:00
Overtorment
4753d984ca
Revert "REF: Wallet tranaction header animation" 2025-03-04 10:16:58 +00:00
GLaDOS
28e2e343b8
Merge pull request #7631 from BlueWallet/headr
REF: Wallet tranaction header animation
2025-03-03 19:53:08 +00:00
Marcos Rodriguez Velez
b81879e9bd Update WalletTransactions.tsx 2025-03-03 14:59:49 -04:00
Marcos Rodriguez Velez
040f91028a Update ScanQRCode.tsx 2025-03-03 14:33:49 -04:00
Marcos Rodriguez Velez
1c8aa08de8 Merge branch 'master' into cc 2025-03-03 14:22:21 -04:00
Marcos Rodriguez Velez
d7743a740f Merge branch 'master' into virw 2025-03-03 14:00:14 -04:00
Marcos Rodriguez Velez
a2ec407720 Merge branch 'master' into headr 2025-03-03 13:43:36 -04:00
GLaDOS
c90bf68a66
Merge pull request #7643 from BlueWallet/scr
FIX: Disable screen protect until it supports nav 7
2025-03-03 17:10:05 +00:00
Marcos Rodriguez Velez
7ab73669dc wip 2025-03-03 12:06:53 -04:00
Marcos Rodriguez Velez
72134fef84 Merge branch 'master' into scr 2025-03-03 12:05:49 -04:00
GLaDOS
c0eec1be8e
Merge pull request #7674 from BlueWallet/marcosrdz-patch-4
Update AndroidManifest.xml
2025-03-03 14:41:36 +00:00
Marcos Rodriguez Vélez
77f9cf7e16
Update AndroidManifest.xml 2025-03-03 09:39:13 -04:00
renovate[bot]
88be0332e4
Update dependency androidx.constraintlayout:constraintlayout to v2.2.1 2025-03-03 13:12:51 +00:00
GLaDOS
a4f8e42d8c
Merge pull request #7673 from BlueWallet/pa
OPS: Packages updates
2025-03-03 13:11:48 +00:00
Marcos Rodriguez Vélez
e519360d89
Update bluewallet2.spec.js 2025-03-03 04:25:38 -04:00
Marcos Rodriguez Velez
62a5efc82c OPS: Packages updates 2025-03-03 03:39:00 -04:00
Marcos Rodriguez Velez
ec2bc5e627 wip 2025-03-03 03:30:19 -04:00
Marcos Rodriguez Velez
0bdfc6fa85 Update WalletTransactions.tsx 2025-03-03 01:17:43 -04:00
Marcos Rodriguez Velez
ef5887f28b REF: CompanionDelegate to hook 2025-03-02 22:12:50 -04:00
Marcos Rodriguez Velez
3f82cb4449 Update WalletTransactions.tsx 2025-03-02 21:46:28 -04:00
Marcos Rodriguez Velez
9574554780 Update WalletDetails.tsx 2025-03-02 20:43:19 -04:00
Marcos Rodriguez Velez
4b249edaa4 Update WalletDetails.tsx 2025-03-02 18:43:40 -04:00
Marcos Rodriguez Velez
e3fcbbb713 Revert "Update WalletDetails.tsx"
This reverts commit 2376ef8be9.
2025-03-02 18:23:00 -04:00
Marcos Rodriguez Velez
63a3c61534 Revert "Update WalletDetails.tsx"
This reverts commit a6d66574cd.
2025-03-02 18:22:58 -04:00
Marcos Rodriguez Velez
a6d66574cd Update WalletDetails.tsx 2025-03-02 18:03:17 -04:00
Marcos Rodriguez Velez
2376ef8be9 Update WalletDetails.tsx 2025-03-02 18:03:07 -04:00
Marcos Rodriguez Vélez
155f021692
Merge branch 'master' into headr 2025-03-02 15:54:50 -04:00
GLaDOS
3a26d6dab2
Merge pull request #7667 from BlueWallet/yml
OPS: Refactor iOS pipeline
2025-03-02 19:49:19 +00:00
Marcos Rodriguez Velez
1da481542a Update build-ios-release-pullrequest.yml 2025-03-02 12:57:34 -04:00
Marcos Rodriguez Velez
472307c271 Revert "Update Fastfile"
This reverts commit 09394ff4f9.
2025-03-02 12:47:13 -04:00
Marcos Rodriguez Velez
09394ff4f9 Update Fastfile 2025-03-02 12:37:30 -04:00
Marcos Rodriguez Velez
16936fca27 wip 2025-03-02 12:20:42 -04:00
Marcos Rodriguez Velez
54166c0592 Update Fastfile 2025-03-02 11:54:38 -04:00
Marcos Rodriguez Velez
bb6d443670 wwip 2025-03-02 11:36:33 -04:00
Marcos Rodriguez Velez
e4e16a8f40 Update Fastfile 2025-03-02 05:16:02 -04:00
Marcos Rodriguez Velez
35deca58e0 Update build-ios-release-pullrequest.yml 2025-03-02 04:52:48 -04:00
Marcos Rodriguez Velez
898443f3a5 Update TransactionsNavigationHeader.tsx 2025-03-02 04:51:07 -04:00
Marcos Rodriguez Velez
15fc708a0a Update Fastfile 2025-03-02 04:47:37 -04:00
Marcos Rodriguez Velez
15c618b59a Update Gemfile.lock 2025-03-02 02:01:12 -04:00
Marcos Rodriguez Velez
ccdb492ba0 w 2025-03-02 01:49:30 -04:00
Marcos Rodriguez Velez
0449965ef5 Revert "wip"
This reverts commit 863ac46bc8.
2025-03-02 01:11:36 -04:00
Marcos Rodriguez Velez
758c2acf3a Revert "Update Fastfile"
This reverts commit aa695f2705.
2025-03-02 01:11:33 -04:00
Marcos Rodriguez Velez
b75aa7b269 wip 2025-03-02 01:11:24 -04:00
Marcos Rodriguez Velez
aa695f2705 Update Fastfile 2025-03-02 01:09:40 -04:00
Marcos Rodriguez Velez
863ac46bc8 wip 2025-03-02 00:02:05 -04:00
Marcos Rodriguez Velez
1946fa0dde OPS: Refactor iOS pipeline 2025-03-01 23:51:54 -04:00
Marcos Rodriguez Velez
a62a21b28b Update WalletTransactions.tsx 2025-03-01 23:32:18 -04:00
Marcos Rodriguez Velez
c1ae300254 Update WalletTransactions.tsx 2025-03-01 22:09:53 -04:00
Marcos Rodriguez Velez
5e4d58b207 Update WalletTransactions.tsx 2025-03-01 21:40:46 -04:00
Marcos Rodriguez Velez
3504d0dc30 Revert "Update WalletTransactions.tsx"
This reverts commit 05491387ff.
2025-03-01 21:24:15 -04:00
Marcos Rodriguez Velez
fe795e648b Revert "Update WalletTransactions.tsx"
This reverts commit a65776933d.
2025-03-01 21:24:12 -04:00
Marcos Rodriguez Velez
af8d7d3477 Revert "Update WalletTransactions.tsx"
This reverts commit 88b8274758.
2025-03-01 21:24:09 -04:00
Marcos Rodriguez Velez
c604ac4197 Merge branch 'master' into headr 2025-03-01 21:23:17 -04:00
Marcos Rodriguez Vélez
4c0fd89530
Update Fastfile 2025-03-01 19:57:55 -04:00
Marcos Rodriguez Vélez
d14b4265f8
Update build-ios-release-pullrequest.yml 2025-03-01 15:41:07 -04:00
Marcos Rodriguez Velez
307e950d15 OPS: Downgrade fastlane 2025-03-01 15:30:21 -04:00
Marcos Rodriguez Velez
10f145d012 wip 2025-03-01 15:21:18 -04:00
Marcos Rodriguez Velez
88b8274758 Update WalletTransactions.tsx 2025-03-01 15:06:57 -04:00
Marcos Rodriguez Velez
a65776933d Update WalletTransactions.tsx 2025-03-01 15:06:47 -04:00
Marcos Rodriguez Velez
05491387ff Update WalletTransactions.tsx 2025-03-01 14:36:42 -04:00
Marcos Rodriguez Velez
e23f233f25 Merge branch 'master' into headr 2025-03-01 14:24:50 -04:00
Marcos Rodriguez Vélez
7eb420c561
Update build-ios-release-pullrequest.yml 2025-03-01 13:41:18 -04:00
Marcos Rodriguez Velez
96e553f3d5 Update WalletTransactions.tsx 2025-03-01 13:21:50 -04:00
Marcos Rodriguez Velez
2f3cf1b4e9 Update WalletTransactions.tsx 2025-03-01 12:14:52 -04:00
Marcos Rodriguez Velez
6a4392de02 Merge branch 'master' into headr 2025-03-01 12:09:49 -04:00
GLaDOS
62bb33a9ff
Merge pull request #7665 from BlueWallet/androidc
FIX: Android cold open crash
2025-03-01 15:40:15 +00:00
GLaDOS
85cd7b4aed
Merge pull request #7664 from BlueWallet/menuview
DEL: ios-context package
2025-03-01 15:08:37 +00:00
Marcos Rodriguez Velez
9507a48314 Update BitcoinPriceWidget.kt 2025-03-01 10:44:15 -04:00
Marcos Rodriguez Velez
07b93d521d FIX: Android cold open crash 2025-03-01 10:38:33 -04:00
Marcos Rodriguez Velez
7c6bf01372 Merge branch 'menuview' into headr 2025-03-01 10:32:29 -04:00
Marcos Rodriguez Velez
b01aa58e3b DEL: ios-context package
RNMenu covers 95% of use case
2025-03-01 10:17:56 -04:00
Marcos Rodriguez Velez
136dd20f9e REF: View Edit Multisig navigation
Easier to popTo since its just 1  screen
2025-03-01 10:10:00 -04:00
Marcos Rodriguez Velez
0bfeda0d75 Merge branch 'master' into headr 2025-03-01 10:05:23 -04:00
Marcos Rodriguez Velez
1a848328e3 Update project.pbxproj 2025-03-01 10:01:59 -04:00
Marcos Rodriguez Vélez
93e6269611
Update build-ios-release-pullrequest.yml 2025-02-28 21:47:31 -04:00
Marcos Rodriguez Vélez
5f8dbc52d1
Update build-ios-release-pullrequest.yml 2025-02-28 21:45:54 -04:00
Marcos Rodriguez Vélez
e4d3ecba98
Update build-ios-release-pullrequest.yml 2025-02-28 21:42:58 -04:00
Marcos Rodriguez Velez
bbe4449dd9 Update WalletTransactions.tsx 2025-02-28 21:27:30 -04:00
Marcos Rodriguez Velez
bf9087eae6 Merge branch 'master' into headr 2025-02-28 21:16:51 -04:00
GLaDOS
b3ff1b7c3f
Merge pull request #7661 from BlueWallet/menu
FIX: MenuElements for macOS and iPad were not firing on nav 7
2025-02-28 22:49:39 +00:00
Marcos Rodriguez Velez
00dcc25142 wip 2025-02-28 18:19:33 -04:00
GLaDOS
4614c51041
Merge pull request #7657 from BlueWallet/locsync20
fix: sync language files
2025-02-28 22:07:39 +00:00
Marcos Rodriguez Velez
8d694ceb7b Merge branch 'master' into headr 2025-02-28 18:00:59 -04:00
Marcos Rodriguez Velez
49f6068b21 FIX: MenuElements for macOS and iPad were not firing on nav 7 2025-02-28 17:57:10 -04:00
Ivan Vershigora
be8437e107 fix: sync language files 2025-02-28 13:59:06 +00:00
GLaDOS
cc71dfce8c
Merge pull request #7658 from BlueWallet/targets
fix: on SendDetails screen move targets creation out of options loop
2025-02-28 13:03:03 +00:00
GLaDOS
2e1f20c080
Merge pull request #7659 from BlueWallet/send-deps
fix: remove eslint-ignore on SendDetails screen
2025-02-28 13:03:00 +00:00
Ivan Vershigora
11dceb19fa
fix: remove eslint-ignore on SendDetails screen 2025-02-28 12:15:40 +00:00
Ivan Vershigora
7bb3dd6aef
fix: on SendDetails screen move targets creation out of options loop 2025-02-28 11:52:06 +00:00
GLaDOS
7e7492d314
Merge pull request #7656 from BlueWallet/fix-7653
FIX: crash when scanning invoice with both ln and onchain address (cl…
2025-02-28 11:33:16 +00:00
GLaDOS
c4b1e67f9d
Merge pull request #7648 from BlueWallet/fix-tx-list-screen-update
FIX: transactions list screen would not always update with new transactions
2025-02-28 11:14:54 +00:00
GLaDOS
79f624e906
Merge pull request #7652 from BlueWallet/ref-android-startup-time
REF: improve startup time
2025-02-28 11:14:51 +00:00
overtorment
fabfc5c156 FIX: crash when scanning invoice with both ln and onchain address (closes #7653) 2025-02-28 10:38:23 +00:00
Overtorment
a3d234bee1
Update build.gradle 2025-02-28 09:49:35 +00:00
Marcos Rodriguez Velez
850ac2c653 Merge branch 'master' into scr 2025-02-27 23:25:26 -04:00
GLaDOS
1c26cb420e
Merge pull request #7651 from BlueWallet/scanmac
FIX: Restore camera access to mac app
2025-02-28 01:09:08 +00:00
Marcos Rodriguez Velez
0f23c4d0a7 Merge branch 'master' into headr 2025-02-27 20:10:41 -04:00
Marcos Rodriguez Velez
9adef2b3c1 Update CameraScreen.tsx 2025-02-27 20:10:01 -04:00
Marcos Rodriguez Velez
a9b003e762 FIX:pacakge hash 2025-02-27 20:06:34 -04:00
Marcos Rodriguez Velez
37b03b12e7 Update CameraScreen.tsx 2025-02-27 19:28:35 -04:00
Marcos Rodriguez Velez
4e7c5a28ae Merge branch 'master' into scr 2025-02-27 15:31:39 -04:00
Marcos Rodriguez Velez
9766d2387a Merge branch 'master' into scanmac 2025-02-27 15:30:24 -04:00
GLaDOS
3939ef32f9
Merge pull request #7644 from BlueWallet/renovate/react-native-community-cli-15.x
Update dependency @react-native-community/cli to v15.1.3
2025-02-27 19:20:48 +00:00
Marcos Rodriguez Vélez
17a5a78fd8
Merge branch 'master' into scanmac 2025-02-27 15:10:45 -04:00
overtorment
f7d673d93b REF: improve startup time 2025-02-27 19:06:39 +00:00
GLaDOS
dc3e88c005
Merge pull request #7649 from BlueWallet/translations_loc-en-json--master_es_419
Updates for file loc/en.json in es_419
2025-02-27 18:41:41 +00:00
overtorment
85cb6c1287 FIX: transactions list screen would not always update with new transactions 2025-02-27 18:41:29 +00:00
Marcos Rodriguez Velez
8488dfb9e7 wip 2025-02-27 14:15:40 -04:00
Marcos Rodriguez Velez
3dde81f3a8 Update CameraScreen.tsx 2025-02-27 14:03:42 -04:00
Marcos Rodriguez Velez
a0dc0a31e7 FIX: Restore camera access to mac app 2025-02-27 13:18:41 -04:00
GLaDOS
2dc26ac26a
Merge pull request #7650 from BlueWallet/fix-ln-refill-address
FIX: when pressing refill on lightning wallet, address would not subs…
2025-02-27 16:44:13 +00:00
overtorment
b881370f83 FIX: when pressing refill on lightning wallet, address would not subsitute 2025-02-27 15:54:48 +00:00
transifex-integration[bot]
4f31aff503
Translate loc/en.json in es_419
100% reviewed source file: 'loc/en.json'
on 'es_419'.
2025-02-27 14:03:45 +00:00
Marcos Rodriguez Velez
2ee13dcf4f Merge branch 'master' into headr 2025-02-26 00:48:33 -04:00
Marcos Rodriguez Velez
bf65d2d302 Merge branch 'master' into scr 2025-02-26 00:27:54 -04:00
renovate[bot]
3599ef50ad
Update dependency @react-native-community/cli to v15.1.3 2025-02-26 03:18:23 +00:00
GLaDOS
80fb4a74a8
Merge pull request #7641 from BlueWallet/renovate/react-native-screens-4.x
Update dependency react-native-screens to v4.9.1
2025-02-26 03:11:45 +00:00
Marcos Rodriguez Vélez
4f326452ab
Merge branch 'master' into renovate/react-native-screens-4.x 2025-02-25 22:26:36 -04:00
Marcos Rodriguez Velez
37a88fd60d Update ImportWallet.tsx 2025-02-25 22:24:33 -04:00
Marcos Rodriguez Velez
3fcf3c0840 FIX: Disable screen protect until it supports nav 7. Fix speed issue exposed by disabling it 2025-02-25 22:24:31 -04:00
GLaDOS
685332ce22
Merge pull request #7636 from BlueWallet/cache
OPS: Remove all cache when building from master.
2025-02-25 20:08:13 +00:00
Marcos Rodriguez Velez
0892332d23 OPS: Version bump 2025-02-25 14:43:15 -04:00
Marcos Rodriguez Velez
11cb7dbed5 Update Podfile.lock 2025-02-25 14:34:27 -04:00
GLaDOS
7205f70c30
Merge pull request #7640 from BlueWallet/toralert
FIX: Attempts to connect without Orbot would not indicate errors
2025-02-25 17:01:10 +00:00
renovate[bot]
aa9e647c28
Update dependency react-native-screens to v4.9.1 2025-02-25 16:54:06 +00:00
GLaDOS
8439ff9893
Merge pull request #7639 from BlueWallet/renovate/lodev09-react-native-true-sheet-digest
Update @lodev09/react-native-true-sheet digest to 5945184
2025-02-25 16:43:37 +00:00
GLaDOS
f83476428e
Merge pull request #7638 from BlueWallet/fix-request-camera-permish
FIX: request camera permission
2025-02-25 16:31:30 +00:00
Marcos Rodriguez Velez
4be2668c81 FIX: Attempts to connect without Orbot would not indicate errors 2025-02-25 11:39:56 -04:00
renovate[bot]
867581003c
Update @lodev09/react-native-true-sheet digest to 5945184 2025-02-25 15:39:31 +00:00
GLaDOS
3f1ea9432b
Merge pull request #7633 from BlueWallet/elecf
FIX: Dont disable save button by isLoading as it can mean client is n…
2025-02-25 15:26:28 +00:00
GLaDOS
2bcbe9903e
Merge pull request #7635 from BlueWallet/renovate/react-native-svg-15.x
Update dependency react-native-svg to v15.11.2
2025-02-25 15:26:23 +00:00
overtorment
7963083237 FIX: request camera permission 2025-02-25 15:24:43 +00:00
GLaDOS
b7e03c1ed1
Merge pull request #7629 from BlueWallet/wrongonpres
FIX: Wrongf onpress
2025-02-25 14:43:47 +00:00
GLaDOS
b74fb5f389
Merge pull request #7630 from BlueWallet/macw
REF: Reuse existing component
2025-02-25 14:22:05 +00:00
Marcos Rodriguez Velez
ddd141fc11 Update package.json 2025-02-25 09:41:46 -04:00
GLaDOS
d9a70f5879
Merge pull request #7608 from BlueWallet/Carousel-on-the-main-screen-does-not-working-from-the-first-go
FIX: Carousel on the main screen does not working from the first go
2025-02-25 10:20:40 +00:00
Marcos Rodriguez Velez
e645c911d7 OPS: Version bump 2025-02-24 21:37:56 -04:00
Marcos Rodriguez Velez
06fbb8c945 OPS: Remove all cache when building from master. 2025-02-24 21:36:20 -04:00
Marcos Rodriguez Velez
c008a644cc Merge branch 'master' into Carousel-on-the-main-screen-does-not-working-from-the-first-go 2025-02-24 21:34:56 -04:00
renovate[bot]
ab7e7cf1d5
Update dependency react-native-svg to v15.11.2 2025-02-24 23:19:56 +00:00
GLaDOS
d38968086e
Merge pull request #7626 from BlueWallet/listener
FIX: iOS runtime crash & update screens package
2025-02-24 13:13:56 +00:00
GLaDOS
787fcb797d
Merge pull request #7628 from BlueWallet/refactor-bring-back-scan-qr-helper
refactor: bring back scan qr helper
2025-02-24 10:17:26 +00:00
Marcos Rodriguez Velez
599b5d3b60 REF: Use new helpers for animation. RemoveonPressout 2025-02-24 02:41:00 -04:00
Marcos Rodriguez Velez
c3f7d5b184 Merge branch 'master' into Carousel-on-the-main-screen-does-not-working-from-the-first-go 2025-02-24 02:40:03 -04:00
Marcos Rodriguez Velez
3423730a41 Update WalletsCarousel.tsx 2025-02-24 02:39:51 -04:00
Marcos Rodriguez Velez
2bb7b0c53f REF: Wallet carousel item animation to use new helperds 2025-02-24 02:39:28 -04:00
Marcos Rodriguez Velez
b0a7053fc0 Merge branch 'master' into headr 2025-02-24 02:21:59 -04:00
GLaDOS
a8c0da4768
Merge pull request #7632 from BlueWallet/renovate/react-native-device-info-14.x
Update dependency react-native-device-info to v14.0.4
2025-02-24 05:04:33 +00:00
Marcos Rodriguez Velez
b357053e65 FIX: Dont disable save button by isLoading as it can mean client is not connected 2025-02-24 00:54:40 -04:00
Marcos Rodriguez Velez
5b20ac352e Update WalletTransactions.tsx 2025-02-24 00:26:15 -04:00
renovate[bot]
8c2640e5a9
Update dependency react-native-device-info to v14.0.4 2025-02-24 03:52:24 +00:00
Marcos Rodriguez Velez
a6575b7b73 Merge branch 'master' into macw 2025-02-23 23:02:37 -04:00
Marcos Rodriguez Velez
db58bcf70d Update ImportWallet.tsx 2025-02-23 23:02:34 -04:00
Marcos Rodriguez Velez
b8d1d686f0 Merge branch 'master' into headr 2025-02-23 23:00:03 -04:00
Marcos Rodriguez Velez
5d088a67c1 REF: Wallet tranaction header animation 2025-02-23 22:59:22 -04:00
GLaDOS
fa43d03a36
Merge pull request #7627 from BlueWallet/translations_loc-en-json--master_pl
Updates for file loc/en.json in pl
2025-02-24 01:39:35 +00:00
Marcos Rodriguez Velez
5a7fb86742 Update addMultisigStep2.js 2025-02-23 21:16:38 -04:00
Marcos Rodriguez Velez
4ff759b538 wip 2025-02-23 21:15:10 -04:00
Marcos Rodriguez Velez
0b33af59f0 Update TooltipMenu.tsx 2025-02-23 21:05:45 -04:00
Marcos Rodriguez Velez
f9d8594509 wip 2025-02-23 20:53:46 -04:00
Marcos Rodriguez Velez
7f97c340f8 REF: Reuse existing component 2025-02-23 18:18:12 -04:00
Marcos Rodriguez Velez
925dc17042 FIX: Wrongf onpress 2025-02-23 17:35:19 -04:00
overtorment
164f16657a refactor: bring back scan qr helper 2025-02-23 21:09:14 +00:00
overtorment
34db010bde REF: scanQr to TS 2025-02-23 18:57:25 +00:00
transifex-integration[bot]
7ccf19212f
Translate loc/en.json in pl
100% reviewed source file: 'loc/en.json'
on 'pl'.
2025-02-23 15:32:37 +00:00
Marcos Rodriguez Velez
133312e065 FIX: iOS runtime crash & update screens package
Lower the file changes in Receive modal PR
2025-02-23 08:31:55 -04:00
Marcos Rodriguez Vélez
46a78e8dfb
Update AppDelegate.mm 2025-02-22 17:32:14 -04:00
GLaDOS
e5ab5f6565
Merge pull request #7566 from BlueWallet/detailsitem
ADD: TX Details action action
2025-02-22 20:11:05 +00:00
GLaDOS
ad71dccd72
Merge pull request #7620 from BlueWallet/usede
REF: use debounce in wallet transactions to avoid rapid reattempts
2025-02-22 17:28:54 +00:00
Marcos Rodriguez Velez
159a8b2e16 Merge branch 'master' into usede 2025-02-22 12:40:23 -04:00
Marcos Rodriguez Velez
d3fd8c050f Update WalletTransactions.tsx 2025-02-22 12:40:17 -04:00
GLaDOS
52b3cb9b34
Merge pull request #7622 from BlueWallet/swift
REF: ObjC emitters to Swift
2025-02-22 15:53:52 +00:00
Marcos Rodriguez Velez
98b643a023 Update WalletTransactions.tsx 2025-02-22 11:49:53 -04:00
Marcos Rodriguez Velez
0eb3393f1f wip 2025-02-22 09:38:12 -04:00
Marcos Rodriguez Velez
47a08448a2 REF: ObjC emitters to Swift 2025-02-22 09:16:25 -04:00
Marcos Rodriguez Velez
254550d92e Update CommonToolTipActions.ts 2025-02-22 09:00:56 -04:00
Marcos Rodriguez Velez
9ed7ceabdb Update WalletTransactions.tsx 2025-02-22 08:37:46 -04:00
Marcos Rodriguez Velez
6afc6624eb wip 2025-02-21 23:14:00 -04:00
Marcos Rodriguez Velez
2317d0a4cf Update WalletTransactions.tsx 2025-02-21 22:05:28 -04:00
Marcos Rodriguez Velez
dcd2023815 REF: use debounce in wallet transactions to avoid rapid reattempts 2025-02-21 21:57:37 -04:00
Marcos Rodriguez Vélez
4b37eaba98
Merge branch 'master' into detailsitem 2025-02-21 21:42:56 -04:00
Marcos Rodriguez Velez
8619e80dc0 OPS: Move nav devtools to packages 2025-02-21 18:20:15 +00:00
GLaDOS
e06d1ce57c
Merge pull request #7614 from BlueWallet/PSBT---Saved-at-a-not-accessible-location-on-Android-13-#7600
FIX: PSBT - Saved at a not accessible location on Android 13 #7600
2025-02-21 17:09:44 +00:00
Overtorment
157bd3529a
Merge branch 'master' into detailsitem 2025-02-21 16:56:49 +00:00
GLaDOS
e92eb7eae0
Merge pull request #7564 from BlueWallet/contr
FIX: Lndhub lacked timeouts
2025-02-21 16:50:53 +00:00
GLaDOS
b9227cdbc6
Merge pull request #7613 from BlueWallet/renovate/react-native-draglist-digest
Update react-native-draglist digest to 8c52785
2025-02-21 02:50:19 +00:00
Marcos Rodriguez Velez
a047c0219e FIX: PSBT - Saved at a not accessible location on Android 13 #7600 2025-02-20 20:59:30 -04:00
renovate[bot]
2ca8eca810
Update react-native-draglist digest to 8c52785 2025-02-21 00:52:43 +00:00
GLaDOS
93f901e94f
Merge pull request #7612 from BlueWallet/renovate/react-native-menu-menu-digest
Update @react-native-menu/menu digest to 038a9c9
2025-02-21 00:43:24 +00:00
Marcos Rodriguez Vélez
670ad6a833
Merge branch 'master' into renovate/react-native-menu-menu-digest 2025-02-20 19:34:58 -04:00
Marcos Rodriguez Vélez
b232a13243
Merge branch 'master' into contr 2025-02-20 19:33:32 -04:00
Marcos Rodriguez Velez
d610063809 Update TransactionListItem.tsx 2025-02-20 19:32:22 -04:00
Marcos Rodriguez Velez
1b328cd130 Merge branch 'master' into detailsitem 2025-02-20 19:32:03 -04:00
Marcos Rodriguez Velez
0b5e640630 Revert "ADD: TX Details action action"
This reverts commit cdd923db7c.
2025-02-20 19:32:01 -04:00
Marcos Rodriguez Velez
527219f697 Update lndHub.ts 2025-02-20 19:30:03 -04:00
GLaDOS
c400771d7a
Merge pull request #7609 from BlueWallet/erro
FIX: Error on send would not scroll to recipient
2025-02-20 23:29:31 +00:00
Marcos Rodriguez Velez
021ed454f1 Merge branch 'master' into contr 2025-02-20 19:29:27 -04:00
GLaDOS
47673a4ae0
Merge pull request #7604 from BlueWallet/addressfixes
Addressfixes
2025-02-20 23:07:08 +00:00
renovate[bot]
7e66e42862
Update @react-native-menu/menu digest to 038a9c9 2025-02-20 23:06:53 +00:00
GLaDOS
e25833f0d3
Merge pull request #7610 from BlueWallet/renovate/lodev09-react-native-true-sheet-digest
Update @lodev09/react-native-true-sheet digest to 0fefdd1
2025-02-20 22:56:00 +00:00
Marcos Rodriguez Velez
0e698069f4 Merge branch 'master' into Carousel-on-the-main-screen-does-not-working-from-the-first-go 2025-02-20 18:32:10 -04:00
Marcos Rodriguez Velez
3a8a7d6da8 Update en.json 2025-02-20 18:09:54 -04:00
Marcos Rodriguez Velez
1b561c8a91 Update lightning-custodian-wallet.ts 2025-02-20 18:09:28 -04:00
Marcos Rodriguez Velez
4b93827b7f Merge branch 'master' into contr 2025-02-20 18:06:15 -04:00
Marcos Rodriguez Velez
3de6976997 Merge branch 'master' into addressfixes 2025-02-20 18:05:33 -04:00
Marcos Rodriguez Velez
9c1be484c1 Update BottomModal.tsx 2025-02-20 18:03:30 -04:00
Marcos Rodriguez Velez
f04b50c58b Merge branch 'master' into renovate/lodev09-react-native-true-sheet-digest 2025-02-20 18:03:22 -04:00
GLaDOS
f974658472
Merge pull request #7596 from BlueWallet/cleanup
FIX: Slight cleanup
2025-02-20 18:37:10 +00:00
Marcos Rodriguez Vélez
208157430f
Merge branch 'master' into erro 2025-02-20 11:32:26 -04:00
Marcos Rodriguez Vélez
b73f04b4e6
Merge branch 'master' into renovate/lodev09-react-native-true-sheet-digest 2025-02-20 11:10:56 -04:00
Marcos Rodriguez Vélez
0429721d66
FIX: Receive close button padding (#7602) 2025-02-20 11:10:39 -04:00
Marcos Rodriguez Vélez
5fb3991cb2
Merge branch 'master' into renovate/lodev09-react-native-true-sheet-digest 2025-02-20 08:45:42 -04:00
GLaDOS
e4093a357d
Merge pull request #7606 from BlueWallet/screens
FIX: UI for scan qr code was not full height when reejcted
2025-02-20 12:29:20 +00:00
renovate[bot]
fda596211a
Update @lodev09/react-native-true-sheet digest to 0fefdd1 2025-02-19 19:32:38 +00:00
Marcos Rodriguez Velez
fb1a30191d Update SendDetails.tsx 2025-02-19 09:57:59 -04:00
Marcos Rodriguez Velez
2f3ac6e972 wip 2025-02-19 09:20:27 -04:00
Marcos Rodriguez Velez
8719ded414 wip 2025-02-19 09:11:01 -04:00
Marcos Rodriguez Velez
680d9d4495 Merge branch 'master' into erro 2025-02-19 09:03:23 -04:00
Marcos Rodriguez Velez
45f095badf REF: Refactor helper 2025-02-19 09:00:49 -04:00
Marcos Rodriguez Velez
66bb0b0e1c Merge branch 'master' into Carousel-on-the-main-screen-does-not-working-from-the-first-go 2025-02-19 08:55:55 -04:00
GLaDOS
33acf30d68
Merge pull request #7605 from BlueWallet/scand
OPS: Move rn devtools to devdep
2025-02-19 11:15:19 +00:00
GLaDOS
2c4bb95475
Merge pull request #7607 from BlueWallet/addw
FIX:  Close button in Add
2025-02-19 11:15:14 +00:00
Ivan
9435fb769f
Merge branch 'master' into addressfixes 2025-02-19 11:04:08 +00:00
GLaDOS
6321627578
Merge pull request #7603 from BlueWallet/receivescreenswit
FIX: unblock UI as user switches address type
2025-02-19 11:00:14 +00:00
Marcos Rodriguez Velez
18cb2faef6 Update SendDetails.tsx 2025-02-18 22:53:43 -04:00
Marcos Rodriguez Velez
2c68583495 FIX: Error on send would not scroll to recipient 2025-02-18 22:51:17 -04:00
Marcos Rodriguez Velez
f8629e2555 FIX: Carousel on the main screen does not working from the first go 2025-02-18 22:44:02 -04:00
Marcos Rodriguez Velez
6c11e2a5b8 FIX: Close button in Add 2025-02-18 22:34:19 -04:00
Marcos Rodriguez Velez
827c2ad3db FIX: UI for scan qr code was not full height when reejcted 2025-02-18 22:26:51 -04:00
Marcos Rodriguez Velez
4be2bb03be OPS: Move rn devtools to devdep 2025-02-18 22:00:44 -04:00
Marcos Rodriguez Velez
12d8596180 Update WalletAddresses.tsx 2025-02-18 21:55:57 -04:00
Marcos Rodriguez Velez
2a4b14d63e Update AddressItem.tsx 2025-02-18 21:51:01 -04:00
Marcos Rodriguez Velez
062b8844d4 FIX: Address could get fully converted to "..." in some scenarios 2025-02-18 21:36:38 -04:00
Marcos Rodriguez Velez
e4cea4f451 Revert "FIX: unblock UI as user switches address type"
This reverts commit a1d5941a75.
2025-02-18 21:25:03 -04:00
Marcos Rodriguez Velez
82f13fbded Merge branch 'nav7frixes' into receivescreenswit 2025-02-18 13:59:31 -04:00
Marcos Rodriguez Velez
a1d5941a75 FIX: unblock UI as user switches address type 2025-02-18 13:58:55 -04:00
Marcos Rodriguez Velez
4d9a2f79f9 Update WalletsCarousel.tsx 2025-02-18 13:42:18 -04:00
Marcos Rodriguez Velez
fef62f2fd8 Merge branch 'master' into cleanup 2025-02-18 13:38:30 -04:00
Marcos Rodriguez Velez
c1adabb021 REF: Error messasge 2025-02-18 13:36:07 -04:00
Marcos Rodriguez Velez
b42290ceee REF: Rename util 2025-02-18 13:27:35 -04:00
Marcos Rodriguez Velez
0aa2ed20f3 Merge branch 'master' into contr 2025-02-18 13:18:14 -04:00
Marcos Rodriguez Velez
c092ea4523 FIX: Receive close button padding 2025-02-18 13:15:07 -04:00
Marcos Rodriguez Vélez
d338f813cb
OPS: Upgrade RNav 7 (#7419) 2025-02-17 15:24:05 -04:00
Marcos Rodriguez Velez
238ee798ab Update lnurl.ts 2025-02-16 22:26:34 -04:00
Marcos Rodriguez Velez
bbf746b011 REF: fetch to timeoutFetch 2025-02-16 22:26:03 -04:00
Marcos Rodriguez Velez
fdd2b66d8e Merge branch 'master' into contr 2025-02-16 22:23:47 -04:00
Marcos Rodriguez Velez
44fc028159 Create fetch.ts 2025-02-16 22:23:44 -04:00
GLaDOS
632500b734
Merge pull request #7555 from BlueWallet/hofffix
FIX: Handoff type wasnt being passed
2025-02-16 10:54:02 +00:00
Marcos Rodriguez Vélez
a4a513f703
Merge branch 'master' into cleanup 2025-02-15 16:13:51 -04:00
GLaDOS
39b141507c
Merge pull request #7593 from BlueWallet/tor
FIX: Allow onion preferred as user could be on Orbot
2025-02-15 18:40:43 +00:00
Marcos Rodriguez Velez
dde4520094 FIX: Slight cleanup 2025-02-15 14:07:37 -04:00
GLaDOS
a7843e127f
Merge pull request #7595 from BlueWallet/lighningse
REF: Reuse AddressInput component
2025-02-15 16:57:49 +00:00
Marcos Rodriguez Velez
f4125cb1e9 Merge branch 'master' into lighningse 2025-02-14 21:18:16 -04:00
GLaDOS
115b0a2a4f
Merge pull request #7594 from BlueWallet/wrap
REF: Wrap BlueElecrum promises in try catch
2025-02-14 20:31:10 +00:00
GLaDOS
7035bec229
Merge pull request #7590 from BlueWallet/translations_loc-en-json--master_pl
Updates for file loc/en.json in pl
2025-02-14 19:32:39 +00:00
Marcos Rodriguez Velez
80ef3252a1 REF: Wrap BlueElecrum promises in try catch 2025-02-14 15:24:20 -04:00
Marcos Rodriguez Velez
6cd6079493 FIX: Allow onion preferred as user could be on Orbot 2025-02-14 14:58:58 -04:00
GLaDOS
9a8158a384
Merge pull request #7529 from BlueWallet/psb
ADD: Test electrum connection prior to saving
2025-02-14 18:54:21 +00:00
GLaDOS
9976734665
Merge pull request #7591 from BlueWallet/allert
FIX: Confirm wallet delete alert would poptotop regardless of confirm…
2025-02-14 10:12:46 +00:00
Marcos Rodriguez Velez
1c15ae0a0c REF: Reuse AddressInput component 2025-02-13 18:38:37 -04:00
Marcos Rodriguez Velez
64d8238872 FIX: Confirm wallet delete alert would poptotop regardless of confirmation 2025-02-13 18:23:47 -04:00
transifex-integration[bot]
336785e1a0
Translate loc/en.json in pl
100% reviewed source file: 'loc/en.json'
on 'pl'.
2025-02-13 15:07:46 +00:00
GLaDOS
d68b806b60
Merge pull request #7586 from BlueWallet/tipbox
REF: TIpBox
2025-02-12 23:07:43 +00:00
GLaDOS
1acf4c9af2
Merge pull request #7587 from BlueWallet/rn
OPS: RN version bump
2025-02-12 23:07:40 +00:00
Marcos Rodriguez Velez
2d51238d6f OPS: RN version bump 2025-02-12 14:03:15 -04:00
Marcos Rodriguez Velez
3b90c49d79 REF: TIpBox 2025-02-12 13:56:12 -04:00
GLaDOS
226d499603
Merge pull request #7583 from BlueWallet/log
FIX: Remove annoying warning
2025-02-12 14:36:47 +00:00
GLaDOS
f155b6b577
Merge pull request #7585 from BlueWallet/locsync19
fix: sync language files
2025-02-12 13:11:26 +00:00
Ivan Vershigora
4be7f78be8
fix: sync language files 2025-02-12 11:21:11 +00:00
GLaDOS
5d81a4cf57
Merge pull request #7580 from BlueWallet/ale
FIX: Alert failed to show in some scenarios
2025-02-12 09:07:26 +00:00
GLaDOS
1b11200a0a
Merge pull request #7582 from BlueWallet/SIGN
FIX: handlePsbtSign();
2025-02-12 09:07:22 +00:00
Marcos Rodriguez Velez
f229beb5e0 FIX: Better erro handling for refreshAllWalletTransactions 2025-02-12 00:42:38 -04:00
Marcos Rodriguez Velez
e176783a3f FIX: Remove annoying warning 2025-02-12 00:23:11 -04:00
Marcos Rodriguez Velez
5c7460d6b1 Update SendDetails.tsx 2025-02-12 00:09:08 -04:00
Marcos Rodriguez Velez
571b056854 FIX: handlePsbtSign(); 2025-02-12 00:08:05 -04:00
Marcos Rodriguez Vélez
54db4b366e
FIX:Indicator color (#7581) 2025-02-11 20:39:48 -04:00
Marcos Rodriguez Velez
02dd22b8d4 Update StorageProvider.tsx 2025-02-11 17:55:46 -04:00
Marcos Rodriguez Velez
8b94a9db10 FIX: Alert failed to show in some scenarios 2025-02-11 17:55:06 -04:00
GLaDOS
175a5f27aa
Merge pull request #7579 from BlueWallet/translations_loc-en-json--master_es_419
Updates for file loc/en.json in es_419
2025-02-11 20:07:40 +00:00
GLaDOS
907e54938c
Merge pull request #7578 from BlueWallet/scanningspeed
FIX: Scanning speed was limited by unused prop
2025-02-11 19:56:20 +00:00
transifex-integration[bot]
c43d36d84d
Translate loc/en.json in es_419
100% reviewed source file: 'loc/en.json'
on 'es_419'.
2025-02-11 19:09:44 +00:00
Marcos Rodriguez Velez
f900f46deb FIX: Scanning speed was limited by unused prop 2025-02-11 15:01:30 -04:00
GLaDOS
472b6c97ff
Merge pull request #7542 from BlueWallet/race
fix: race condition error in HD wallet fetchTransactions
2025-02-10 19:43:25 +00:00
GLaDOS
ae80cb9118
Merge pull request #7577 from BlueWallet/notifre
FIX: Alert the user if notifications deregister failed
2025-02-10 19:06:25 +00:00
GLaDOS
f389af09ba
Merge pull request #7536 from BlueWallet/vvi
DEL: File not used
2025-02-10 18:56:25 +00:00
Ivan Vershigora
fc5eccfcd4 fix: race condition error in HD wallet fetchTransactions 2025-02-10 18:51:18 +00:00
Marcos Rodriguez Velez
307306f5ec Merge branch 'master' into notifre 2025-02-10 14:24:27 -04:00
GLaDOS
73081033ed
Merge pull request #7576 from BlueWallet/fco
ADD: Single button container should be round as per design
2025-02-10 15:49:13 +00:00
Marcos Rodriguez Vélez
8b531350af
Update WalletDetails.tsx 2025-02-10 10:50:07 -04:00
Marcos Rodriguez Velez
4c09a52e02 wip 2025-02-10 10:07:28 -04:00
Marcos Rodriguez Velez
879f4f4081 FIX: Alert the user if notifications deregister failed 2025-02-10 09:49:01 -04:00
Marcos Rodriguez Velez
ea870729d6 ADD: Single button container should be round as per design 2025-02-09 23:52:37 -04:00
Marcos Rodriguez Velez
b8a8986a8e OPS:Version bump 2025-02-09 14:27:27 -04:00
Marcos Rodriguez Velez
caf41400a0 Update WalletTransactions.tsx 2025-02-09 12:03:39 -04:00
GLaDOS
30ed2a3d96
Merge pull request #7574 from BlueWallet/grad
FIX: Lower the height of the background behind refresh controller
2025-02-09 15:27:26 +00:00
Marcos Rodriguez Velez
a8374ab25b Update WalletTransactions.tsx 2025-02-09 10:35:47 -04:00
Marcos Rodriguez Velez
d3fd15dcf8 Update WalletTransactions.tsx 2025-02-09 09:46:44 -04:00
Marcos Rodriguez Velez
e8c181359d FIX: Lower the height of the background behind refresh controller 2025-02-09 09:16:09 -04:00
GLaDOS
307f6881c9
Merge pull request #7573 from BlueWallet/ELE
FIX: PReferred server would not save if it shared host name with existing one
2025-02-09 12:34:35 +00:00
Marcos Rodriguez Velez
e38e70bb0b FIX: PReferred server would not save if it shared host name 2025-02-08 22:23:03 -04:00
GLaDOS
92fda5d969
Merge pull request #7571 from BlueWallet/psbt
FIX: Can not sign exported psbt in ColdCard  #7490
2025-02-09 02:12:06 +00:00
Marcos Rodriguez Velez
e69c105ccf Update psbtMultisig.js 2025-02-08 20:59:51 -04:00
Marcos Rodriguez Velez
002efdc4e7 wip 2025-02-08 19:30:33 -04:00
Marcos Rodriguez Velez
63ab4da34b Update psbtMultisigQRCode.js 2025-02-08 18:46:08 -04:00
Marcos Rodriguez Velez
c2ff24591e Update psbtMultisig.js 2025-02-08 18:43:23 -04:00
Marcos Rodriguez Velez
19dd1047d1 Update psbtMultisig.js 2025-02-08 18:40:19 -04:00
Marcos Rodriguez Velez
38d92a7a5c Update psbtMultisigQRCode.js 2025-02-08 18:38:52 -04:00
Marcos Rodriguez Velez
59f2835cb8 Merge branch 'master' into psbt 2025-02-08 18:27:16 -04:00
GLaDOS
9863dfd47b
Merge pull request #7572 from BlueWallet/foc
FIX: Crash when pressing MAX on send details
2025-02-08 22:27:00 +00:00
Marcos Rodriguez Velez
70e32c9d69 Update psbtMultisigQRCode.js 2025-02-08 18:25:16 -04:00
Marcos Rodriguez Velez
2b393ba997 numbers 2025-02-08 18:15:17 -04:00
Marcos Rodriguez Velez
88a1ec4260 wip 2025-02-08 18:02:26 -04:00
Marcos Rodriguez Velez
5eabded72b Update SendDetails.tsx 2025-02-08 03:00:10 -04:00
Marcos Rodriguez Vélez
d957ee7197
Update SendDetails.tsx 2025-02-08 01:12:24 -04:00
Marcos Rodriguez Velez
a4df48a0c5 wip 2025-02-07 20:14:02 -04:00
Marcos Rodriguez Velez
6d7e48eb1a wip 2025-02-07 20:11:40 -04:00
Marcos Rodriguez Velez
0481c8d6a9 Update SendDetails.tsx 2025-02-07 20:07:08 -04:00
Marcos Rodriguez Velez
85e47ac83d Update psbtMultisigQRCode.js 2025-02-07 20:03:19 -04:00
Marcos Rodriguez Vélez
0d64347813
Update en.json 2025-02-07 18:01:22 -04:00
Marcos Rodriguez Velez
4849042dc6 wip 2025-02-07 13:16:41 -04:00
GLaDOS
d2e186bbf7
Merge pull request #7568 from BlueWallet/Nav
FIX: Dont run checks in ScanQrcode
2025-02-07 11:37:29 +00:00
Marcos Rodriguez Velez
588da24f0b FIX: Crash when pressing MAX on send details 2025-02-06 23:45:43 -04:00
Marcos Rodriguez Velez
a8858833ef FIX: Can not sign exported psbt in ColdCard #7490 2025-02-06 23:43:48 -04:00
GLaDOS
06ec5feb4b
Merge pull request #7570 from BlueWallet/translations_loc-en-json--master_fr_FR
Updates for file loc/en.json in fr_FR
2025-02-06 01:36:42 +00:00
transifex-integration[bot]
e9c3e3143f
Translate loc/en.json in fr_FR
100% reviewed source file: 'loc/en.json'
on 'fr_FR'.
2025-02-05 16:54:13 +00:00
GLaDOS
83f545ed4b
Merge pull request #7565 from BlueWallet/qrui
FIX: ScanQR UI fixes
2025-02-05 15:39:33 +00:00
GLaDOS
7b8b3a0be2
Merge pull request #7567 from BlueWallet/swipe
FIX: Swiping is now easier
2025-02-05 15:39:29 +00:00
junderw
6dde0c4b4e Add support for importing minikeys (Casascius Coin, Satori Coin etc.) 2025-02-05 08:41:08 +00:00
Marcos Rodriguez Vélez
e810baf9c8
FIX: Dont run checks in ScanQrcode 2025-02-05 01:04:30 -04:00
Marcos Rodriguez Vélez
facd7b7783
Update ScanQRCode.js 2025-02-05 00:57:02 -04:00
Marcos Rodriguez Velez
89e7b23c05 Update PromptPasswordConfirmationModal.tsx 2025-02-05 00:35:52 -04:00
Marcos Rodriguez Velez
56c983e1dc Update ManageWalletsListItem.tsx 2025-02-05 00:27:16 -04:00
Marcos Rodriguez Velez
016e9f4214 Update ManageWalletsListItem.tsx 2025-02-04 23:33:51 -04:00
Marcos Rodriguez Velez
c932d0da5a FIX: Swiping is now easier 2025-02-04 23:14:00 -04:00
Marcos Rodriguez Velez
cdd923db7c ADD: TX Details action action 2025-02-04 22:32:04 -04:00
Marcos Rodriguez Velez
4611c46d1e FIX: ScanQR UI fixes 2025-02-04 21:54:24 -04:00
GLaDOS
59c9edeebd
Merge pull request #7561 from BlueWallet/txheader
REF: Wallet info header in Transactions
2025-02-04 17:33:37 +00:00
GLaDOS
05eff5f2b4
Merge pull request #7554 from BlueWallet/import
FIX: Import flow was holding on to previous reference
2025-02-04 17:22:30 +00:00
GLaDOS
8894bcf965
Merge pull request #7556 from BlueWallet/Issue---Function-to-broadcast-transaction-from-.final-PSBT-not-available-#7551
FIX: Issue - Function to broadcast transaction from *.final PSBT not …
2025-02-04 17:22:25 +00:00
Marcos Rodriguez Velez
a6306c53d8 FIX: Lndhub lacked timeouts 2025-02-03 01:29:48 -04:00
Marcos Rodriguez Velez
d05d51237a Update WalletTransactions.tsx 2025-02-02 20:49:03 -04:00
Marcos Rodriguez Vélez
5c70faf17d
Update WalletTransactions.tsx 2025-02-02 18:20:57 -04:00
Marcos Rodriguez Velez
18c5e38d6c wip 2025-02-02 16:51:32 -04:00
Marcos Rodriguez Velez
ca1be7d443 REF: Wallet info header in Transactions 2025-02-02 15:21:12 -04:00
GLaDOS
ec027a12df
Merge pull request #7560 from BlueWallet/translations_loc-en-json--master_de_DE
Updates for file loc/en.json in de_DE
2025-02-02 17:07:56 +00:00
transifex-integration[bot]
6b013e5bb7
Translate loc/en.json in de_DE
100% reviewed source file: 'loc/en.json'
on 'de_DE'.
2025-02-02 09:52:41 +00:00
Marcos Rodriguez Velez
950848181e Update SendDetails.tsx 2025-02-01 21:22:24 -04:00
Marcos Rodriguez Velez
559468b221 FIX: Issue - Function to broadcast transaction from *.final PSBT not available #7551 2025-02-01 21:08:35 -04:00
Marcos Rodriguez Velez
3adb90abff Update create.js 2025-02-01 10:51:37 -04:00
Marcos Rodriguez Vélez
6765dd7246
Update useHandoffListener.ts 2025-02-01 10:29:05 -04:00
Marcos Rodriguez Velez
6698645f48 Update SendDetails.tsx 2025-01-31 19:28:13 -04:00
Marcos Rodriguez Vélez
cdd76db18f
Merge branch 'master' into import 2025-01-31 19:19:15 -04:00
Marcos Rodriguez Velez
715991b106 FIX: Handoff type wasnt being passed 2025-01-31 18:49:22 -04:00
Marcos Rodriguez Velez
7882716c73 Update package-lock.json 2025-01-31 18:21:06 -04:00
Marcos Rodriguez Velez
9f912c51ed Merge branch 'master' into import 2025-01-31 18:14:35 -04:00
Marcos Rodriguez Velez
8ae9ac6155 OPS: Version bump 2025-01-31 18:13:54 -04:00
Marcos Rodriguez Velez
ea4acc2556 FIX: Import flow was holding on to previous reference 2025-01-31 18:13:06 -04:00
Marcos Rodriguez Velez
e1202c6854 Update ElectrumSettings.tsx 2025-01-26 12:29:52 -04:00
Marcos Rodriguez Velez
021a1fd352 Merge branch 'master' into psb 2025-01-26 12:28:13 -04:00
Marcos Rodriguez Velez
8f26859f76 DEL: File not used 2025-01-26 10:48:10 -04:00
Marcos Rodriguez Velez
f6a6d7c41e ADD: Test electrum connection prior to saving 2025-01-24 00:22:20 -04:00
197 changed files with 6240 additions and 4408 deletions

View file

@ -47,6 +47,24 @@
"device": "emulator", "device": "emulator",
"app": "android.debug" "app": "android.debug"
}, },
"android.debug.device": {
"device": {
"device": {
"adbName": ".*"
},
"type": "android.attached"
},
"app": "android.debug"
},
"android.release.device": {
"device": {
"device": {
"adbName": ".*"
},
"type": "android.attached"
},
"app": "android.release"
},
"android.release": { "android.release": {
"device": "emulator", "device": "emulator",
"app": "android.release" "app": "android.release"

View file

@ -22,12 +22,40 @@ jobs:
branch_name: ${{ steps.get_latest_commit_details.outputs.branch_name }} branch_name: ${{ steps.get_latest_commit_details.outputs.branch_name }}
env: env:
APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_ID: ${{ secrets.APPLE_ID }}
MATCH_READONLY: "true"
steps: steps:
- name: Checkout Project - name: Checkout Project
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
fetch-depth: 0 # Ensures the full Git history is available fetch-depth: 0 # Ensures the full Git history is available
- name: Setup Caching
uses: actions/cache@v3
with:
path: |
~/Library/Caches/CocoaPods
ios/Pods
~/.npm
node_modules
vendor/bundle
key: ${{ runner.os }}-ios-${{ hashFiles('**/package-lock.json', '**/Podfile.lock', '**/Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-ios-
- name: Clear All Caches
if: github.ref == 'refs/heads/master'
run: |
echo "Clearing Xcode DerivedData..."
rm -rf ~/Library/Developer/Xcode/DerivedData
echo "Clearing CocoaPods Cache..."
rm -rf ~/Library/Caches/CocoaPods
echo "Clearing npm Cache..."
npm cache clean --force
echo "Clearing Ruby Gems Cache..."
rm -rf ~/.gem
echo "Clearing Bundler Cache..."
rm -rf ~/.bundle/cache
- name: Ensure Correct Branch - name: Ensure Correct Branch
if: github.ref != 'refs/heads/master' if: github.ref != 'refs/heads/master'
@ -67,15 +95,32 @@ jobs:
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 20 node-version: 20
cache: 'npm'
- uses: maxim-lobanov/setup-xcode@v1 - uses: maxim-lobanov/setup-xcode@v1
with: with:
xcode-version: 16.0 xcode-version: latest
- name: Install iOS Simulator Runtime
run: |
echo "Available iOS simulator runtimes:"
xcrun simctl list runtimes
# Try to download the latest iOS 16.x simulator if not present
if (! xcrun simctl list runtimes | grep -q "iOS 16"); then
echo "Installing iOS 16.4 simulator..."
sudo xcode-select -s /Applications/Xcode.app
xcodebuild -downloadPlatform iOS
fi
echo "Available iOS simulator runtimes after install:"
xcrun simctl list runtimes
- name: Set Up Ruby - name: Set Up Ruby
uses: ruby/setup-ruby@v1 uses: ruby/setup-ruby@v1
with: with:
ruby-version: 3.1.6 ruby-version: 3.1.6
bundler-cache: true
- name: Install Dependencies with Bundler - name: Install Dependencies with Bundler
run: | run: |
@ -88,6 +133,7 @@ jobs:
- name: Install CocoaPods Dependencies - name: Install CocoaPods Dependencies
run: | run: |
bundle exec fastlane ios install_pods bundle exec fastlane ios install_pods
echo "CocoaPods dependencies installed successfully"
- name: Generate Build Number Based on Timestamp - name: Generate Build Number Based on Timestamp
id: generate_build_number id: generate_build_number
@ -133,8 +179,26 @@ jobs:
- name: Build App - name: Build App
id: build_app id: build_app
run: | run: |
bundle exec fastlane ios build_app_lane --verbose bundle exec fastlane ios build_app_lane
echo "ipa_output_path=$IPA_OUTPUT_PATH" >> $GITHUB_OUTPUT # Set the IPA output path for future jobs
# Ensure IPA path is set for subsequent steps
if [ -f "./ios/build/ipa_path.txt" ]; then
IPA_PATH=$(cat ./ios/build/ipa_path.txt)
echo "IPA_OUTPUT_PATH=$IPA_PATH" >> $GITHUB_ENV
echo "ipa_output_path=$IPA_PATH" >> $GITHUB_OUTPUT
echo "Found IPA at: $IPA_PATH"
else
echo "Warning: ipa_path.txt not found, trying to locate IPA file manually..."
IPA_PATH=$(find ./ios -name "*.ipa" | head -n 1)
if [ -n "$IPA_PATH" ]; then
echo "IPA_OUTPUT_PATH=$IPA_PATH" >> $GITHUB_ENV
echo "ipa_output_path=$IPA_PATH" >> $GITHUB_OUTPUT
echo "Found IPA at: $IPA_PATH"
else
echo "Error: No IPA file found"
exit 1
fi
fi
- name: Upload Bugsnag Sourcemaps - name: Upload Bugsnag Sourcemaps
if: success() if: success()
@ -142,8 +206,8 @@ jobs:
env: env:
BUGSNAG_API_KEY: ${{ secrets.BUGSNAG_API_KEY }} BUGSNAG_API_KEY: ${{ secrets.BUGSNAG_API_KEY }}
BUGSNAG_RELEASE_STAGE: production BUGSNAG_RELEASE_STAGE: production
PROJECT_VERSION: ${{ needs.build.outputs.project_version }} PROJECT_VERSION: ${{ env.PROJECT_VERSION }}
NEW_BUILD_NUMBER: ${{ needs.build.outputs.new_build_number }} NEW_BUILD_NUMBER: ${{ env.NEW_BUILD_NUMBER }}
- name: Upload Build Logs - name: Upload Build Logs
if: always() if: always()
@ -151,13 +215,32 @@ jobs:
with: with:
name: build_logs name: build_logs
path: ./ios/build_logs/ path: ./ios/build_logs/
retention-days: 7
- name: Verify IPA File Before Upload
run: |
echo "Checking IPA file at: $IPA_OUTPUT_PATH"
if [ -f "$IPA_OUTPUT_PATH" ]; then
echo "✅ IPA file exists"
ls -la "$IPA_OUTPUT_PATH"
else
echo "❌ IPA file not found at: $IPA_OUTPUT_PATH"
echo "Current directory contents:"
find ./ios -name "*.ipa"
exit 1
fi
- name: Upload IPA as Artifact - name: Upload IPA as Artifact
if: success() if: success()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: BlueWallet_${{env.PROJECT_VERSION}}_${{env.NEW_BUILD_NUMBER}}.ipa name: BlueWallet_IPA
path: ${{ env.IPA_OUTPUT_PATH }} # Directly from Fastfile `IPA_OUTPUT_PATH` path: ${{ env.IPA_OUTPUT_PATH }}
retention-days: 7
- name: Delete Temporary Keychain
if: always()
run: bundle exec fastlane ios delete_temp_keychain
testflight-upload: testflight-upload:
needs: build needs: build
@ -177,6 +260,7 @@ jobs:
uses: ruby/setup-ruby@v1 uses: ruby/setup-ruby@v1
with: with:
ruby-version: 3.1.6 ruby-version: 3.1.6
bundler-cache: true
- name: Install Dependencies with Bundler - name: Install Dependencies with Bundler
run: | run: |
@ -186,18 +270,11 @@ jobs:
- name: Download IPA from Artifact - name: Download IPA from Artifact
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: BlueWallet_${{ needs.build.outputs.project_version }}_${{ needs.build.outputs.new_build_number }}.ipa name: BlueWallet_IPA
path: ./ path: ./
- name: Create App Store Connect API Key JSON - name: Create App Store Connect API Key JSON
run: echo '${{ secrets.APP_STORE_CONNECT_API_KEY_CONTENT }}' > ./appstore_api_key.json run: echo '${{ secrets.APP_STORE_CONNECT_API_KEY_CONTENT }}' > ./appstore_api_key.json
- name: Verify IPA File Download
run: |
echo "Current directory:"
pwd
echo "Files in current directory:"
ls -la ./
- name: Set IPA Path Environment Variable - name: Set IPA Path Environment Variable
run: echo "IPA_OUTPUT_PATH=$(pwd)/BlueWallet_${{ needs.build.outputs.project_version }}_${{ needs.build.outputs.new_build_number }}.ipa" >> $GITHUB_ENV run: echo "IPA_OUTPUT_PATH=$(pwd)/BlueWallet_${{ needs.build.outputs.project_version }}_${{ needs.build.outputs.new_build_number }}.ipa" >> $GITHUB_ENV
@ -205,19 +282,23 @@ jobs:
- name: Verify IPA Path Before Upload - name: Verify IPA Path Before Upload
run: | run: |
if [ ! -f "$IPA_OUTPUT_PATH" ]; then if [ ! -f "$IPA_OUTPUT_PATH" ]; then
echo "IPA file not found at path: $IPA_OUTPUT_PATH" echo "❌ IPA file not found at path: $IPA_OUTPUT_PATH"
ls -la $(pwd)
exit 1 exit 1
else
echo "✅ Found IPA at: $IPA_OUTPUT_PATH"
fi fi
- name: Print Environment Variables for Debugging - name: Print Environment Variables for Debugging
run: | run: |
echo "LATEST_COMMIT_MESSAGE: $LATEST_COMMIT_MESSAGE" echo "LATEST_COMMIT_MESSAGE: $LATEST_COMMIT_MESSAGE"
echo "BRANCH_NAME: $BRANCH_NAME" echo "BRANCH_NAME: $BRANCH_NAME"
echo "PROJECT_VERSION: $PROJECT_VERSION"
echo "NEW_BUILD_NUMBER: $NEW_BUILD_NUMBER"
echo "IPA_OUTPUT_PATH: $IPA_OUTPUT_PATH"
- name: Upload to TestFlight - name: Upload to TestFlight
run: | run: bundle exec fastlane ios upload_to_testflight_lane
ls -la $IPA_OUTPUT_PATH
bundle exec fastlane ios upload_to_testflight_lane
env: env:
APP_STORE_CONNECT_API_KEY_PATH: $(pwd)/appstore_api_key.p8 APP_STORE_CONNECT_API_KEY_PATH: $(pwd)/appstore_api_key.p8
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
@ -228,18 +309,19 @@ jobs:
APP_STORE_CONNECT_API_KEY_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_KEY_ID }} APP_STORE_CONNECT_API_KEY_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_KEY_ID }}
APP_STORE_CONNECT_API_KEY_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }} APP_STORE_CONNECT_API_KEY_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
IPA_OUTPUT_PATH: ${{ env.IPA_OUTPUT_PATH }}
- name: Post PR Comment - name: Post PR Comment
if: success() && github.event_name == 'pull_request' if: success() && github.event_name == 'pull_request'
uses: actions/github-script@v6 uses: actions/github-script@v6
env: env:
BUILD_NUMBER: ${{ needs.build.outputs.new_build_number }} BUILD_NUMBER: ${{ needs.build.outputs.new_build_number }}
PROJECT_VERSION: ${{ needs.build.outputs.project_version }}
LATEST_COMMIT_MESSAGE: ${{ needs.build.outputs.latest_commit_message }} LATEST_COMMIT_MESSAGE: ${{ needs.build.outputs.latest_commit_message }}
with: with:
script: | script: |
const buildNumber = process.env.BUILD_NUMBER; const buildNumber = process.env.BUILD_NUMBER;
const message = `The build ${buildNumber} has been uploaded to TestFlight.`; const version = process.env.PROJECT_VERSION;
const message = `✅ Build ${version} (${buildNumber}) has been uploaded to TestFlight and will be available for testing soon.`;
const prNumber = context.payload.pull_request.number; const prNumber = context.payload.pull_request.number;
const repo = context.repo; const repo = context.repo;
github.rest.issues.createComment({ github.rest.issues.createComment({

17
App.tsx
View file

@ -1,5 +1,3 @@
import 'react-native-gesture-handler'; // should be on top
import { NavigationContainer } from '@react-navigation/native'; import { NavigationContainer } from '@react-navigation/native';
import React from 'react'; import React from 'react';
import { useColorScheme } from 'react-native'; import { useColorScheme } from 'react-native';
@ -9,23 +7,26 @@ import { SettingsProvider } from './components/Context/SettingsProvider';
import { BlueDarkTheme, BlueDefaultTheme } from './components/themes'; import { BlueDarkTheme, BlueDefaultTheme } from './components/themes';
import MasterView from './navigation/MasterView'; import MasterView from './navigation/MasterView';
import { navigationRef } from './NavigationService'; import { navigationRef } from './NavigationService';
import { useLogger } from '@react-navigation/devtools';
import { StorageProvider } from './components/Context/StorageProvider'; import { StorageProvider } from './components/Context/StorageProvider';
const App = () => { const App = () => {
const colorScheme = useColorScheme(); const colorScheme = useColorScheme();
useLogger(navigationRef);
return ( return (
<LargeScreenProvider> <NavigationContainer ref={navigationRef} theme={colorScheme === 'dark' ? BlueDarkTheme : BlueDefaultTheme}>
<NavigationContainer ref={navigationRef} theme={colorScheme === 'dark' ? BlueDarkTheme : BlueDefaultTheme}> <SafeAreaProvider>
<SafeAreaProvider> <LargeScreenProvider>
<StorageProvider> <StorageProvider>
<SettingsProvider> <SettingsProvider>
<MasterView /> <MasterView />
</SettingsProvider> </SettingsProvider>
</StorageProvider> </StorageProvider>
</SafeAreaProvider> </LargeScreenProvider>
</NavigationContainer> </SafeAreaProvider>
</LargeScreenProvider> </NavigationContainer>
); );
}; };

View file

@ -40,9 +40,16 @@ export const BlueCard = props => {
return <View {...props} style={{ padding: 20 }} />; return <View {...props} style={{ padding: 20 }} />;
}; };
export const BlueText = props => { export const BlueText = ({ bold = false, ...props }) => {
const { colors } = useTheme(); const { colors } = useTheme();
const style = StyleSheet.compose({ color: colors.foregroundColor, writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr' }, props.style); const style = StyleSheet.compose(
{
color: colors.foregroundColor,
writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr',
fontWeight: bold ? 'bold' : 'normal',
},
props.style,
);
return <Text {...props} style={style} />; return <Text {...props} style={style} />;
}; };
@ -75,6 +82,7 @@ export const BlueFormMultiInput = props => {
multiline multiline
underlineColorAndroid="transparent" underlineColorAndroid="transparent"
numberOfLines={4} numberOfLines={4}
editable={!props.editable}
style={{ style={{
paddingHorizontal: 8, paddingHorizontal: 8,
paddingVertical: 16, paddingVertical: 16,

View file

@ -3,9 +3,14 @@ source "https://rubygems.org"
# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
ruby "3.1.6" ruby "3.1.6"
gem 'rubyzip', '2.4.1' gem 'rubyzip', '2.4.1'
gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1' gem 'cocoapods', '~> 1.14.3'
gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0' gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0'
gem "fastlane", ">= 2.225.0" gem "fastlane", "~> 2.226.0"
gem 'xcodeproj', '< 1.26.0' gem 'xcodeproj', '< 1.26.0'
gem 'concurrent-ruby', '< 1.3.4'
# Required for App Store Connect API
gem "jwt"
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
eval_gemfile(plugins_path) if File.exist?(plugins_path) eval_gemfile(plugins_path) if File.exist?(plugins_path)

View file

@ -24,17 +24,18 @@ GEM
json (>= 1.5.1) json (>= 1.5.1)
artifactory (3.0.17) artifactory (3.0.17)
atomos (0.1.3) atomos (0.1.3)
aws-eventstream (1.3.0) aws-eventstream (1.3.1)
aws-partitions (1.1042.0) aws-partitions (1.1058.0)
aws-sdk-core (3.217.0) aws-sdk-core (3.219.0)
aws-eventstream (~> 1, >= 1.3.0) aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0) aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9) aws-sigv4 (~> 1.9)
base64
jmespath (~> 1, >= 1.6.1) jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.97.0) aws-sdk-kms (1.99.0)
aws-sdk-core (~> 3, >= 3.216.0) aws-sdk-core (~> 3, >= 3.216.0)
aws-sigv4 (~> 1.5) aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.178.0) aws-sdk-s3 (1.182.0)
aws-sdk-core (~> 3, >= 3.216.0) aws-sdk-core (~> 3, >= 3.216.0)
aws-sdk-kms (~> 1) aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5) aws-sigv4 (~> 1.5)
@ -45,10 +46,10 @@ GEM
benchmark (0.4.0) benchmark (0.4.0)
bigdecimal (3.1.9) bigdecimal (3.1.9)
claide (1.1.0) claide (1.1.0)
cocoapods (1.15.2) cocoapods (1.14.3)
addressable (~> 2.8) addressable (~> 2.8)
claide (>= 1.0.2, < 2.0) claide (>= 1.0.2, < 2.0)
cocoapods-core (= 1.15.2) cocoapods-core (= 1.14.3)
cocoapods-deintegrate (>= 1.0.3, < 2.0) cocoapods-deintegrate (>= 1.0.3, < 2.0)
cocoapods-downloader (>= 2.1, < 3.0) cocoapods-downloader (>= 2.1, < 3.0)
cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-plugins (>= 1.0.0, < 2.0)
@ -63,7 +64,7 @@ GEM
nap (~> 1.0) nap (~> 1.0)
ruby-macho (>= 2.3.0, < 3.0) ruby-macho (>= 2.3.0, < 3.0)
xcodeproj (>= 1.23.0, < 2.0) xcodeproj (>= 1.23.0, < 2.0)
cocoapods-core (1.15.2) cocoapods-core (1.14.3)
activesupport (>= 5.0, < 8) activesupport (>= 5.0, < 8)
addressable (~> 2.8) addressable (~> 2.8)
algoliasearch (~> 1.0) algoliasearch (~> 1.0)
@ -86,10 +87,10 @@ GEM
colored2 (3.1.2) colored2 (3.1.2)
commander (4.6.0) commander (4.6.0)
highline (~> 2.0.0) highline (~> 2.0.0)
concurrent-ruby (1.3.5) concurrent-ruby (1.3.3)
connection_pool (2.5.0) connection_pool (2.5.0)
declarative (0.0.20) declarative (0.0.20)
digest-crc (0.6.5) digest-crc (0.7.0)
rake (>= 12.0.0, < 14.0.0) rake (>= 12.0.0, < 14.0.0)
domain_name (0.6.20240107) domain_name (0.6.20240107)
dotenv (2.8.1) dotenv (2.8.1)
@ -172,6 +173,9 @@ GEM
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
fastlane-plugin-browserstack (0.3.3) fastlane-plugin-browserstack (0.3.3)
rest-client (~> 2.0, >= 2.0.2) rest-client (~> 2.0, >= 2.0.2)
fastlane-plugin-bugsnag (2.3.1)
git
xml-simple
fastlane-plugin-bugsnag_sourcemaps_upload (0.2.0) fastlane-plugin-bugsnag_sourcemaps_upload (0.2.0)
fastlane-sirp (1.0.0) fastlane-sirp (1.0.0)
sysrandom (~> 1.0) sysrandom (~> 1.0)
@ -179,6 +183,11 @@ GEM
fourflusher (2.3.1) fourflusher (2.3.1)
fuzzy_match (2.0.4) fuzzy_match (2.0.4)
gh_inspector (1.1.3) gh_inspector (1.1.3)
git (3.0.0)
activesupport (>= 5.0)
addressable (~> 2.8)
process_executer (~> 1.3)
rchardet (~> 1.9)
google-apis-androidpublisher_v3 (0.54.0) google-apis-androidpublisher_v3 (0.54.0)
google-apis-core (>= 0.11.0, < 2.a) google-apis-core (>= 0.11.0, < 2.a)
google-apis-core (0.11.3) google-apis-core (0.11.3)
@ -219,24 +228,26 @@ GEM
http-accept (1.7.0) http-accept (1.7.0)
http-cookie (1.0.8) http-cookie (1.0.8)
domain_name (~> 0.5) domain_name (~> 0.5)
httpclient (2.8.3) httpclient (2.9.0)
mutex_m
i18n (1.14.7) i18n (1.14.7)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
jmespath (1.6.2) jmespath (1.6.2)
json (2.9.1) json (2.10.1)
jwt (2.10.1) jwt (2.10.1)
base64 base64
logger (1.6.5) logger (1.6.6)
mime-types (3.6.0) mime-types (3.6.0)
logger logger
mime-types-data (~> 3.2015) mime-types-data (~> 3.2015)
mime-types-data (3.2025.0107) mime-types-data (3.2025.0220)
mini_magick (4.13.2) mini_magick (4.13.2)
mini_mime (1.1.5) mini_mime (1.1.5)
minitest (5.25.4) minitest (5.25.4)
molinillo (0.8.0) molinillo (0.8.0)
multi_json (1.15.0) multi_json (1.15.0)
multipart-post (2.4.1) multipart-post (2.4.1)
mutex_m (0.3.0)
nanaimo (0.3.0) nanaimo (0.3.0)
nap (1.1.0) nap (1.1.0)
naturally (2.2.1) naturally (2.2.1)
@ -245,8 +256,10 @@ GEM
optparse (0.6.0) optparse (0.6.0)
os (1.1.4) os (1.1.4)
plist (3.7.2) plist (3.7.2)
process_executer (1.3.0)
public_suffix (4.0.7) public_suffix (4.0.7)
rake (13.2.1) rake (13.2.1)
rchardet (1.9.0)
representable (3.2.0) representable (3.2.0)
declarative (< 0.1.0) declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0) trailblazer-option (>= 0.1.1, < 0.2.0)
@ -257,7 +270,7 @@ GEM
mime-types (>= 1.16, < 4.0) mime-types (>= 1.16, < 4.0)
netrc (~> 0.8) netrc (~> 0.8)
retriable (3.1.2) retriable (3.1.2)
rexml (3.4.0) rexml (3.4.1)
rouge (3.28.0) rouge (3.28.0)
ruby-macho (2.5.1) ruby-macho (2.5.1)
ruby2_keywords (0.0.5) ruby2_keywords (0.0.5)
@ -299,16 +312,21 @@ GEM
rouge (~> 3.28.0) rouge (~> 3.28.0)
xcpretty-travis-formatter (1.0.1) xcpretty-travis-formatter (1.0.1)
xcpretty (~> 0.2, >= 0.0.7) xcpretty (~> 0.2, >= 0.0.7)
xml-simple (1.1.9)
rexml
PLATFORMS PLATFORMS
ruby ruby
DEPENDENCIES DEPENDENCIES
activesupport (>= 6.1.7.5, != 7.1.0) activesupport (>= 6.1.7.5, != 7.1.0)
cocoapods (>= 1.13, != 1.15.1, != 1.15.0) cocoapods (~> 1.14.3)
fastlane (>= 2.225.0) concurrent-ruby (< 1.3.4)
fastlane (~> 2.226.0)
fastlane-plugin-browserstack fastlane-plugin-browserstack
fastlane-plugin-bugsnag
fastlane-plugin-bugsnag_sourcemaps_upload fastlane-plugin-bugsnag_sourcemaps_upload
jwt
rubyzip (= 2.4.1) rubyzip (= 2.4.1)
xcodeproj (< 1.26.0) xcodeproj (< 1.26.0)

View file

@ -1,23 +0,0 @@
import 'react-native-gesture-handler'; // should be on top
import React, { lazy, Suspense } from 'react';
import MainRoot from './navigation';
import { useStorage } from './hooks/context/useStorage';
const CompanionDelegates = lazy(() => import('./components/CompanionDelegates'));
const MasterView = () => {
const { walletsInitialized } = useStorage();
return (
<>
<MainRoot />
{walletsInitialized && (
<Suspense>
<CompanionDelegates />
</Suspense>
)}
</>
);
};
export default MasterView;

View file

@ -14,10 +14,6 @@ export function dispatch(action: NavigationAction) {
} }
} }
export function navigateToWalletsList() {
navigate('WalletsList');
}
export function reset() { export function reset() {
if (navigationRef.isReady()) { if (navigationRef.isReady()) {
navigationRef.current?.reset({ navigationRef.current?.reset({

View file

@ -73,6 +73,10 @@ def enableProguardInReleaseBuilds = false
def jscFlavor = 'org.webkit:android-jsc-intl:+' def jscFlavor = 'org.webkit:android-jsc-intl:+'
android { android {
androidResources {
noCompress += ["bundle"]
}
ndkVersion rootProject.ext.ndkVersion ndkVersion rootProject.ext.ndkVersion
buildToolsVersion rootProject.ext.buildToolsVersion buildToolsVersion rootProject.ext.buildToolsVersion
compileSdkVersion rootProject.ext.compileSdkVersion compileSdkVersion rootProject.ext.compileSdkVersion
@ -83,7 +87,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1 versionCode 1
versionName "7.0.9" versionName "7.1.5"
testBuildType System.getProperty('testBuildType', 'debug') testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
} }
@ -133,7 +137,7 @@ dependencies {
androidTestImplementation('com.wix:detox:0.1.1') androidTestImplementation('com.wix:detox:0.1.1')
implementation 'androidx.appcompat:appcompat:1.7.0' implementation 'androidx.appcompat:appcompat:1.7.0'
implementation fileTree(dir: "libs", include: ["*.jar"]) implementation fileTree(dir: "libs", include: ["*.jar"])
implementation 'androidx.constraintlayout:constraintlayout:2.2.0' implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
} }
apply plugin: 'com.google.gms.google-services' // Google Services plugin apply plugin: 'com.google.gms.google-services' // Google Services plugin
apply plugin: "com.bugsnag.android.gradle" apply plugin: "com.bugsnag.android.gradle"

View file

@ -16,13 +16,6 @@
<uses-permission android:name="android.permission.USE_FINGERPRINT" /> <uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.ACTION_OPEN_DOCUMENT" />
<uses-permission android:name="android.permission.ACTION_GET_CONTENT" />
<uses-permission android:name="android.permission.ACTION_CREATE_DOCUMENT" />
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.ACTION_SCREEN_OFF"/>
<uses-permission android:name="android.permission.ACTION_SCREEN_ON"/>
<application <application
android:name=".MainApplication" android:name=".MainApplication"
@ -58,7 +51,15 @@
<meta-data <meta-data
android:name="firebase_analytics_collection_enabled" android:name="firebase_analytics_collection_enabled"
android:value="false" /> android:value="false" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationActions" /> <receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationActions" />
<receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationPublisher" /> <receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationPublisher" />
<receiver <receiver

View file

@ -4,6 +4,7 @@ import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider import android.appwidget.AppWidgetProvider
import android.content.Context import android.content.Context
import android.util.Log import android.util.Log
import android.widget.Toast
import androidx.work.WorkManager import androidx.work.WorkManager
class BitcoinPriceWidget : AppWidgetProvider() { class BitcoinPriceWidget : AppWidgetProvider() {
@ -11,7 +12,6 @@ class BitcoinPriceWidget : AppWidgetProvider() {
companion object { companion object {
private const val TAG = "BitcoinPriceWidget" private const val TAG = "BitcoinPriceWidget"
private const val SHARED_PREF_NAME = "group.io.bluewallet.bluewallet" private const val SHARED_PREF_NAME = "group.io.bluewallet.bluewallet"
private const val WIDGET_COUNT_KEY = "widget_count"
} }
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
@ -24,22 +24,12 @@ class BitcoinPriceWidget : AppWidgetProvider() {
override fun onEnabled(context: Context) { override fun onEnabled(context: Context) {
super.onEnabled(context) super.onEnabled(context)
val sharedPref = context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)
val widgetCount = sharedPref.getInt(WIDGET_COUNT_KEY, 0)
if (widgetCount >= 1) {
Log.e(TAG, "Only one widget instance is allowed.")
return
}
sharedPref.edit().putInt(WIDGET_COUNT_KEY, widgetCount + 1).apply()
Log.d(TAG, "onEnabled called") Log.d(TAG, "onEnabled called")
WidgetUpdateWorker.scheduleWork(context) WidgetUpdateWorker.scheduleWork(context)
} }
override fun onDisabled(context: Context) { override fun onDisabled(context: Context) {
super.onDisabled(context) super.onDisabled(context)
val sharedPref = context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)
val widgetCount = sharedPref.getInt(WIDGET_COUNT_KEY, 1)
sharedPref.edit().putInt(WIDGET_COUNT_KEY, widgetCount - 1).apply()
Log.d(TAG, "onDisabled called") Log.d(TAG, "onDisabled called")
clearCache(context) clearCache(context)
WorkManager.getInstance(context).cancelUniqueWork(WidgetUpdateWorker.WORK_NAME) WorkManager.getInstance(context).cancelUniqueWork(WidgetUpdateWorker.WORK_NAME)
@ -47,9 +37,6 @@ class BitcoinPriceWidget : AppWidgetProvider() {
override fun onDeleted(context: Context, appWidgetIds: IntArray) { override fun onDeleted(context: Context, appWidgetIds: IntArray) {
super.onDeleted(context, appWidgetIds) super.onDeleted(context, appWidgetIds)
val sharedPref = context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)
val widgetCount = sharedPref.getInt(WIDGET_COUNT_KEY, 1)
sharedPref.edit().putInt(WIDGET_COUNT_KEY, widgetCount - appWidgetIds.size).apply()
Log.d(TAG, "onDeleted called for widgets: ${appWidgetIds.joinToString()}") Log.d(TAG, "onDeleted called for widgets: ${appWidgetIds.joinToString()}")
} }

View file

@ -56,7 +56,8 @@ object MarketAPI {
"CoinGecko" -> "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=${endPointKey.lowercase()}" "CoinGecko" -> "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=${endPointKey.lowercase()}"
"BNR" -> "https://www.bnr.ro/nbrfxrates.xml" "BNR" -> "https://www.bnr.ro/nbrfxrates.xml"
"Kraken" -> "https://api.kraken.com/0/public/Ticker?pair=XXBTZ${endPointKey.uppercase()}" "Kraken" -> "https://api.kraken.com/0/public/Ticker?pair=XXBTZ${endPointKey.uppercase()}"
else -> "https://api.coindesk.com/v1/bpi/currentprice/$endPointKey.json" "CoinDesk" -> "https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=${endPointKey.uppercase()}"
else -> "https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=${endPointKey.uppercase()}"
} }
} }
} }
@ -73,6 +74,10 @@ object MarketAPI {
"coinpaprika" -> json.getJSONObject("quotes").getJSONObject("INR").getString("price") "coinpaprika" -> json.getJSONObject("quotes").getJSONObject("INR").getString("price")
"Coinbase" -> json.getJSONObject("data").getString("amount") "Coinbase" -> json.getJSONObject("data").getString("amount")
"Kraken" -> json.getJSONObject("result").getJSONObject("XXBTZ${endPointKey.uppercase()}").getJSONArray("c").getString(0) "Kraken" -> json.getJSONObject("result").getJSONObject("XXBTZ${endPointKey.uppercase()}").getJSONArray("c").getString(0)
"CoinDesk" -> {
val rate = json.optDouble(endPointKey.uppercase(), -1.0)
if (rate < 0) null else rate.toString()
}
else -> null else -> null
} }
} catch (e: Exception) { } catch (e: Exception) {

View file

@ -56,7 +56,7 @@
<TextView <TextView
android:id="@+id/price_value" android:id="@+id/price_value"
style="@style/WidgetTextPrimary" style="@style/WidgetTextPrimary"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="end" android:layout_gravity="end"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
@ -67,6 +67,7 @@
android:autoSizeTextType="uniform" android:autoSizeTextType="uniform"
android:duplicateParentState="false" android:duplicateParentState="false"
android:editable="false" android:editable="false"
android:gravity="end"
android:lines="1" android:lines="1"
android:text="Loading..." android:text="Loading..."
android:textSize="24sp" android:textSize="24sp"

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/widget_layout" android:initialLayout="@layout/widget_layout"
android:minWidth="160dp" android:minWidth="170dp"
android:minHeight="100dp" android:minHeight="100dp"
android:updatePeriodMillis="0" android:updatePeriodMillis="0"
android:widgetCategory="home_screen" android:widgetCategory="home_screen"

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="downloads" path="Download/" />
</paths>

View file

@ -138,31 +138,40 @@ async function _getRealm() {
} }
export const getPreferredServer = async (): Promise<ElectrumServerItem | undefined> => { export const getPreferredServer = async (): Promise<ElectrumServerItem | undefined> => {
await DefaultPreference.setName(GROUP_IO_BLUEWALLET); try {
const host = (await DefaultPreference.get(ELECTRUM_HOST)) as string; await DefaultPreference.setName(GROUP_IO_BLUEWALLET);
const tcpPort = await DefaultPreference.get(ELECTRUM_TCP_PORT); const host = (await DefaultPreference.get(ELECTRUM_HOST)) as string;
const sslPort = await DefaultPreference.get(ELECTRUM_SSL_PORT); const tcpPort = await DefaultPreference.get(ELECTRUM_TCP_PORT);
const sslPort = await DefaultPreference.get(ELECTRUM_SSL_PORT);
console.log('Getting preferred server:', { host, tcpPort, sslPort }); console.log('Getting preferred server:', { host, tcpPort, sslPort });
if (!host) { if (!host) {
console.warn('Preferred server host is undefined'); console.warn('Preferred server host is undefined');
return; return;
}
return {
host,
tcp: tcpPort ? Number(tcpPort) : undefined,
ssl: sslPort ? Number(sslPort) : undefined,
};
} catch (error) {
console.error('Error in getPreferredServer:', error);
return undefined;
} }
return {
host,
tcp: tcpPort ? Number(tcpPort) : undefined,
ssl: sslPort ? Number(sslPort) : undefined,
};
}; };
export const removePreferredServer = async () => { export const removePreferredServer = async () => {
await DefaultPreference.setName(GROUP_IO_BLUEWALLET); try {
console.log('Removing preferred server'); await DefaultPreference.setName(GROUP_IO_BLUEWALLET);
await DefaultPreference.clear(ELECTRUM_HOST); console.log('Removing preferred server');
await DefaultPreference.clear(ELECTRUM_TCP_PORT); await DefaultPreference.clear(ELECTRUM_HOST);
await DefaultPreference.clear(ELECTRUM_SSL_PORT); await DefaultPreference.clear(ELECTRUM_TCP_PORT);
await DefaultPreference.clear(ELECTRUM_SSL_PORT);
} catch (error) {
console.error('Error in removePreferredServer:', error);
}
}; };
export async function isDisabled(): Promise<boolean> { export async function isDisabled(): Promise<boolean> {
@ -204,26 +213,31 @@ function getNextPeer() {
} }
async function getSavedPeer(): Promise<Peer | null> { async function getSavedPeer(): Promise<Peer | null> {
await DefaultPreference.setName(GROUP_IO_BLUEWALLET); try {
const host = (await DefaultPreference.get(ELECTRUM_HOST)) as string; await DefaultPreference.setName(GROUP_IO_BLUEWALLET);
const tcpPort = await DefaultPreference.get(ELECTRUM_TCP_PORT); const host = (await DefaultPreference.get(ELECTRUM_HOST)) as string;
const sslPort = await DefaultPreference.get(ELECTRUM_SSL_PORT); const tcpPort = await DefaultPreference.get(ELECTRUM_TCP_PORT);
const sslPort = await DefaultPreference.get(ELECTRUM_SSL_PORT);
console.log('Getting saved peer:', { host, tcpPort, sslPort }); console.log('Getting saved peer:', { host, tcpPort, sslPort });
if (!host) { if (!host) {
return null;
}
if (sslPort) {
return { host, ssl: Number(sslPort) };
}
if (tcpPort) {
return { host, tcp: Number(tcpPort) };
}
return null;
} catch (error) {
console.error('Error in getSavedPeer:', error);
return null; return null;
} }
if (sslPort) {
return { host, ssl: Number(sslPort) };
}
if (tcpPort) {
return { host, tcp: Number(tcpPort) };
}
return null;
} }
export async function connectMain(): Promise<void> { export async function connectMain(): Promise<void> {
@ -239,19 +253,6 @@ export async function connectMain(): Promise<void> {
console.log('Using peer:', JSON.stringify(usingPeer)); console.log('Using peer:', JSON.stringify(usingPeer));
await DefaultPreference.setName(GROUP_IO_BLUEWALLET);
try {
if (usingPeer.host.endsWith('onion')) {
const randomPeer = getCurrentPeer();
await DefaultPreference.set(ELECTRUM_HOST, randomPeer.host);
await DefaultPreference.set(ELECTRUM_TCP_PORT, randomPeer.tcp ?? '');
await DefaultPreference.set(ELECTRUM_SSL_PORT, randomPeer.ssl ?? '');
}
} catch (e) {
// Must be running on Android
console.log(e);
}
try { try {
console.log('begin connection:', JSON.stringify(usingPeer)); console.log('begin connection:', JSON.stringify(usingPeer));
mainClient = new ElectrumClient(net, tls, usingPeer.ssl || usingPeer.tcp, usingPeer.host, usingPeer.ssl ? 'tls' : 'tcp'); mainClient = new ElectrumClient(net, tls, usingPeer.ssl || usingPeer.tcp, usingPeer.host, usingPeer.ssl ? 'tls' : 'tcp');
@ -262,7 +263,8 @@ export async function connectMain(): Promise<void> {
// most likely got a timeout from electrum ping. lets reconnect // most likely got a timeout from electrum ping. lets reconnect
// but only if we were previously connected (mainConnected), otherwise theres other // but only if we were previously connected (mainConnected), otherwise theres other
// code which does connection retries // code which does connection retries
mainClient.close(); mainClient?.close();
mainClient = undefined;
mainConnected = false; mainConnected = false;
// dropping `mainConnected` flag ensures there wont be reconnection race condition if several // dropping `mainConnected` flag ensures there wont be reconnection race condition if several
// errors triggered // errors triggered
@ -310,12 +312,15 @@ export async function connectMain(): Promise<void> {
} catch (e) { } catch (e) {
mainConnected = false; mainConnected = false;
console.log('bad connection:', JSON.stringify(usingPeer), e); console.log('bad connection:', JSON.stringify(usingPeer), e);
mainClient?.close();
mainClient = undefined;
} }
if (!mainConnected) { if (!mainConnected) {
console.log('retry'); console.log('retry');
connectionAttempt = connectionAttempt + 1; connectionAttempt = connectionAttempt + 1;
mainClient.close && mainClient.close(); mainClient?.close();
mainClient = undefined;
if (connectionAttempt >= 5) { if (connectionAttempt >= 5) {
presentNetworkErrorAlert(usingPeer); presentNetworkErrorAlert(usingPeer);
} else { } else {
@ -407,7 +412,8 @@ const presentNetworkErrorAlert = async (usingPeer?: Peer) => {
text: loc.wallets.list_tryagain, text: loc.wallets.list_tryagain,
onPress: () => { onPress: () => {
connectionAttempt = 0; connectionAttempt = 0;
mainClient.close() && mainClient.close(); mainClient?.close();
mainClient = undefined;
setTimeout(connectMain, 500); setTimeout(connectMain, 500);
}, },
style: 'default', style: 'default',
@ -418,7 +424,8 @@ const presentNetworkErrorAlert = async (usingPeer?: Peer) => {
presentResetToDefaultsAlert().then(result => { presentResetToDefaultsAlert().then(result => {
if (result) { if (result) {
connectionAttempt = 0; connectionAttempt = 0;
mainClient.close() && mainClient.close(); mainClient?.close();
mainClient = undefined;
setTimeout(connectMain, 500); setTimeout(connectMain, 500);
} }
}); });
@ -429,7 +436,8 @@ const presentNetworkErrorAlert = async (usingPeer?: Peer) => {
text: loc._.cancel, text: loc._.cancel,
onPress: () => { onPress: () => {
connectionAttempt = 0; connectionAttempt = 0;
mainClient.close() && mainClient.close(); mainClient?.close();
mainClient = undefined;
}, },
style: 'cancel', style: 'cancel',
}, },
@ -474,13 +482,18 @@ async function getRandomDynamicPeer(): Promise<Peer> {
} }
export const getBalanceByAddress = async function (address: string): Promise<{ confirmed: number; unconfirmed: number }> { export const getBalanceByAddress = async function (address: string): Promise<{ confirmed: number; unconfirmed: number }> {
if (!mainClient) throw new Error('Electrum client is not connected'); try {
const script = bitcoin.address.toOutputScript(address); if (!mainClient) throw new Error('Electrum client is not connected');
const hash = bitcoin.crypto.sha256(script); const script = bitcoin.address.toOutputScript(address);
const reversedHash = Buffer.from(hash).reverse(); const hash = bitcoin.crypto.sha256(script);
const balance = await mainClient.blockchainScripthash_getBalance(reversedHash.toString('hex')); const reversedHash = Buffer.from(hash).reverse();
balance.addr = address; const balance = await mainClient.blockchainScripthash_getBalance(reversedHash.toString('hex'));
return balance; balance.addr = address;
return balance;
} catch (error) {
console.error('Error in getBalanceByAddress:', error);
throw error;
}
}; };
export const getConfig = async function () { export const getConfig = async function () {
@ -958,25 +971,29 @@ export async function multiGetTransactionByTxid<T extends boolean>(
} }
// saving cache: // saving cache:
realm.write(() => { try {
for (const txid of Object.keys(ret)) { realm.write(() => {
const tx = ret[txid]; for (const txid of Object.keys(ret)) {
// dont cache immature txs, but only for 'verbose', since its fully decoded tx jsons. non-verbose are just plain const tx = ret[txid];
// strings txhex // dont cache immature txs, but only for 'verbose', since its fully decoded tx jsons. non-verbose are just plain
if (verbose && typeof tx !== 'string' && (!tx?.confirmations || tx.confirmations < 7)) { // strings txhex
continue; if (verbose && typeof tx !== 'string' && (!tx?.confirmations || tx.confirmations < 7)) {
} continue;
}
realm.create( realm.create(
'Cache', 'Cache',
{ {
cache_key: txid + cacheKeySuffix, cache_key: txid + cacheKeySuffix,
cache_value: JSON.stringify(ret[txid]), cache_value: JSON.stringify(ret[txid]),
}, },
Realm.UpdateMode.Modified, Realm.UpdateMode.Modified,
); );
} }
}); });
} catch (writeError) {
console.error('Failed to write transaction cache:', writeError);
}
return ret; return ret;
} }

View file

@ -1,9 +1,8 @@
import { Alert, Linking, Platform } from 'react-native'; import { Platform } from 'react-native';
import DocumentPicker from 'react-native-document-picker'; import DocumentPicker from 'react-native-document-picker';
import RNFS from 'react-native-fs'; import RNFS from 'react-native-fs';
import { launchImageLibrary, ImagePickerResponse } from 'react-native-image-picker'; import { launchImageLibrary, ImagePickerResponse } from 'react-native-image-picker';
import Share from 'react-native-share'; import Share from 'react-native-share';
import { request, PERMISSIONS } from 'react-native-permissions';
import presentAlert from '../components/Alert'; import presentAlert from '../components/Alert';
import loc from '../loc'; import loc from '../loc';
import { isDesktop } from './environment'; import { isDesktop } from './environment';
@ -37,7 +36,7 @@ const _shareOpen = async (filePath: string, showShareDialog: boolean = false) =>
/** /**
* Writes a file to fs, and triggers an OS sharing dialog, so user can decide where to put this file (share to cloud * Writes a file to fs, and triggers an OS sharing dialog, so user can decide where to put this file (share to cloud
* or perhabs messaging app). Provided filename should be just a file name, NOT a path * or perhaps messaging app). Provided filename should be just a file name, NOT a path
*/ */
export const writeFileAndExport = async function (fileName: string, contents: string, showShareDialog: boolean = true) { export const writeFileAndExport = async function (fileName: string, contents: string, showShareDialog: boolean = true) {
@ -48,36 +47,21 @@ export const writeFileAndExport = async function (fileName: string, contents: st
await RNFS.writeFile(filePath, contents); await RNFS.writeFile(filePath, contents);
await _shareOpen(filePath, showShareDialog); await _shareOpen(filePath, showShareDialog);
} else if (Platform.OS === 'android') { } else if (Platform.OS === 'android') {
const isAndroidVersion33OrAbove = Platform.Version >= 33; const filePath = `${RNFS.DownloadDirectoryPath}/${sanitizedFileName}`;
const permissionType = isAndroidVersion33OrAbove ? PERMISSIONS.ANDROID.READ_MEDIA_IMAGES : PERMISSIONS.ANDROID.READ_EXTERNAL_STORAGE; try {
await RNFS.writeFile(filePath, contents);
const result = await request(permissionType); if (showShareDialog) {
if (result === 'granted') { await _shareOpen(filePath);
const filePath = `${RNFS.ExternalDirectoryPath}/${sanitizedFileName}`; } else {
try { presentAlert({ message: loc.formatString(loc.send.file_saved_at_path, { filePath }) });
await RNFS.writeFile(filePath, contents);
if (showShareDialog) {
await _shareOpen(filePath);
} else {
presentAlert({ message: loc.formatString(loc.send.file_saved_at_path, { filePath }) });
}
} catch (e: any) {
presentAlert({ message: e.message });
} }
} else { } catch (e: any) {
Alert.alert(loc.send.permission_storage_title, loc.send.permission_storage_denied_message, [ console.error(e);
{ presentAlert({ message: e.message });
text: loc.send.open_settings,
onPress: () => {
Linking.openSettings();
},
style: 'default',
},
{ text: loc._.cancel, onPress: () => {}, style: 'cancel' },
]);
} }
} }
} catch (error: any) { } catch (error: any) {
console.error(error);
presentAlert({ message: error.message }); presentAlert({ message: error.message });
} }
}; };
@ -114,7 +98,7 @@ const _readPsbtFileIntoBase64 = async function (uri: string): Promise<string> {
} else { } else {
// file was a text file, having base64 psbt in there. so we basically have double base64encoded string // file was a text file, having base64 psbt in there. so we basically have double base64encoded string
// thats why we are returning string that was decoded once; // thats why we are returning string that was decoded once;
// most likely produced by Coldcard // most likely produced by ColdCard
return stringData; return stringData;
} }
}; };
@ -183,23 +167,11 @@ export const showFilePickerAndReadFile = async function (): Promise<{ data: stri
if (res.fileCopyUri.toLowerCase().endsWith('.psbt')) { if (res.fileCopyUri.toLowerCase().endsWith('.psbt')) {
// this is either binary file from ElectrumDesktop OR string file with base64 string in there // this is either binary file from ElectrumDesktop OR string file with base64 string in there
const file = await _readPsbtFileIntoBase64(fileCopyUri); const file = await _readPsbtFileIntoBase64(fileCopyUri);
return { data: file, uri: decodeURI(res.fileCopyUri) }; return { data: file, uri: fileCopyUri };
} }
if (res.type === DocumentPicker.types.images || res.type?.startsWith('image/')) { if (res.type === DocumentPicker.types.images || res.type?.startsWith('image/')) {
try { return await handleImageFile(fileCopyUri);
const uri2 = res.fileCopyUri.replace('file://', '');
const result = await RNQRGenerator.detect({ uri: decodeURI(uri2) });
if (result) {
return { data: result.values[0], uri: fileCopyUri };
}
presentAlert({ message: loc.send.qr_error_no_qrcode });
return { data: false, uri: false };
} catch (error) {
console.error(error);
presentAlert({ message: loc.send.qr_error_no_qrcode });
return { data: false, uri: false };
}
} }
const file = await RNFS.readFile(fileCopyUri); const file = await RNFS.readFile(fileCopyUri);
@ -212,6 +184,33 @@ export const showFilePickerAndReadFile = async function (): Promise<{ data: stri
} }
}; };
const handleImageFile = async (fileCopyUri: string): Promise<{ data: string | false; uri: string | false }> => {
try {
const exists = await RNFS.exists(fileCopyUri);
if (!exists) {
presentAlert({ message: 'File does not exist' });
return { data: false, uri: false };
}
// First attempt: use original URI
let result = await RNQRGenerator.detect({ uri: decodeURI(fileCopyUri) });
if (result?.values && result.values.length > 0) {
return { data: result.values[0], uri: fileCopyUri };
}
// Second attempt: remove file:// prefix and try again
const altUri = fileCopyUri.replace(/^file:\/\//, '');
result = await RNQRGenerator.detect({ uri: decodeURI(altUri) });
if (result?.values && result.values.length > 0) {
return { data: result.values[0], uri: fileCopyUri };
}
presentAlert({ message: loc.send.qr_error_no_qrcode });
return { data: false, uri: false };
} catch (error: any) {
console.error(error);
presentAlert({ message: loc.send.qr_error_no_qrcode });
return { data: false, uri: false };
}
};
export const readFileOutsideSandbox = (filePath: string) => { export const readFileOutsideSandbox = (filePath: string) => {
if (Platform.OS === 'ios') { if (Platform.OS === 'ios') {
return readFile(filePath); return readFile(filePath);

View file

@ -6,6 +6,7 @@ import { checkNotifications, requestNotifications, RESULTS } from 'react-native-
import PushNotification from 'react-native-push-notification'; import PushNotification from 'react-native-push-notification';
import loc from '../loc'; import loc from '../loc';
import { groundControlUri } from './constants'; import { groundControlUri } from './constants';
import { fetch } from '../util/fetch';
const PUSH_TOKEN = 'PUSH_TOKEN'; const PUSH_TOKEN = 'PUSH_TOKEN';
const GROUNDCONTROL_BASE_URI = 'GROUNDCONTROL_BASE_URI'; const GROUNDCONTROL_BASE_URI = 'GROUNDCONTROL_BASE_URI';
@ -385,10 +386,6 @@ export const configureNotifications = async onProcessNotifications => {
}); });
}; };
const _sleep = async ms => {
return new Promise(resolve => setTimeout(resolve, ms));
};
/** /**
* Validates whether the provided GroundControl URI is valid by pinging it. * Validates whether the provided GroundControl URI is valid by pinging it.
* *
@ -396,15 +393,13 @@ const _sleep = async ms => {
* @returns {Promise<boolean>} TRUE if valid, FALSE otherwise * @returns {Promise<boolean>} TRUE if valid, FALSE otherwise
*/ */
export const isGroundControlUriValid = async uri => { export const isGroundControlUriValid = async uri => {
let response;
try { try {
response = await Promise.race([fetch(`${uri}/ping`, { headers: _getHeaders() }), _sleep(2000)]); const response = await fetch(`${uri}/ping`, { headers: _getHeaders() });
} catch (_) {} const json = await response.json();
return !!json.description;
if (!response) return false; } catch (_) {
return false;
const json = await response.json(); }
return !!json.description;
}; };
export const isNotificationsCapable = hasGmsSync() || hasHmsSync() || Platform.OS !== 'android'; export const isNotificationsCapable = hasGmsSync() || hasHmsSync() || Platform.OS !== 'android';
@ -430,24 +425,21 @@ const getLevels = async () => {
const pushToken = await getPushToken(); const pushToken = await getPushToken();
if (!pushToken || !pushToken.token || !pushToken.os) return; if (!pushToken || !pushToken.token || !pushToken.os) return;
let response;
try { try {
response = await Promise.race([ const response = await fetch(`${baseURI}/getTokenConfiguration`, {
fetch(`${baseURI}/getTokenConfiguration`, { method: 'POST',
method: 'POST', headers: _getHeaders(),
headers: _getHeaders(), body: JSON.stringify({
body: JSON.stringify({ token: pushToken.token,
token: pushToken.token, os: pushToken.os,
os: pushToken.os,
}),
}), }),
_sleep(3000), });
]);
} catch (_) {}
if (!response) return {}; if (!response) return {};
return await response.json();
return await response.json(); } catch (_) {
return {};
}
}; };
/** /**
@ -574,6 +566,9 @@ export const isNotificationsEnabled = async () => {
return !isDisabledByUser && !!token && !!levels.level_all; return !isDisabledByUser && !!token && !!levels.level_all;
} catch (error) { } catch (error) {
console.log('Error checking notification levels:', error); console.log('Error checking notification levels:', error);
if (error instanceof SyntaxError) {
throw error;
}
return false; return false;
} }
}; };

View file

@ -1,4 +1,5 @@
import URL from 'url'; import URL from 'url';
import { fetch } from '../util/fetch';
export default class Azteco { export default class Azteco {
/** /**

View file

@ -1,7 +1,6 @@
import bip21, { TOptions } from 'bip21'; import bip21, { TOptions } from 'bip21';
import * as bitcoin from 'bitcoinjs-lib'; import * as bitcoin from 'bitcoinjs-lib';
import URL from 'url'; import URL from 'url';
import { readFileOutsideSandbox } from '../blue_modules/fs'; import { readFileOutsideSandbox } from '../blue_modules/fs';
import { Chain } from '../models/bitcoinUnits'; import { Chain } from '../models/bitcoinUnits';
import { WatchOnlyWallet } from './'; import { WatchOnlyWallet } from './';
@ -87,9 +86,9 @@ class DeeplinkSchemaMatch {
} else if (wallet.chain === Chain.OFFCHAIN) { } else if (wallet.chain === Chain.OFFCHAIN) {
if (action === 'openSend') { if (action === 'openSend') {
completionHandler([ completionHandler([
'ScanLndInvoiceRoot', 'ScanLNDInvoiceRoot',
{ {
screen: 'ScanLndInvoice', screen: 'ScanLNDInvoice',
params: { params: {
walletID: wallet.getID(), walletID: wallet.getID(),
}, },
@ -157,9 +156,9 @@ class DeeplinkSchemaMatch {
]); ]);
} else if (DeeplinkSchemaMatch.isLightningInvoice(event.url)) { } else if (DeeplinkSchemaMatch.isLightningInvoice(event.url)) {
completionHandler([ completionHandler([
'ScanLndInvoiceRoot', 'ScanLNDInvoiceRoot',
{ {
screen: 'ScanLndInvoice', screen: 'ScanLNDInvoice',
params: { params: {
uri: event.url.replace('://', ':'), uri: event.url.replace('://', ':'),
}, },
@ -182,9 +181,9 @@ class DeeplinkSchemaMatch {
// this might be not just an email but a lightning address // this might be not just an email but a lightning address
// @see https://lightningaddress.com // @see https://lightningaddress.com
completionHandler([ completionHandler([
'ScanLndInvoiceRoot', 'ScanLNDInvoiceRoot',
{ {
screen: 'ScanLndInvoice', screen: 'ScanLNDInvoice',
params: { params: {
uri: event.url, uri: event.url,
}, },
@ -306,9 +305,9 @@ class DeeplinkSchemaMatch {
]; ];
} else { } else {
return [ return [
'ScanLndInvoiceRoot', 'ScanLNDInvoiceRoot',
{ {
screen: 'ScanLndInvoice', screen: 'ScanLNDInvoice',
params: { params: {
uri: uri.lndInvoice, uri: uri.lndInvoice,
walletID: wallet.getID(), walletID: wallet.getID(),

View file

@ -6,6 +6,7 @@ import CryptoJS from 'crypto-js';
// @ts-ignore theres no types for secp256k1 // @ts-ignore theres no types for secp256k1
import secp256k1 from 'secp256k1'; import secp256k1 from 'secp256k1';
import { parse } from 'url'; // eslint-disable-line n/no-deprecated-api import { parse } from 'url'; // eslint-disable-line n/no-deprecated-api
import { fetch } from '../util/fetch';
const ONION_REGEX = /^(http:\/\/[^/:@]+\.onion(?::\d{1,5})?)(\/.*)?$/; // regex for onion URL const ONION_REGEX = /^(http:\/\/[^/:@]+\.onion(?::\d{1,5})?)(\/.*)?$/; // regex for onion URL

View file

@ -310,8 +310,11 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
// then we combine it all together // then we combine it all together
const addresses2fetch = []; const addresses2fetch = [];
// Store these values to avoid a race condition if fetchBalance func changes them
const next_free_address_index = this.next_free_address_index;
const next_free_change_address_index = this.next_free_change_address_index;
for (let c = 0; c < this.next_free_address_index + this.gap_limit; c++) { for (let c = 0; c < next_free_address_index + this.gap_limit; c++) {
// external addresses first // external addresses first
let hasUnconfirmed = false; let hasUnconfirmed = false;
this._txs_by_external_index[c] = this._txs_by_external_index[c] || []; this._txs_by_external_index[c] = this._txs_by_external_index[c] || [];
@ -322,7 +325,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
} }
} }
for (let c = 0; c < this.next_free_change_address_index + this.gap_limit; c++) { for (let c = 0; c < next_free_change_address_index + this.gap_limit; c++) {
// next, internal addresses // next, internal addresses
let hasUnconfirmed = false; let hasUnconfirmed = false;
this._txs_by_internal_index[c] = this._txs_by_internal_index[c] || []; this._txs_by_internal_index[c] = this._txs_by_internal_index[c] || [];
@ -389,10 +392,10 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
// now purge all unconfirmed txs from internal hashmaps, since some may be evicted from mempool because they became invalid // now purge all unconfirmed txs from internal hashmaps, since some may be evicted from mempool because they became invalid
// or replaced. hashmaps are going to be re-populated anyways, since we fetched TXs for addresses with unconfirmed TXs // or replaced. hashmaps are going to be re-populated anyways, since we fetched TXs for addresses with unconfirmed TXs
for (let c = 0; c < this.next_free_address_index + this.gap_limit; c++) { for (let c = 0; c < next_free_address_index + this.gap_limit; c++) {
this._txs_by_external_index[c] = this._txs_by_external_index[c].filter(tx => !!tx.confirmations); this._txs_by_external_index[c] = this._txs_by_external_index[c].filter(tx => !!tx.confirmations);
} }
for (let c = 0; c < this.next_free_change_address_index + this.gap_limit; c++) { for (let c = 0; c < next_free_change_address_index + this.gap_limit; c++) {
this._txs_by_internal_index[c] = this._txs_by_internal_index[c].filter(tx => !!tx.confirmations); this._txs_by_internal_index[c] = this._txs_by_internal_index[c].filter(tx => !!tx.confirmations);
} }
for (const pc of this._receive_payment_codes) { for (const pc of this._receive_payment_codes) {
@ -404,7 +407,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
// now, we need to put transactions in all relevant `cells` of internal hashmaps: // now, we need to put transactions in all relevant `cells` of internal hashmaps:
// this._txs_by_internal_index, this._txs_by_external_index & this._txs_by_payment_code_index // this._txs_by_internal_index, this._txs_by_external_index & this._txs_by_payment_code_index
for (let c = 0; c < this.next_free_address_index + this.gap_limit; c++) { for (let c = 0; c < next_free_address_index + this.gap_limit; c++) {
for (const tx of Object.values(txdatas)) { for (const tx of Object.values(txdatas)) {
for (const vin of tx.vin) { for (const vin of tx.vin) {
if (vin.addresses && vin.addresses.indexOf(this._getExternalAddressByIndex(c)) !== -1) { if (vin.addresses && vin.addresses.indexOf(this._getExternalAddressByIndex(c)) !== -1) {
@ -445,7 +448,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
} }
} }
for (let c = 0; c < this.next_free_change_address_index + this.gap_limit; c++) { for (let c = 0; c < next_free_change_address_index + this.gap_limit; c++) {
for (const tx of Object.values(txdatas)) { for (const tx of Object.values(txdatas)) {
for (const vin of tx.vin) { for (const vin of tx.vin) {
if (vin.addresses && vin.addresses.indexOf(this._getInternalAddressByIndex(c)) !== -1) { if (vin.addresses && vin.addresses.indexOf(this._getInternalAddressByIndex(c)) !== -1) {

View file

@ -1,5 +1,6 @@
import b58 from 'bs58check'; import b58 from 'bs58check';
import createHash from 'create-hash'; import createHash from 'create-hash';
import wif from 'wif';
import { BitcoinUnit, Chain } from '../../models/bitcoinUnits'; import { BitcoinUnit, Chain } from '../../models/bitcoinUnits';
import { CreateTransactionResult, CreateTransactionUtxo, Transaction, Utxo } from './types'; import { CreateTransactionResult, CreateTransactionUtxo, Transaction, Utxo } from './types';
@ -211,6 +212,17 @@ export class AbstractWallet {
setSecret(newSecret: string): this { setSecret(newSecret: string): this {
const origSecret = newSecret; const origSecret = newSecret;
// is it minikey https://en.bitcoin.it/wiki/Mini_private_key_format
// Starts with S, is 22 length or larger, is base58
if (newSecret.startsWith('S') && newSecret.length >= 22 && /^[1-9A-HJ-NP-Za-km-z]+$/.test(newSecret)) {
// minikey + ? hashed with SHA256 starts with 0x00 byte
if (createHash('sha256').update(`${newSecret}?`).digest('hex').startsWith('00')) {
// it is a valid minikey
newSecret = wif.encode(0x80, createHash('sha256').update(newSecret).digest(), false);
}
}
this.secret = newSecret.trim().replace('bitcoin:', '').replace('BITCOIN:', ''); this.secret = newSecret.trim().replace('bitcoin:', '').replace('BITCOIN:', '');
if (this.secret.startsWith('BC1')) this.secret = this.secret.toLowerCase(); if (this.secret.startsWith('BC1')) this.secret = this.secret.toLowerCase();

View file

@ -1,6 +1,7 @@
import bolt11 from 'bolt11'; import bolt11 from 'bolt11';
import { BitcoinUnit, Chain } from '../../models/bitcoinUnits'; import { BitcoinUnit, Chain } from '../../models/bitcoinUnits';
import { LegacyWallet } from './legacy-wallet'; import { LegacyWallet } from './legacy-wallet';
import { fetch } from '../../util/fetch';
export class LightningCustodianWallet extends LegacyWallet { export class LightningCustodianWallet extends LegacyWallet {
static readonly type = 'lightningCustodianWallet'; static readonly type = 'lightningCustodianWallet';

View file

@ -79,6 +79,20 @@ export type TransactionOutput = {
}; };
}; };
export interface DecodedInvoice {
destination: string;
payment_hash: string;
num_satoshis: number;
timestamp: number;
expiry: number;
description: string;
description_hash: string;
fallback_addr: string;
cltv_expiry: string;
route_hints: any[];
[key: string]: any;
}
export type LightningTransaction = { export type LightningTransaction = {
memo?: string; memo?: string;
type?: 'user_invoice' | 'payment_request' | 'bitcoind_tx' | 'paid_invoice'; type?: 'user_invoice' | 'payment_request' | 'bitcoind_tx' | 'paid_invoice';

View file

@ -5,7 +5,7 @@ import { useTheme } from './themes';
import ToolTipMenu from './TooltipMenu'; import ToolTipMenu from './TooltipMenu';
import { CommonToolTipActions } from '../typings/CommonToolTipActions'; import { CommonToolTipActions } from '../typings/CommonToolTipActions';
import loc from '../loc'; import loc from '../loc';
import { navigationRef } from '../NavigationService'; import { useExtendedNavigation } from '../hooks/useExtendedNavigation';
type AddWalletButtonProps = { type AddWalletButtonProps = {
onPress?: (event: GestureResponderEvent) => void; onPress?: (event: GestureResponderEvent) => void;
@ -23,21 +23,25 @@ const styles = StyleSheet.create({
const AddWalletButton: React.FC<AddWalletButtonProps> = ({ onPress }) => { const AddWalletButton: React.FC<AddWalletButtonProps> = ({ onPress }) => {
const { colors } = useTheme(); const { colors } = useTheme();
const navigation = useExtendedNavigation();
const stylesHook = StyleSheet.create({ const stylesHook = StyleSheet.create({
ball: { ball: {
backgroundColor: colors.buttonBackgroundColor, backgroundColor: colors.buttonBackgroundColor,
}, },
}); });
const onPressMenuItem = useCallback((action: string) => { const onPressMenuItem = useCallback(
switch (action) { (action: string) => {
case CommonToolTipActions.ImportWallet.id: switch (action) {
navigationRef.current?.navigate('AddWalletRoot', { screen: 'ImportWallet' }); case CommonToolTipActions.ImportWallet.id:
break; navigation.navigate('AddWalletRoot', { screen: 'ImportWallet' });
default: break;
break; default:
} break;
}, []); }
},
[navigation],
);
const actions = useMemo(() => [CommonToolTipActions.ImportWallet], []); const actions = useMemo(() => [CommonToolTipActions.ImportWallet], []);

View file

@ -1,23 +1,18 @@
import React, { useCallback } from 'react'; import React from 'react';
import { Keyboard, StyleProp, StyleSheet, TextInput, View, ViewStyle } from 'react-native'; import { StyleProp, StyleSheet, TextInput, View, ViewStyle } from 'react-native';
import loc from '../loc'; import loc from '../loc';
import { AddressInputScanButton } from './AddressInputScanButton'; import { AddressInputScanButton } from './AddressInputScanButton';
import { useTheme } from './themes'; import { useTheme } from './themes';
import DeeplinkSchemaMatch from '../class/deeplink-schema-match';
import triggerHapticFeedback, { HapticFeedbackTypes } from '../blue_modules/hapticFeedback';
interface AddressInputProps { interface AddressInputProps {
isLoading?: boolean; isLoading?: boolean;
address?: string; address?: string;
placeholder?: string; placeholder?: string;
onChangeText: (text: string) => void; onChangeText: (text: string) => void;
onBarScanned: (ret: { data?: any }) => void;
scanButtonTapped?: () => void;
launchedBy?: string;
editable?: boolean; editable?: boolean;
inputAccessoryViewID?: string; inputAccessoryViewID?: string;
onBlur?: () => void;
onFocus?: () => void; onFocus?: () => void;
onBlur?: () => void;
testID?: string; testID?: string;
style?: StyleProp<ViewStyle>; style?: StyleProp<ViewStyle>;
keyboardType?: keyboardType?:
@ -42,13 +37,10 @@ const AddressInput = ({
testID = 'AddressInput', testID = 'AddressInput',
placeholder = loc.send.details_address, placeholder = loc.send.details_address,
onChangeText, onChangeText,
onBarScanned,
scanButtonTapped = () => {},
launchedBy,
editable = true, editable = true,
inputAccessoryViewID, inputAccessoryViewID,
onBlur = () => {},
onFocus = () => {}, onFocus = () => {},
onBlur = () => {},
keyboardType = 'default', keyboardType = 'default',
style, style,
}: AddressInputProps) => { }: AddressInputProps) => {
@ -64,24 +56,6 @@ const AddressInput = ({
}, },
}); });
const validateAddressWithFeedback = useCallback((value: string) => {
const isBitcoinAddress = DeeplinkSchemaMatch.isBitcoinAddress(value);
const isLightningInvoice = DeeplinkSchemaMatch.isLightningInvoice(value);
const isValid = isBitcoinAddress || isLightningInvoice;
triggerHapticFeedback(isValid ? HapticFeedbackTypes.NotificationSuccess : HapticFeedbackTypes.NotificationError);
return {
isValid,
type: isBitcoinAddress ? 'bitcoin' : isLightningInvoice ? 'lightning' : 'invalid',
};
}, []);
const onBlurEditing = () => {
validateAddressWithFeedback(address);
onBlur();
Keyboard.dismiss();
};
return ( return (
<View style={[styles.root, stylesHook.root, style]}> <View style={[styles.root, stylesHook.root, style]}>
<TextInput <TextInput
@ -95,21 +69,13 @@ const AddressInput = ({
multiline={!editable} multiline={!editable}
inputAccessoryViewID={inputAccessoryViewID} inputAccessoryViewID={inputAccessoryViewID}
clearButtonMode="while-editing" clearButtonMode="while-editing"
onBlur={onBlurEditing}
onFocus={onFocus} onFocus={onFocus}
autoCapitalize="none" autoCapitalize="none"
autoCorrect={false} autoCorrect={false}
keyboardType={keyboardType} keyboardType={keyboardType}
onBlur={onBlur}
/> />
{editable ? ( {editable ? <AddressInputScanButton isLoading={isLoading} onChangeText={onChangeText} /> : null}
<AddressInputScanButton
isLoading={isLoading}
launchedBy={launchedBy}
scanButtonTapped={scanButtonTapped}
onBarScanned={onBarScanned}
onChangeText={onChangeText}
/>
) : null}
</View> </View>
); );
}; };

View file

@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useMemo } from 'react'; import React, { useCallback, useMemo } from 'react';
import { Image, Keyboard, Platform, StyleSheet, Text } from 'react-native'; import { Image, Keyboard, Platform, StyleSheet, Text } from 'react-native';
import Clipboard from '@react-native-clipboard/clipboard'; import Clipboard from '@react-native-clipboard/clipboard';
import ToolTipMenu from './TooltipMenu'; import ToolTipMenu from './TooltipMenu';
@ -9,33 +9,27 @@ import { useTheme } from './themes';
import RNQRGenerator from 'rn-qr-generator'; import RNQRGenerator from 'rn-qr-generator';
import { CommonToolTipActions } from '../typings/CommonToolTipActions'; import { CommonToolTipActions } from '../typings/CommonToolTipActions';
import { useSettings } from '../hooks/context/useSettings'; import { useSettings } from '../hooks/context/useSettings';
import { useRoute } from '@react-navigation/native';
import { useExtendedNavigation } from '../hooks/useExtendedNavigation'; import { useExtendedNavigation } from '../hooks/useExtendedNavigation';
interface AddressInputScanButtonProps { interface AddressInputScanButtonProps {
isLoading: boolean; isLoading?: boolean;
launchedBy?: string;
scanButtonTapped: () => void;
onBarScanned: (ret: { data?: any }) => void;
onChangeText: (text: string) => void; onChangeText: (text: string) => void;
} type?: 'default' | 'link';
testID?: string;
interface RouteParams { beforePress?: () => Promise<void> | void;
onBarScanned?: any;
} }
export const AddressInputScanButton = ({ export const AddressInputScanButton = ({
isLoading, isLoading,
launchedBy,
scanButtonTapped,
onBarScanned,
onChangeText, onChangeText,
type = 'default',
testID = 'BlueAddressInputScanQrButton',
beforePress,
}: AddressInputScanButtonProps) => { }: AddressInputScanButtonProps) => {
const { colors } = useTheme(); const { colors } = useTheme();
const { isClipboardGetContentEnabled } = useSettings(); const { isClipboardGetContentEnabled } = useSettings();
const navigation = useExtendedNavigation(); const navigation = useExtendedNavigation();
const params = useRoute().params as RouteParams;
const stylesHook = StyleSheet.create({ const stylesHook = StyleSheet.create({
scan: { scan: {
backgroundColor: colors.scanLabel, backgroundColor: colors.scanLabel,
@ -46,16 +40,17 @@ export const AddressInputScanButton = ({
}); });
const toolTipOnPress = useCallback(async () => { const toolTipOnPress = useCallback(async () => {
await scanButtonTapped(); if (beforePress) {
await beforePress();
}
Keyboard.dismiss(); Keyboard.dismiss();
navigation.navigate('ScanQRCode', { navigation.navigate('ScanQRCode', {
showFileImportButton: true, showFileImportButton: true,
}); });
}, [navigation, scanButtonTapped]); }, [navigation, beforePress]);
const actions = useMemo(() => { const actions = useMemo(() => {
const availableActions = [ const availableActions = [
CommonToolTipActions.ScanQR,
CommonToolTipActions.ChoosePhoto, CommonToolTipActions.ChoosePhoto,
CommonToolTipActions.ImportFile, CommonToolTipActions.ImportFile,
{ {
@ -67,20 +62,10 @@ export const AddressInputScanButton = ({
return availableActions; return availableActions;
}, [isClipboardGetContentEnabled]); }, [isClipboardGetContentEnabled]);
useEffect(() => {
const data = params.onBarScanned;
if (data) {
onBarScanned({ data });
navigation.setParams({ onBarScanned: undefined });
}
});
const onMenuItemPressed = useCallback( const onMenuItemPressed = useCallback(
async (action: string) => { async (action: string) => {
if (onBarScanned === undefined) throw new Error('onBarScanned is required');
switch (action) { switch (action) {
case CommonToolTipActions.ScanQR.id: case CommonToolTipActions.ScanQR.id:
scanButtonTapped();
navigation.navigate('ScanQRCode', { navigation.navigate('ScanQRCode', {
showFileImportButton: true, showFileImportButton: true,
}); });
@ -147,7 +132,7 @@ export const AddressInputScanButton = ({
} }
Keyboard.dismiss(); Keyboard.dismiss();
}, },
[navigation, onBarScanned, onChangeText, scanButtonTapped], [navigation, onChangeText],
); );
const buttonStyle = useMemo(() => [styles.scan, stylesHook.scan], [stylesHook.scan]); const buttonStyle = useMemo(() => [styles.scan, stylesHook.scan], [stylesHook.scan]);
@ -157,21 +142,29 @@ export const AddressInputScanButton = ({
actions={actions} actions={actions}
isButton isButton
onPressMenuItem={onMenuItemPressed} onPressMenuItem={onMenuItemPressed}
testID="BlueAddressInputScanQrButton" testID={testID}
disabled={isLoading} disabled={isLoading}
onPress={toolTipOnPress} onPress={toolTipOnPress}
buttonStyle={buttonStyle} buttonStyle={type === 'default' ? buttonStyle : undefined}
accessibilityLabel={loc.send.details_scan} accessibilityLabel={loc.send.details_scan}
accessibilityHint={loc.send.details_scan_hint} accessibilityHint={loc.send.details_scan_hint}
> >
<Image source={require('../img/scan-white.png')} accessible={false} /> {type === 'default' ? (
<Text style={[styles.scanText, stylesHook.scanText]} accessible={false}> <>
{loc.send.details_scan} <Image source={require('../img/scan-white.png')} accessible={false} />
</Text> <Text style={[styles.scanText, stylesHook.scanText]} accessible={false}>
{loc.send.details_scan}
</Text>
</>
) : (
<Text style={[styles.linkText, { color: colors.foregroundColor }]}>{loc.wallets.import_scan_qr}</Text>
)}
</ToolTipMenu> </ToolTipMenu>
); );
}; };
AddressInputScanButton.displayName = 'AddressInputScanButton';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
scan: { scan: {
height: 36, height: 36,
@ -186,4 +179,8 @@ const styles = StyleSheet.create({
scanText: { scanText: {
marginLeft: 4, marginLeft: 4,
}, },
linkText: {
textAlign: 'center',
fontSize: 16,
},
}); });

View file

@ -146,7 +146,9 @@ class AmountInput extends Component {
textInput = React.createRef(); textInput = React.createRef();
handleTextInputOnPress = () => { handleTextInputOnPress = () => {
this.textInput.current.focus(); if (this.textInput && this.textInput.current && typeof this.textInput.current.focus === 'function') {
this.textInput.current.focus();
}
}; };
handleChangeText = text => { handleChangeText = text => {
@ -258,7 +260,11 @@ class AmountInput extends Component {
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={loc._.enter_amount} accessibilityLabel={loc._.enter_amount}
disabled={this.props.pointerEvents === 'none'} disabled={this.props.pointerEvents === 'none'}
onPress={() => this.textInput.focus()} onPress={() => {
if (this.textInput && this.textInput.current && typeof this.textInput.current.focus === 'function') {
this.textInput.current.focus();
}
}}
> >
<> <>
<View style={styles.root}> <View style={styles.root}>

View file

@ -1,5 +1,5 @@
import React, { forwardRef, useImperativeHandle, useRef, ReactElement, ComponentType } from 'react'; import React, { forwardRef, useImperativeHandle, useRef, ReactElement, ComponentType } from 'react';
import { SheetSize, SizeInfo, TrueSheet, TrueSheetProps } from '@lodev09/react-native-true-sheet'; import { SheetSize, SizeChangeEvent, TrueSheet, TrueSheetProps } from '@lodev09/react-native-true-sheet';
import { Keyboard, Image, StyleSheet, View, TouchableOpacity, Platform, GestureResponderEvent, Text } from 'react-native'; import { Keyboard, Image, StyleSheet, View, TouchableOpacity, Platform, GestureResponderEvent, Text } from 'react-native';
import SaveFileButton from './SaveFileButton'; import SaveFileButton from './SaveFileButton';
import { useTheme } from './themes'; import { useTheme } from './themes';
@ -14,7 +14,7 @@ interface BottomModalProps extends TrueSheetProps {
footer?: ReactElement | ComponentType<any> | null; footer?: ReactElement | ComponentType<any> | null;
footerDefaultMargins?: boolean | number; footerDefaultMargins?: boolean | number;
onPresent?: () => void; onPresent?: () => void;
onSizeChange?: (size: SizeInfo) => void; onSizeChange?: (event: SizeChangeEvent) => void;
showCloseButton?: boolean; showCloseButton?: boolean;
shareContent?: BottomModalShareContent; shareContent?: BottomModalShareContent;
shareButtonOnPress?: (event: GestureResponderEvent) => void; shareButtonOnPress?: (event: GestureResponderEvent) => void;

View file

@ -1,10 +1,13 @@
import React, { useState, useRef } from 'react'; import React, { useState, useRef } from 'react';
import { Animated, StyleSheet, TouchableOpacity, View } from 'react-native'; import { Animated, Platform, StyleSheet, TouchableOpacity, View } from 'react-native';
// @ts-ignore: no declaration file yet
import { Camera, CameraApi, CameraType, Orientation } from 'react-native-camera-kit'; import { Camera, CameraApi, CameraType, Orientation } from 'react-native-camera-kit';
import loc from '../loc'; import loc from '../loc';
import { Icon } from '@rneui/base'; import { Icon } from '@rneui/base';
import { OnOrientationChangeData, OnReadCodeData } from 'react-native-camera-kit/dist/CameraProps';
import { triggerSelectionHapticFeedback } from '../blue_modules/hapticFeedback'; import { triggerSelectionHapticFeedback } from '../blue_modules/hapticFeedback';
import { isDesktop } from '../blue_modules/environment';
// @ts-ignore: no declaration file yet
import { OnOrientationChangeData, OnReadCodeData } from 'react-native-camera-kit/dist/CameraProps';
interface CameraScreenProps { interface CameraScreenProps {
onCancelButtonPress: () => void; onCancelButtonPress: () => void;
@ -46,8 +49,8 @@ const CameraScreen: React.FC<CameraScreenProps> = ({
// For real phone apps, lock your UI orientation using a library like 'react-native-orientation-locker' // For real phone apps, lock your UI orientation using a library like 'react-native-orientation-locker'
const rotateUi = true; const rotateUi = true;
const uiRotation = orientationAnim.interpolate({ const uiRotation = orientationAnim.interpolate({
inputRange: [1, 4], inputRange: [1, 2, 3, 4],
outputRange: ['180deg', '-90deg'], outputRange: ['180deg', '90deg', '0deg', '-90deg'],
}); });
const uiRotationStyle = rotateUi ? { transform: [{ rotate: uiRotation }] } : {}; const uiRotationStyle = rotateUi ? { transform: [{ rotate: uiRotation }] } : {};
@ -95,68 +98,104 @@ const CameraScreen: React.FC<CameraScreenProps> = ({
return ( return (
<View style={styles.screen}> <View style={styles.screen}>
<View style={styles.topButtons}> {/* Render top buttons only if not desktop as they would not be relevant */}
<TouchableOpacity style={styles.topButton} onPress={onSetTorch}> {!isDesktop && (
<Animated.View style={[styles.topButtonImg, uiRotationStyle]}> <View style={styles.topButtons}>
<Icon name={torchMode ? 'flashlight-on' : 'flashlight-off'} type="font-awesome-6" color="#ffffff" /> <TouchableOpacity style={[styles.topButton, uiRotationStyle, torchMode ? styles.activeTorch : {}]} onPress={onSetTorch}>
</Animated.View> <Animated.View style={styles.topButtonImg}>
</TouchableOpacity> {Platform.OS === 'ios' ? (
<View style={styles.rightButtonsContainer}> <Icon name={torchMode ? 'flashlight-on' : 'flashlight-off'} type="font-awesome-6" color={torchMode ? '#000' : '#fff'} />
{showImagePickerButton && ( ) : (
<TouchableOpacity <Icon name={torchMode ? 'flash-on' : 'flash-off'} type="ionicons" color={torchMode ? '#000' : '#fff'} />
accessibilityRole="button" )}
accessibilityLabel={loc._.pick_image} </Animated.View>
style={[styles.topButton, styles.spacing, uiRotationStyle]} </TouchableOpacity>
onPress={onImagePickerButtonPress} <View style={styles.rightButtonsContainer}>
> {showImagePickerButton && (
<Animated.View style={[styles.topButtonImg, uiRotationStyle]}> <TouchableOpacity
<Icon name="image" type="font-awesome" color="#ffffff" /> accessibilityRole="button"
</Animated.View> accessibilityLabel={loc._.pick_image}
</TouchableOpacity> style={[styles.topButton, styles.spacing, uiRotationStyle]}
)} onPress={onImagePickerButtonPress}
{showFilePickerButton && ( >
<TouchableOpacity <Animated.View style={styles.topButtonImg}>
accessibilityRole="button" <Icon name="image" type="font-awesome" color="#ffffff" />
accessibilityLabel={loc._.pick_file} </Animated.View>
style={[styles.topButton, styles.spacing, uiRotationStyle]} </TouchableOpacity>
onPress={onFilePickerButtonPress} )}
> {showFilePickerButton && (
<Animated.View style={[styles.topButtonImg, uiRotationStyle]}> <TouchableOpacity
<Icon name="file-import" type="font-awesome-5" color="#ffffff" /> accessibilityRole="button"
</Animated.View> accessibilityLabel={loc._.pick_file}
</TouchableOpacity> style={[styles.topButton, styles.spacing, uiRotationStyle]}
)} onPress={onFilePickerButtonPress}
>
<Animated.View style={styles.topButtonImg}>
<Icon name="file-import" type="font-awesome-5" color="#ffffff" />
</Animated.View>
</TouchableOpacity>
)}
</View>
</View> </View>
</View> )}
<View style={styles.cameraContainer}> <View style={styles.cameraContainer}>
<Camera <Camera
ref={cameraRef} ref={cameraRef}
style={styles.cameraPreview} style={styles.cameraPreview}
cameraType={cameraType} cameraType={cameraType}
resetFocusWhenMotionDetected
zoom={zoom}
maxZoom={10}
scanBarcode scanBarcode
resizeMode="cover" resizeMode="cover"
onZoom={handleZoom}
onReadCode={handleReadCode} onReadCode={handleReadCode}
torchMode={torchMode ? 'on' : 'off'} torchMode={torchMode ? 'on' : 'off'}
shutterPhotoSound resetFocusWhenMotionDetected
maxPhotoQualityPrioritization="quality" zoom={zoom}
onZoom={handleZoom}
maxZoom={10}
onOrientationChange={handleOrientationChange} onOrientationChange={handleOrientationChange}
/> />
</View> </View>
<View style={styles.bottomButtons}> <View style={styles.bottomButtons}>
<TouchableOpacity onPress={onCancelButtonPress}> <TouchableOpacity onPress={onCancelButtonPress}>
<Animated.Text style={[styles.backTextStyle, uiRotationStyle]}>{loc._.cancel}</Animated.Text> <Animated.Text style={[styles.backTextStyle, uiRotationStyle]}>{loc._.cancel}</Animated.Text>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity style={styles.bottomButton} onPress={onSwitchCameraPressed}> {isDesktop ? (
<Animated.View style={[styles.topButtonImg, uiRotationStyle]}> <View style={styles.rightButtonsContainer}>
<Icon name="cameraswitch" type="font-awesome-6" color="#ffffff" /> {showImagePickerButton && (
</Animated.View> <TouchableOpacity
</TouchableOpacity> accessibilityRole="button"
accessibilityLabel={loc._.pick_image}
style={[styles.bottomButton, styles.spacing, uiRotationStyle]}
onPress={onImagePickerButtonPress}
>
<Animated.View style={styles.topButtonImg}>
<Icon name="image" type="font-awesome" color="#ffffff" />
</Animated.View>
</TouchableOpacity>
)}
{showFilePickerButton && (
<TouchableOpacity
accessibilityRole="button"
accessibilityLabel={loc._.pick_file}
style={[styles.bottomButton, styles.spacing, uiRotationStyle]}
onPress={onFilePickerButtonPress}
>
<Animated.View style={styles.topButtonImg}>
<Icon name="file-import" type="font-awesome-5" color="#ffffff" />
</Animated.View>
</TouchableOpacity>
)}
</View>
) : (
<TouchableOpacity style={[styles.bottomButton, uiRotationStyle]} onPress={onSwitchCameraPressed}>
<Animated.View style={[styles.topButtonImg, uiRotationStyle]}>
{Platform.OS === 'ios' ? (
<Icon name="cameraswitch" type="font-awesome-6" color="#ffffff" />
) : (
<Icon name={cameraType === CameraType.Back ? 'camera-rear' : 'camera-front'} type="ionicons" color="#ffffff" />
)}
</Animated.View>
</TouchableOpacity>
)}
</View> </View>
</View> </View>
); );
@ -165,6 +204,9 @@ const CameraScreen: React.FC<CameraScreenProps> = ({
export default CameraScreen; export default CameraScreen;
const styles = StyleSheet.create({ const styles = StyleSheet.create({
activeTorch: {
backgroundColor: '#fff',
},
screen: { screen: {
height: '100%', height: '100%',
backgroundColor: '#000000', backgroundColor: '#000000',

View file

@ -1,14 +1,15 @@
import React, { createContext, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import React, { createContext, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { InteractionManager } from 'react-native'; import { InteractionManager, LayoutAnimation } from 'react-native';
import A from '../../blue_modules/analytics'; import A from '../../blue_modules/analytics';
import { BlueApp as BlueAppClass, LegacyWallet, TCounterpartyMetadata, TTXMetadata, WatchOnlyWallet } from '../../class'; import { BlueApp as BlueAppClass, LegacyWallet, TCounterpartyMetadata, TTXMetadata, WatchOnlyWallet } from '../../class';
import type { TWallet } from '../../class/wallets/types'; import type { TWallet } from '../../class/wallets/types';
import presentAlert from '../../components/Alert'; import presentAlert from '../../components/Alert';
import loc from '../../loc'; import loc, { formatBalanceWithoutSuffix } from '../../loc';
import * as BlueElectrum from '../../blue_modules/BlueElectrum'; import * as BlueElectrum from '../../blue_modules/BlueElectrum';
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback'; import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
import { startAndDecrypt } from '../../blue_modules/start-and-decrypt'; import { startAndDecrypt } from '../../blue_modules/start-and-decrypt';
import { majorTomToGroundControl } from '../../blue_modules/notifications'; import { isNotificationsEnabled, majorTomToGroundControl, unsubscribe } from '../../blue_modules/notifications';
import { BitcoinUnit } from '../../models/bitcoinUnits';
const BlueApp = BlueAppClass.getInstance(); const BlueApp = BlueAppClass.getInstance();
@ -49,6 +50,8 @@ interface StorageContextType {
cachedPassword: typeof BlueApp.cachedPassword; cachedPassword: typeof BlueApp.cachedPassword;
getItem: typeof BlueApp.getItem; getItem: typeof BlueApp.getItem;
setItem: typeof BlueApp.setItem; setItem: typeof BlueApp.setItem;
handleWalletDeletion: (walletID: string, forceDelete?: boolean) => Promise<boolean>;
confirmWalletDeletion: (wallet: any, onConfirmed: () => void) => void;
} }
export enum WalletTransactionsStatus { export enum WalletTransactionsStatus {
@ -99,6 +102,120 @@ export const StorageProvider = ({ children }: { children: React.ReactNode }) =>
setWallets([...BlueApp.getWallets()]); setWallets([...BlueApp.getWallets()]);
}, []); }, []);
const handleWalletDeletion = useCallback(
async (walletID: string, forceDelete = false): Promise<boolean> => {
console.debug(`handleWalletDeletion: invoked for walletID ${walletID}`);
const wallet = wallets.find(w => w.getID() === walletID);
if (!wallet) {
console.warn(`handleWalletDeletion: wallet not found for ${walletID}`);
return false;
}
if (forceDelete) {
deleteWallet(wallet);
await saveToDisk(true);
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
return true;
}
let isNotificationsSettingsEnabled = false;
try {
isNotificationsSettingsEnabled = await isNotificationsEnabled();
} catch (error) {
console.error(`handleWalletDeletion: error checking notifications for wallet ${walletID}`, error);
return await new Promise<boolean>(resolve => {
presentAlert({
title: loc.errors.error,
message: loc.wallets.details_delete_wallet_error_message,
buttons: [
{
text: loc.wallets.details_delete_anyway,
onPress: async () => {
const result = await handleWalletDeletion(walletID, true);
resolve(result);
},
style: 'destructive',
},
{
text: loc.wallets.list_tryagain,
onPress: async () => {
const result = await handleWalletDeletion(walletID);
resolve(result);
},
},
{
text: loc._.cancel,
onPress: () => resolve(false),
style: 'cancel',
},
],
options: { cancelable: false },
});
});
}
try {
if (isNotificationsSettingsEnabled) {
const externalAddresses = wallet.getAllExternalAddresses();
if (externalAddresses.length > 0) {
console.debug(`handleWalletDeletion: unsubscribing addresses for wallet ${walletID}`);
try {
await unsubscribe(externalAddresses, [], []);
console.debug(`handleWalletDeletion: unsubscribe succeeded for wallet ${walletID}`);
} catch (unsubscribeError) {
console.error(`handleWalletDeletion: unsubscribe failed for wallet ${walletID}`, unsubscribeError);
presentAlert({
title: loc.errors.error,
message: loc.wallets.details_delete_wallet_error_message,
buttons: [{ text: loc._.ok, onPress: () => {} }],
options: { cancelable: false },
});
return false;
}
}
}
deleteWallet(wallet);
console.debug(`handleWalletDeletion: wallet ${walletID} deleted successfully`);
await saveToDisk(true);
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
return true;
} catch (e: unknown) {
console.error(`handleWalletDeletion: encountered error for wallet ${walletID}`, e);
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
return await new Promise<boolean>(resolve => {
presentAlert({
title: loc.errors.error,
message: loc.wallets.details_delete_wallet_error_message,
buttons: [
{
text: loc.wallets.details_delete_anyway,
onPress: async () => {
const result = await handleWalletDeletion(walletID, true);
resolve(result);
},
style: 'destructive',
},
{
text: loc.wallets.list_tryagain,
onPress: async () => {
const result = await handleWalletDeletion(walletID);
resolve(result);
},
},
{
text: loc._.cancel,
onPress: () => resolve(false),
style: 'cancel',
},
],
options: { cancelable: false },
});
});
}
},
[deleteWallet, saveToDisk, wallets],
);
const resetWallets = useCallback(() => { const resetWallets = useCallback(() => {
setWallets(BlueApp.getWallets()); setWallets(BlueApp.getWallets());
}, []); }, []);
@ -120,56 +237,72 @@ export const StorageProvider = ({ children }: { children: React.ReactNode }) =>
} }
}, [walletsInitialized]); }, [walletsInitialized]);
// Add a refresh lock to prevent concurrent refreshes
const refreshingRef = useRef<boolean>(false);
const refreshAllWalletTransactions = useCallback( const refreshAllWalletTransactions = useCallback(
async (lastSnappedTo?: number, showUpdateStatusIndicator: boolean = true) => { async (lastSnappedTo?: number, showUpdateStatusIndicator: boolean = true) => {
const TIMEOUT_DURATION = 30000; if (refreshingRef.current) {
console.debug('[refreshAllWalletTransactions] Refresh already in progress');
return;
}
refreshingRef.current = true;
await new Promise<void>(resolve => InteractionManager.runAfterInteractions(() => resolve()));
const TIMEOUT_DURATION = 30000;
const timeoutPromise = new Promise<never>((_resolve, reject) => const timeoutPromise = new Promise<never>((_resolve, reject) =>
setTimeout(() => { setTimeout(() => {
reject(new Error('refreshAllWalletTransactions: Timeout reached')); console.debug('[refreshAllWalletTransactions] Timeout reached');
reject(new Error('Timeout reached'));
}, TIMEOUT_DURATION), }, TIMEOUT_DURATION),
); );
const mainLogicPromise = new Promise<void>((resolve, reject) => { try {
InteractionManager.runAfterInteractions(async () => { if (showUpdateStatusIndicator) {
let noErr = true; console.debug('[refreshAllWalletTransactions] Setting wallet transaction status to ALL');
try { setWalletTransactionUpdateStatus(WalletTransactionsStatus.ALL);
await BlueElectrum.waitTillConnected(); }
if (showUpdateStatusIndicator) { console.debug('[refreshAllWalletTransactions] Waiting for connectivity...');
setWalletTransactionUpdateStatus(WalletTransactionsStatus.ALL); await BlueElectrum.waitTillConnected();
} console.debug('[refreshAllWalletTransactions] Connected to Electrum');
const paymentCodesStart = Date.now();
await BlueApp.fetchSenderPaymentCodes(lastSnappedTo);
const paymentCodesEnd = Date.now();
console.debug('fetch payment codes took', (paymentCodesEnd - paymentCodesStart) / 1000, 'sec');
// Restore fetch payment codes timing measurement
if (typeof BlueApp.fetchSenderPaymentCodes === 'function') {
const codesStart = Date.now();
console.debug('[refreshAllWalletTransactions] Fetching sender payment codes');
await BlueApp.fetchSenderPaymentCodes(lastSnappedTo);
const codesEnd = Date.now();
console.debug('[refreshAllWalletTransactions] fetch payment codes took', (codesEnd - codesStart) / 1000, 'sec');
} else {
console.warn('[refreshAllWalletTransactions] fetchSenderPaymentCodes is not available');
}
console.debug('[refreshAllWalletTransactions] Fetching wallet balances and transactions');
await Promise.race([
(async () => {
const balanceStart = Date.now(); const balanceStart = Date.now();
await BlueApp.fetchWalletBalances(lastSnappedTo); await BlueApp.fetchWalletBalances(lastSnappedTo);
const balanceEnd = Date.now(); const balanceEnd = Date.now();
console.debug('fetch balance took', (balanceEnd - balanceStart) / 1000, 'sec'); console.debug('[refreshAllWalletTransactions] fetch balance took', (balanceEnd - balanceStart) / 1000, 'sec');
const start = Date.now(); const txStart = Date.now();
await BlueApp.fetchWalletTransactions(lastSnappedTo); await BlueApp.fetchWalletTransactions(lastSnappedTo);
const end = Date.now(); const txEnd = Date.now();
console.debug('fetch tx took', (end - start) / 1000, 'sec'); console.debug('[refreshAllWalletTransactions] fetch tx took', (txEnd - txStart) / 1000, 'sec');
} catch (err) {
noErr = false;
console.error(err);
reject(err);
} finally {
setWalletTransactionUpdateStatus(WalletTransactionsStatus.NONE);
}
if (noErr) await saveToDisk();
resolve();
});
});
try { console.debug('[refreshAllWalletTransactions] Saving data to disk');
await Promise.race([mainLogicPromise, timeoutPromise]); await saveToDisk();
} catch (err) { })(),
console.error('Error in refreshAllWalletTransactions:', err); timeoutPromise,
]);
console.debug('[refreshAllWalletTransactions] Refresh completed successfully');
} catch (error) {
console.error('[refreshAllWalletTransactions] Error:', error);
} finally { } finally {
console.debug('[refreshAllWalletTransactions] Resetting wallet transaction status and refresh lock');
setWalletTransactionUpdateStatus(WalletTransactionsStatus.NONE); setWalletTransactionUpdateStatus(WalletTransactionsStatus.NONE);
refreshingRef.current = false;
} }
}, },
[saveToDisk], [saveToDisk],
@ -182,24 +315,26 @@ export const StorageProvider = ({ children }: { children: React.ReactNode }) =>
let noErr = true; let noErr = true;
try { try {
if (Date.now() - (_lastTimeTriedToRefetchWallet[walletID] || 0) < 5000) { if (Date.now() - (_lastTimeTriedToRefetchWallet[walletID] || 0) < 5000) {
console.debug('Re-fetch wallet happens too fast; NOP'); console.debug('[fetchAndSaveWalletTransactions] Re-fetch wallet happens too fast; NOP');
return; return;
} }
_lastTimeTriedToRefetchWallet[walletID] = Date.now(); _lastTimeTriedToRefetchWallet[walletID] = Date.now();
await BlueElectrum.waitTillConnected(); await BlueElectrum.waitTillConnected();
setWalletTransactionUpdateStatus(walletID); setWalletTransactionUpdateStatus(walletID);
const balanceStart = Date.now(); const balanceStart = Date.now();
await BlueApp.fetchWalletBalances(index); await BlueApp.fetchWalletBalances(index);
const balanceEnd = Date.now(); const balanceEnd = Date.now();
console.debug('fetch balance took', (balanceEnd - balanceStart) / 1000, 'sec'); console.debug('[fetchAndSaveWalletTransactions] fetch balance took', (balanceEnd - balanceStart) / 1000, 'sec');
const start = Date.now();
const txStart = Date.now();
await BlueApp.fetchWalletTransactions(index); await BlueApp.fetchWalletTransactions(index);
const end = Date.now(); const txEnd = Date.now();
console.debug('fetch tx took', (end - start) / 1000, 'sec'); console.debug('[fetchAndSaveWalletTransactions] fetch tx took', (txEnd - txStart) / 1000, 'sec');
} catch (err) { } catch (err) {
noErr = false; noErr = false;
console.error(err); console.error('[fetchAndSaveWalletTransactions] Error:', err);
} finally { } finally {
setWalletTransactionUpdateStatus(WalletTransactionsStatus.NONE); setWalletTransactionUpdateStatus(WalletTransactionsStatus.NONE);
} }
@ -217,10 +352,10 @@ export const StorageProvider = ({ children }: { children: React.ReactNode }) =>
return; return;
} }
const emptyWalletLabel = new LegacyWallet().getLabel(); const emptyWalletLabel = new LegacyWallet().getLabel();
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
if (w.getLabel() === emptyWalletLabel) w.setLabel(loc.wallets.import_imported + ' ' + w.typeReadable); if (w.getLabel() === emptyWalletLabel) w.setLabel(loc.wallets.import_imported + ' ' + w.typeReadable);
w.setUserHasSavedExport(true); w.setUserHasSavedExport(true);
addWallet(w); addWallet(w);
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
await saveToDisk(); await saveToDisk();
A(A.ENUM.CREATED_WALLET); A(A.ENUM.CREATED_WALLET);
presentAlert({ presentAlert({
@ -239,6 +374,36 @@ export const StorageProvider = ({ children }: { children: React.ReactNode }) =>
[wallets, addWallet, saveToDisk], [wallets, addWallet, saveToDisk],
); );
function confirmWalletDeletion(wallet: any, onConfirmed: () => void) {
triggerHapticFeedback(HapticFeedbackTypes.NotificationWarning);
try {
const balance = formatBalanceWithoutSuffix(wallet.getBalance(), BitcoinUnit.SATS, true);
presentAlert({
title: loc.wallets.details_delete_wallet,
message: loc.formatString(loc.wallets.details_del_wb_q, { balance }),
buttons: [
{
text: loc.wallets.details_delete,
onPress: () => {
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
onConfirmed();
},
style: 'destructive',
},
{
text: loc._.cancel,
onPress: () => {},
style: 'cancel',
},
],
options: { cancelable: false },
});
} catch (error) {
// Handle error silently if needed
}
}
const value: StorageContextType = useMemo( const value: StorageContextType = useMemo(
() => ({ () => ({
wallets, wallets,
@ -274,6 +439,8 @@ export const StorageProvider = ({ children }: { children: React.ReactNode }) =>
isPasswordInUse: BlueApp.isPasswordInUse, isPasswordInUse: BlueApp.isPasswordInUse,
walletTransactionUpdateStatus, walletTransactionUpdateStatus,
setWalletTransactionUpdateStatus, setWalletTransactionUpdateStatus,
handleWalletDeletion,
confirmWalletDeletion,
}), }),
[ [
wallets, wallets,
@ -291,7 +458,7 @@ export const StorageProvider = ({ children }: { children: React.ReactNode }) =>
refreshAllWalletTransactions, refreshAllWalletTransactions,
resetWallets, resetWallets,
walletTransactionUpdateStatus, walletTransactionUpdateStatus,
setWalletTransactionUpdateStatus, handleWalletDeletion,
], ],
); );

View file

@ -1,14 +1,18 @@
import React, { forwardRef, ReactNode, useEffect, useRef, useState } from 'react'; import React, { forwardRef, ReactNode, useEffect, useRef, useState, useCallback } from 'react';
import { Animated, Dimensions, PixelRatio, StyleSheet, Text, TouchableOpacity, useWindowDimensions, View } from 'react-native'; import { Animated, Dimensions, PixelRatio, StyleSheet, Text, TouchableOpacity, useWindowDimensions, View } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { useTheme } from './themes'; import { useTheme } from './themes';
const BORDER_RADIUS = 8; const BORDER_RADIUS = 8;
const PADDINGS = 24; const PADDINGS = 24;
const ICON_MARGIN = 7; const ICON_MARGIN = 7;
const cStyles = StyleSheet.create({ const buttonFontSize = (() => {
const baseSize = PixelRatio.roundToNearestPixel(Dimensions.get('window').width / 26);
return Math.min(22, baseSize);
})();
const containerStyles = StyleSheet.create({
root: { root: {
alignSelf: 'center', alignSelf: 'center',
height: '6.9%', height: '6.9%',
@ -26,6 +30,27 @@ const cStyles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
overflow: 'hidden', overflow: 'hidden',
}, },
rootRound: {
borderRadius: 9999,
},
});
const buttonStyles = StyleSheet.create({
root: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
overflow: 'hidden',
},
icon: {
alignItems: 'center',
},
text: {
fontSize: buttonFontSize,
fontWeight: '600',
marginLeft: ICON_MARGIN,
backgroundColor: 'transparent',
},
}); });
interface FContainerProps { interface FContainerProps {
@ -51,93 +76,79 @@ export const FContainer = forwardRef<View, FContainerProps>((props, ref) => {
}).start(); }).start();
}, [height, slideAnimation]); }, [height, slideAnimation]);
const computeNewWidth = useCallback(
(layoutWidth: number, totalChildren: number) => {
const maxWidth = width - BORDER_RADIUS - 140;
const paddedWidth = Math.ceil(layoutWidth + PADDINGS * 2);
let calculatedWidth = paddedWidth * totalChildren > maxWidth ? Math.floor(maxWidth / totalChildren) : paddedWidth;
if (totalChildren === 1 && calculatedWidth < 90) calculatedWidth = 90;
return calculatedWidth;
},
[width],
);
const onLayout = (event: { nativeEvent: { layout: { width: number } } }) => { const onLayout = (event: { nativeEvent: { layout: { width: number } } }) => {
if (layoutCalculated.current) return; if (layoutCalculated.current) return;
const maxWidth = width - BORDER_RADIUS - 140; const { width: layoutWidth } = event.nativeEvent.layout;
const layoutWidth = event.nativeEvent.layout.width; const totalChildren = React.Children.toArray(props.children).filter(Boolean).length;
const withPaddings = Math.ceil(layoutWidth + PADDINGS * 2); setNewWidth(computeNewWidth(layoutWidth, totalChildren));
const len = React.Children.toArray(props.children).filter(Boolean).length;
let newW = withPaddings * len > maxWidth ? Math.floor(maxWidth / len) : withPaddings;
if (len === 1 && newW < 90) newW = 90;
setNewWidth(newW);
layoutCalculated.current = true; layoutCalculated.current = true;
}; };
const renderChild = (child: ReactNode, index: number, array: ReactNode[]): ReactNode => {
if (typeof child === 'string') {
return (
<View key={index} style={{ width: newWidth }}>
<Text adjustsFontSizeToFit numberOfLines={1}>
{child}
</Text>
</View>
);
}
return React.cloneElement(child as React.ReactElement<any>, {
width: newWidth,
key: index,
first: index === 0,
last: index === array.length - 1,
singleChild: array.length === 1,
});
};
const totalChildren = React.Children.toArray(props.children).filter(Boolean).length;
return ( return (
<Animated.View <Animated.View
ref={ref} ref={ref}
onLayout={onLayout} onLayout={onLayout}
style={[ style={[
cStyles.root, containerStyles.root,
props.inline ? cStyles.rootInline : cStyles.rootAbsolute, props.inline ? containerStyles.rootInline : containerStyles.rootAbsolute,
bottomInsets, bottomInsets,
newWidth ? cStyles.rootPost : cStyles.rootPre, newWidth ? containerStyles.rootPost : containerStyles.rootPre,
totalChildren === 1 ? containerStyles.rootRound : null,
{ transform: [{ translateY: slideAnimation }] }, { transform: [{ translateY: slideAnimation }] },
]} ]}
> >
{newWidth {newWidth ? React.Children.toArray(props.children).filter(Boolean).map(renderChild) : props.children}
? React.Children.toArray(props.children)
.filter(Boolean)
.map((child, index, array) => {
if (typeof child === 'string') {
return (
<View key={index} style={{ width: newWidth }}>
<Text adjustsFontSizeToFit numberOfLines={1}>
{child}
</Text>
</View>
);
}
return React.cloneElement(child as React.ReactElement<any>, {
width: newWidth,
key: index,
first: index === 0,
last: index === array.length - 1,
});
})
: props.children}
</Animated.View> </Animated.View>
); );
}); });
const buttonFontSize =
PixelRatio.roundToNearestPixel(Dimensions.get('window').width / 26) > 22
? 22
: PixelRatio.roundToNearestPixel(Dimensions.get('window').width / 26);
const bStyles = StyleSheet.create({
root: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
overflow: 'hidden',
},
icon: {
alignItems: 'center',
},
text: {
fontSize: buttonFontSize,
fontWeight: '600',
marginLeft: ICON_MARGIN,
backgroundColor: 'transparent',
},
});
interface FButtonProps { interface FButtonProps {
text: string; text: string;
icon: ReactNode; icon: ReactNode;
width?: number; width?: number;
first?: boolean; first?: boolean;
last?: boolean; last?: boolean;
singleChild?: boolean;
disabled?: boolean; disabled?: boolean;
testID?: string; testID?: string;
onPress: () => void; onPress: () => void;
onLongPress?: () => void; onLongPress?: () => void;
} }
export const FButton = ({ text, icon, width, first, last, testID, ...props }: FButtonProps) => { export const FButton = ({ text, icon, width, first, last, singleChild, testID, ...props }: FButtonProps) => {
const { colors } = useTheme(); const { colors } = useTheme();
const bStylesHook = StyleSheet.create({ const customButtonStyles = StyleSheet.create({
root: { root: {
backgroundColor: colors.buttonBackgroundColor, backgroundColor: colors.buttonBackgroundColor,
borderRadius: BORDER_RADIUS, borderRadius: BORDER_RADIUS,
@ -151,9 +162,12 @@ export const FButton = ({ text, icon, width, first, last, testID, ...props }: FB
marginRight: { marginRight: {
marginRight: 10, marginRight: 10,
}, },
rootRound: {
borderRadius: 9999,
},
}); });
const style: Record<string, any> = {}; const style: Record<string, any> = {};
const additionalStyles = !last ? bStylesHook.marginRight : {}; const additionalStyles = !last ? customButtonStyles.marginRight : {};
if (width) { if (width) {
style.paddingHorizontal = PADDINGS; style.paddingHorizontal = PADDINGS;
@ -165,11 +179,15 @@ export const FButton = ({ text, icon, width, first, last, testID, ...props }: FB
accessibilityLabel={text} accessibilityLabel={text}
accessibilityRole="button" accessibilityRole="button"
testID={testID} testID={testID}
style={[bStyles.root, bStylesHook.root, style, additionalStyles]} style={[buttonStyles.root, customButtonStyles.root, style, additionalStyles, singleChild ? customButtonStyles.rootRound : null]}
{...props} {...props}
> >
<View style={bStyles.icon}>{icon}</View> <View style={buttonStyles.icon}>{icon}</View>
<Text numberOfLines={1} adjustsFontSizeToFit style={[bStyles.text, props.disabled ? bStylesHook.textDisabled : bStylesHook.text]}> <Text
numberOfLines={1}
adjustsFontSizeToFit
style={[buttonStyles.text, props.disabled ? customButtonStyles.textDisabled : customButtonStyles.text]}
>
{text} {text}
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>

View file

@ -9,7 +9,12 @@ import { HandOffComponentProps } from './types';
const HandOffComponent: React.FC<HandOffComponentProps> = props => { const HandOffComponent: React.FC<HandOffComponentProps> = props => {
const { isHandOffUseEnabled } = useSettings(); const { isHandOffUseEnabled } = useSettings();
console.debug('HandOffComponent is rendering.'); if (!props || !props.type || !props.userInfo || Object.keys(props.userInfo).length === 0) {
console.debug('HandOffComponent: Missing required type or userInfo data');
return null;
}
const userInfo = JSON.stringify(props.userInfo);
console.debug(`HandOffComponent is rendering. Type: ${props.type}, UserInfo: ${userInfo}...`);
return isHandOffUseEnabled ? <Handoff {...props} /> : null; return isHandOffUseEnabled ? <Handoff {...props} /> : null;
}; };

View file

@ -1,13 +1,13 @@
import React, { useCallback, useState } from 'react'; import React, { useCallback, useState, useEffect, useRef } from 'react';
import { View, StyleSheet, ViewStyle, TouchableOpacity, ActivityIndicator, Platform } from 'react-native'; import { StyleSheet, ViewStyle, TouchableOpacity, ActivityIndicator, Platform, Animated } from 'react-native';
import { Icon, ListItem } from '@rneui/base'; import { Icon, ListItem } from '@rneui/base';
import { ExtendedTransaction, LightningTransaction, TWallet } from '../class/wallets/types'; import { ExtendedTransaction, LightningTransaction, TWallet } from '../class/wallets/types';
import { WalletCarouselItem } from './WalletsCarousel'; import { WalletCarouselItem } from './WalletsCarousel';
import { TransactionListItem } from './TransactionListItem'; import { TransactionListItem } from './TransactionListItem';
import { useTheme } from './themes'; import { useTheme } from './themes';
import { BitcoinUnit } from '../models/bitcoinUnits'; import { BitcoinUnit } from '../models/bitcoinUnits';
import { TouchableOpacityWrapper } from './ListItem';
import loc from '../loc'; import loc from '../loc';
import triggerHapticFeedback, { HapticFeedbackTypes } from '../blue_modules/hapticFeedback';
enum ItemType { enum ItemType {
WalletSection = 'wallet', WalletSection = 'wallet',
@ -40,6 +40,7 @@ interface ManageWalletsListItemProps {
handleToggleHideBalance: (wallet: TWallet) => void; handleToggleHideBalance: (wallet: TWallet) => void;
isActive?: boolean; isActive?: boolean;
style?: ViewStyle; style?: ViewStyle;
globalDragActive?: boolean;
} }
interface SwipeContentProps { interface SwipeContentProps {
@ -83,10 +84,35 @@ const ManageWalletsListItem: React.FC<ManageWalletsListItemProps> = ({
onPressIn, onPressIn,
onPressOut, onPressOut,
isActive, isActive,
globalDragActive,
style, style,
}) => { }) => {
const { colors } = useTheme(); const { colors } = useTheme();
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [isSwipeActive, setIsSwipeActive] = useState(false);
const resetFunctionRef = useRef<(() => void) | null>(null);
const CARD_SORT_ACTIVE = 1.06;
const INACTIVE_SCALE_WHEN_ACTIVE = 0.9;
const SCALE_DURATION = 200;
const scaleValue = useRef(new Animated.Value(1)).current;
const prevIsActive = useRef(isActive);
const DEFAULT_VERTICAL_MARGIN = -10;
const REDUCED_VERTICAL_MARGIN = -50;
useEffect(() => {
if (isActive !== prevIsActive.current) {
triggerHapticFeedback(HapticFeedbackTypes.ImpactMedium);
}
prevIsActive.current = isActive;
Animated.timing(scaleValue, {
toValue: isActive ? CARD_SORT_ACTIVE : globalDragActive ? INACTIVE_SCALE_WHEN_ACTIVE : 1,
duration: SCALE_DURATION,
useNativeDriver: true,
}).start();
}, [isActive, globalDragActive, scaleValue]);
const onPress = useCallback(() => { const onPress = useCallback(() => {
if (item.type === ItemType.WalletSection) { if (item.type === ItemType.WalletSection) {
@ -101,43 +127,83 @@ const ManageWalletsListItem: React.FC<ManageWalletsListItemProps> = ({
reset(); reset();
}; };
const leftContent = (reset: () => void) => ( const leftContent = (reset: () => void) => {
<LeftSwipeContent onPress={() => handleLeftPress(reset)} hideBalance={(item.data as TWallet).hideBalance} colors={colors} /> resetFunctionRef.current = reset;
); return <LeftSwipeContent onPress={() => handleLeftPress(reset)} hideBalance={(item.data as TWallet).hideBalance} colors={colors} />;
const handleRightPress = (reset: () => void) => {
handleDeleteWallet(item.data as TWallet);
reset();
}; };
const rightContent = (reset: () => void) => <RightSwipeContent onPress={() => handleRightPress(reset)} />; const handleRightPress = (reset: () => void) => {
reset();
setTimeout(() => {
handleDeleteWallet(item.data as TWallet);
}, 100); // short delay to allow swipe reset animation to complete
};
const rightContent = (reset: () => void) => {
resetFunctionRef.current = reset;
return <RightSwipeContent onPress={() => handleRightPress(reset)} />;
};
const startDrag = useCallback(() => {
if (isSwipeActive) {
return;
}
if (resetFunctionRef.current) {
resetFunctionRef.current();
}
scaleValue.setValue(CARD_SORT_ACTIVE);
triggerHapticFeedback(HapticFeedbackTypes.ImpactMedium);
if (drag) {
drag();
}
}, [CARD_SORT_ACTIVE, drag, scaleValue, isSwipeActive]);
if (isLoading) { if (isLoading) {
return <ActivityIndicator size="large" color={colors.brandingColor} />; return <ActivityIndicator size="large" color={colors.brandingColor} />;
} }
if (item.type === ItemType.WalletSection) { if (item.type === ItemType.WalletSection) {
const animatedStyle = {
transform: [{ scale: scaleValue }],
marginVertical: globalDragActive && !isActive ? REDUCED_VERTICAL_MARGIN : DEFAULT_VERTICAL_MARGIN,
};
const backgroundColor = isActive || globalDragActive ? colors.brandingColor : colors.background;
const swipeDisabled = isActive || globalDragActive;
return ( return (
<ListItem.Swipeable <Animated.View style={animatedStyle}>
leftWidth={80} <ListItem.Swipeable
rightWidth={90} leftWidth={swipeDisabled ? 0 : 80}
containerStyle={[{ backgroundColor: colors.background }, style]} rightWidth={swipeDisabled ? 0 : 90}
leftContent={leftContent} containerStyle={[style, { backgroundColor }, swipeDisabled ? styles.transparentBackground : {}]}
rightContent={rightContent} leftContent={swipeDisabled ? null : leftContent}
Component={TouchableOpacityWrapper} rightContent={swipeDisabled ? null : rightContent}
onPressOut={onPressOut} onPressOut={onPressOut}
onPressIn={onPressIn} minSlideWidth={swipeDisabled ? 0 : 80}
style={isActive ? styles.activeItem : undefined} onPressIn={onPressIn}
> style={swipeDisabled ? styles.transparentBackground : {}}
<ListItem.Content onSwipeBegin={direction => {
style={{ if (!swipeDisabled) {
backgroundColor: colors.background, console.debug(`Swipe began: ${direction}`);
setIsSwipeActive(true);
}
}}
onSwipeEnd={() => {
if (!swipeDisabled) {
console.debug('Swipe ended');
setIsSwipeActive(false);
}
}} }}
> >
<View style={styles.walletCarouselItemContainer}> <ListItem.Content>
<WalletCarouselItem <WalletCarouselItem
item={item.data} item={item.data}
handleLongPress={isDraggingDisabled ? undefined : drag} handleLongPress={isDraggingDisabled || isSwipeActive ? undefined : startDrag}
onPress={onPress} onPress={onPress}
onPressIn={onPressIn} onPressIn={onPressIn}
onPressOut={onPressOut} onPressOut={onPressOut}
@ -145,10 +211,11 @@ const ManageWalletsListItem: React.FC<ManageWalletsListItemProps> = ({
searchQuery={state.searchQuery} searchQuery={state.searchQuery}
isPlaceHolder={isPlaceHolder} isPlaceHolder={isPlaceHolder}
renderHighlightedText={renderHighlightedText} renderHighlightedText={renderHighlightedText}
customStyle={styles.carouselItem}
/> />
</View> </ListItem.Content>
</ListItem.Content> </ListItem.Swipeable>
</ListItem.Swipeable> </Animated.View>
); );
} else if (item.type === ItemType.TransactionSection && item.data) { } else if (item.type === ItemType.TransactionSection && item.data) {
const w = state.wallets.find(wallet => wallet.getTransactions().some((tx: ExtendedTransaction) => tx.hash === item.data.hash)); const w = state.wallets.find(wallet => wallet.getTransactions().some((tx: ExtendedTransaction) => tx.hash === item.data.hash));
@ -169,22 +236,22 @@ const ManageWalletsListItem: React.FC<ManageWalletsListItemProps> = ({
}; };
const styles = StyleSheet.create({ const styles = StyleSheet.create({
walletCarouselItemContainer: {
width: '100%',
},
leftButtonContainer: { leftButtonContainer: {
flex: 1, flex: 1,
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
}, },
carouselItem: {
width: '100%',
},
rightButtonContainer: { rightButtonContainer: {
flex: 1, flex: 1,
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
backgroundColor: 'red', backgroundColor: 'red',
}, },
activeItem: { transparentBackground: {
backgroundColor: 'rgba(0, 0, 0, 0.1)', backgroundColor: 'transparent',
}, },
}); });

View file

@ -1,5 +1,5 @@
import React, { useState, useRef, forwardRef, useImperativeHandle, useEffect } from 'react'; import React, { useState, useRef, forwardRef, useImperativeHandle, useEffect } from 'react';
import { View, Text, TextInput, StyleSheet, Animated, Easing, ViewStyle, Keyboard, Platform, UIManager, ScrollView } from 'react-native'; import { View, Text, TextInput, StyleSheet, Animated, Easing, ViewStyle, Keyboard, Platform, UIManager } from 'react-native';
import BottomModal, { BottomModalHandle } from './BottomModal'; import BottomModal, { BottomModalHandle } from './BottomModal';
import { useTheme } from '../components/themes'; import { useTheme } from '../components/themes';
import loc from '../loc'; import loc from '../loc';
@ -43,11 +43,10 @@ const PromptPasswordConfirmationModal = forwardRef<PromptPasswordConfirmationMod
const fadeInAnimation = useRef(new Animated.Value(0)).current; const fadeInAnimation = useRef(new Animated.Value(0)).current;
const scaleAnimation = useRef(new Animated.Value(1)).current; const scaleAnimation = useRef(new Animated.Value(1)).current;
const shakeAnimation = useRef(new Animated.Value(0)).current; const shakeAnimation = useRef(new Animated.Value(0)).current;
const explanationOpacity = useRef(new Animated.Value(1)).current; // New animated value for opacity const explanationOpacity = useRef(new Animated.Value(1)).current;
const { colors } = useTheme(); const { colors } = useTheme();
const passwordInputRef = useRef<TextInput>(null); const passwordInputRef = useRef<TextInput>(null);
const confirmPasswordInputRef = useRef<TextInput>(null); const confirmPasswordInputRef = useRef<TextInput>(null);
const scrollView = useRef<ScrollView>(null);
const { isVisible } = useKeyboard(); const { isVisible } = useKeyboard();
const stylesHook = StyleSheet.create({ const stylesHook = StyleSheet.create({
@ -103,42 +102,43 @@ const PromptPasswordConfirmationModal = forwardRef<PromptPasswordConfirmationMod
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [modalType]); }, [modalType]);
const handleShakeAnimation = () => { const performShake = (shakeAnimRef: Animated.Value) => {
Animated.sequence([ Animated.sequence([
Animated.timing(shakeAnimation, { Animated.timing(shakeAnimRef, {
toValue: 10, toValue: 10,
duration: 100, duration: 100,
easing: Easing.inOut(Easing.ease), easing: Easing.inOut(Easing.ease),
useNativeDriver: true, useNativeDriver: true,
}), }),
Animated.timing(shakeAnimation, { Animated.timing(shakeAnimRef, {
toValue: -10, toValue: -10,
duration: 100, duration: 100,
easing: Easing.inOut(Easing.ease), easing: Easing.inOut(Easing.ease),
useNativeDriver: true, useNativeDriver: true,
}), }),
Animated.timing(shakeAnimation, { Animated.timing(shakeAnimRef, {
toValue: 5, toValue: 5,
duration: 100, duration: 100,
easing: Easing.inOut(Easing.ease), easing: Easing.inOut(Easing.ease),
useNativeDriver: true, useNativeDriver: true,
}), }),
Animated.timing(shakeAnimation, { Animated.timing(shakeAnimRef, {
toValue: -5, toValue: -5,
duration: 100, duration: 100,
easing: Easing.inOut(Easing.ease), easing: Easing.inOut(Easing.ease),
useNativeDriver: true, useNativeDriver: true,
}), }),
Animated.timing(shakeAnimation, { Animated.timing(shakeAnimRef, {
toValue: 0, toValue: 0,
duration: 100, duration: 100,
easing: Easing.inOut(Easing.ease), easing: Easing.inOut(Easing.ease),
useNativeDriver: true, useNativeDriver: true,
}), }),
]).start(() => { ]).start();
confirmPasswordInputRef.current?.focus(); };
confirmPasswordInputRef.current?.setNativeProps({ selection: { start: 0, end: confirmPassword.length } });
}); const handleShakeAnimation = () => {
performShake(shakeAnimation);
}; };
const handleSuccessAnimation = () => { const handleSuccessAnimation = () => {
@ -180,6 +180,17 @@ const PromptPasswordConfirmationModal = forwardRef<PromptPasswordConfirmationMod
}); });
}; };
const handleConfirmationFailure = () => {
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
if (!isSuccess) handleShakeAnimation();
onConfirmationFailure();
};
const handleConfirmSuccess = () => {
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
handleSuccessAnimation();
};
const handleSubmit = async () => { const handleSubmit = async () => {
Keyboard.dismiss(); Keyboard.dismiss();
setIsLoading(true); setIsLoading(true);
@ -189,37 +200,13 @@ const PromptPasswordConfirmationModal = forwardRef<PromptPasswordConfirmationMod
if (modalType === MODAL_TYPES.CREATE_PASSWORD || modalType === MODAL_TYPES.CREATE_FAKE_STORAGE) { if (modalType === MODAL_TYPES.CREATE_PASSWORD || modalType === MODAL_TYPES.CREATE_FAKE_STORAGE) {
if (password === confirmPassword && password) { if (password === confirmPassword && password) {
success = await onConfirmationSuccess(password); success = await onConfirmationSuccess(password);
if (success) { success ? handleConfirmSuccess() : handleConfirmationFailure();
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
handleSuccessAnimation();
} else {
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
onConfirmationFailure();
if (!isSuccess) {
// Prevent shake animation if success is detected
handleShakeAnimation();
}
}
} else { } else {
triggerHapticFeedback(HapticFeedbackTypes.NotificationError); handleConfirmationFailure();
if (!isSuccess) {
// Prevent shake animation if success is detected
handleShakeAnimation();
}
} }
} else if (modalType === MODAL_TYPES.ENTER_PASSWORD) { } else if (modalType === MODAL_TYPES.ENTER_PASSWORD) {
success = await onConfirmationSuccess(password); success = await onConfirmationSuccess(password);
if (success) { success ? handleConfirmSuccess() : handleConfirmationFailure();
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
handleSuccessAnimation();
} else {
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
if (!isSuccess) {
// Prevent shake animation if success is detected
handleShakeAnimation();
}
onConfirmationFailure();
}
} }
} finally { } finally {
setIsLoading(false); // Ensure loading state is reset setIsLoading(false); // Ensure loading state is reset
@ -258,18 +245,18 @@ const PromptPasswordConfirmationModal = forwardRef<PromptPasswordConfirmationMod
onConfirmationFailure(); onConfirmationFailure();
}; };
const opacity = isVisible ? 0 : 1;
return ( return (
<BottomModal <BottomModal
ref={modalRef} ref={modalRef}
onDismiss={onModalDismiss} onClose={onModalDismiss}
grabber={false} grabber={false}
showCloseButton={!isSuccess} showCloseButton={!isSuccess}
onCloseModalPressed={handleCancel} onCloseModalPressed={handleCancel}
backgroundColor={colors.modal} backgroundColor={colors.modal}
isGrabberVisible={!isSuccess} isGrabberVisible={!isSuccess}
scrollRef={scrollView}
keyboardMode="pan"
dismissible={false} dismissible={false}
sizes={Platform.OS === 'ios' ? ['auto'] : [420, 'auto']}
footer={ footer={
!isSuccess ? ( !isSuccess ? (
showExplanation && modalType === MODAL_TYPES.CREATE_PASSWORD ? ( showExplanation && modalType === MODAL_TYPES.CREATE_PASSWORD ? (
@ -282,16 +269,19 @@ const PromptPasswordConfirmationModal = forwardRef<PromptPasswordConfirmationMod
/> />
</Animated.View> </Animated.View>
) : ( ) : (
<Animated.View style={[{ opacity: fadeOutAnimation, transform: [{ scale: scaleAnimation }] }, styles.feeModalFooter]}> <Animated.View
{!isVisible && ( style={[
<SecondButton { opacity: isVisible ? opacity : fadeOutAnimation, transform: [{ scale: scaleAnimation }] },
title={isLoading ? '' : loc._.ok} styles.feeModalFooterSpacing,
onPress={handleSubmit} ]}
testID="OKButton" >
loading={isLoading} <SecondButton
disabled={isLoading || !password || (modalType === MODAL_TYPES.CREATE_PASSWORD && !confirmPassword)} title={isLoading ? '' : loc._.ok}
/> onPress={handleSubmit}
)} testID="OKButton"
loading={isLoading}
disabled={isLoading || !password || (modalType === MODAL_TYPES.CREATE_PASSWORD && !confirmPassword)}
/>
</Animated.View> </Animated.View>
) )
) : null ) : null
@ -302,14 +292,14 @@ const PromptPasswordConfirmationModal = forwardRef<PromptPasswordConfirmationMod
{modalType === MODAL_TYPES.CREATE_PASSWORD && showExplanation && ( {modalType === MODAL_TYPES.CREATE_PASSWORD && showExplanation && (
<Animated.View style={{ opacity: explanationOpacity }}> <Animated.View style={{ opacity: explanationOpacity }}>
<Text style={[styles.textLabel, stylesHook.feeModalLabel]}>{loc.settings.encrypt_storage_explanation_headline}</Text> <Text style={[styles.textLabel, stylesHook.feeModalLabel]}>{loc.settings.encrypt_storage_explanation_headline}</Text>
<Animated.ScrollView style={styles.explanationScrollView} ref={scrollView}> <Animated.View>
<Text style={[styles.description, stylesHook.feeModalCustomText]}> <Text style={[styles.description, stylesHook.feeModalCustomText]} maxFontSizeMultiplier={1.2}>
{loc.settings.encrypt_storage_explanation_description_line1} {loc.settings.encrypt_storage_explanation_description_line1}
</Text> </Text>
<Text style={[styles.description, stylesHook.feeModalCustomText]}> <Text style={[styles.description, stylesHook.feeModalCustomText]} maxFontSizeMultiplier={1.2}>
{loc.settings.encrypt_storage_explanation_description_line2} {loc.settings.encrypt_storage_explanation_description_line2}
</Text> </Text>
</Animated.ScrollView> </Animated.View>
<View style={styles.feeModalFooter} /> <View style={styles.feeModalFooter} />
</Animated.View> </Animated.View>
)} )}
@ -398,29 +388,30 @@ export default PromptPasswordConfirmationModal;
const styles = StyleSheet.create({ const styles = StyleSheet.create({
modalContent: { modalContent: {
padding: 22, padding: 22,
width: '100%', // Ensure modal content takes full width width: '100%',
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
}, },
minHeight: { minHeight: {
minHeight: 280, minHeight: 420,
}, },
feeModalFooter: { feeModalFooter: {
padding: 16, padding: 16,
}, },
feeModalFooterSpacing: { feeModalFooterSpacing: {
padding: 16, padding: 24,
marginVertical: 24,
}, },
inputContainer: { inputContainer: {
marginBottom: 10, marginBottom: 10,
width: '100%', // Ensure full width width: '100%',
}, },
input: { input: {
borderRadius: 4, borderRadius: 4,
padding: 8, padding: 8,
marginVertical: 8, marginVertical: 8,
fontSize: 16, fontSize: 16,
width: '100%', // Ensure full width width: '100%',
}, },
textLabel: { textLabel: {
fontSize: 20, fontSize: 20,
@ -436,7 +427,8 @@ const styles = StyleSheet.create({
successContainer: { successContainer: {
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
height: 100, margin: 24,
marginBottom: 48,
}, },
circle: { circle: {
width: 60, width: 60,
@ -450,7 +442,4 @@ const styles = StyleSheet.create({
color: 'white', color: 'white',
fontSize: 30, fontSize: 30,
}, },
explanationScrollView: {
maxHeight: 200,
},
}); });

View file

@ -1,4 +1,4 @@
import React from 'react'; import React, { useMemo } from 'react';
import { requireNativeComponent, View, StyleSheet, NativeSyntheticEvent } from 'react-native'; import { requireNativeComponent, View, StyleSheet, NativeSyntheticEvent } from 'react-native';
interface SegmentedControlProps { interface SegmentedControlProps {
@ -21,9 +21,18 @@ interface NativeSegmentedControlProps {
const NativeSegmentedControl = requireNativeComponent<NativeSegmentedControlProps>('CustomSegmentedControl'); const NativeSegmentedControl = requireNativeComponent<NativeSegmentedControlProps>('CustomSegmentedControl');
const SegmentedControl: React.FC<SegmentedControlProps> = ({ values, selectedIndex, onChange }) => { const SegmentedControl: React.FC<SegmentedControlProps> = ({ values, selectedIndex, onChange }) => {
const handleChange = (event: NativeSyntheticEvent<SegmentedControlEvent>) => { const handleChange = useMemo(
onChange(event.nativeEvent.selectedIndex); () => (event: NativeSyntheticEvent<SegmentedControlEvent>) => {
}; if (event?.nativeEvent?.selectedIndex !== undefined) {
onChange(event.nativeEvent.selectedIndex);
}
},
[onChange],
);
if (!Array.isArray(values) || values.length === 0) {
return null;
}
return ( return (
<View style={styles.container}> <View style={styles.container}>

View file

@ -109,8 +109,8 @@ const SelectFeeModal = forwardRef<BottomModalHandle, SelectFeeModalProps>(
}); });
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
present: async () => feeModalRef.current?.present(), present: async () => await feeModalRef.current?.present(),
dismiss: async () => feeModalRef.current?.dismiss(), dismiss: async () => await feeModalRef.current?.dismiss(),
})); }));
const options: Option[] = [ const options: Option[] = [
@ -163,8 +163,8 @@ const SelectFeeModal = forwardRef<BottomModalHandle, SelectFeeModalProps>(
const handleSelectOption = async (fee: number | null, rate: number) => { const handleSelectOption = async (fee: number | null, rate: number) => {
setFeePrecalc(fp => ({ ...fp, current: fee })); setFeePrecalc(fp => ({ ...fp, current: fee }));
await feeModalRef.current?.dismiss();
setCustomFee(rate.toString()); setCustomFee(rate.toString());
await feeModalRef.current?.dismiss();
}; };
return ( return (

73
components/TipBox.tsx Normal file
View file

@ -0,0 +1,73 @@
import React from 'react';
import { View, StyleSheet, ViewStyle } from 'react-native';
import { useTheme } from './themes';
import { BlueText } from '../BlueComponents';
interface TipBoxProps {
number?: string;
title?: string;
description?: string;
additionalDescription?: string;
containerStyle?: ViewStyle;
}
const TipBox: React.FC<TipBoxProps> = ({ number, title, description, additionalDescription, containerStyle }) => {
const { colors } = useTheme();
const stylesHook = StyleSheet.create({
tipBox: {
backgroundColor: colors.ballOutgoingExpired,
borderRadius: 12,
padding: 16,
marginBottom: 24,
...containerStyle,
},
tipHeader: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: number || title ? 16 : 0,
},
tipHeaderText: {
marginLeft: 4,
flex: 1,
},
description: {
marginBottom: additionalDescription ? 16 : 0,
},
});
return (
<View style={stylesHook.tipBox}>
{(number || title) && (
<View style={stylesHook.tipHeader}>
{number && (
<View style={styles.vaultKeyCircle}>
<BlueText style={styles.vaultKeyText}>{number}</BlueText>
</View>
)}
{title && (
<BlueText bold style={stylesHook.tipHeaderText}>
{title}
</BlueText>
)}
</View>
)}
{description && <BlueText style={stylesHook.description}>{description}</BlueText>}
{additionalDescription && <BlueText>{additionalDescription}</BlueText>}
</View>
);
};
const styles = StyleSheet.create({
vaultKeyCircle: {
width: 32,
height: 32,
justifyContent: 'center',
alignItems: 'center',
},
vaultKeyText: {
fontSize: 18,
fontWeight: 'bold',
},
});
export default TipBox;

View file

@ -1,19 +1,15 @@
import React, { Ref, useCallback, useMemo } from 'react'; import React, { useCallback, useMemo } from 'react';
import { Platform, Pressable, TouchableOpacity } from 'react-native'; import { Platform, TouchableOpacity } from 'react-native';
import { MenuView, MenuAction, NativeActionEvent } from '@react-native-menu/menu'; import { MenuView, MenuAction, NativeActionEvent } from '@react-native-menu/menu';
import { ContextMenuView, RenderItem, OnPressMenuItemEventObject, IconConfig, MenuElementConfig } from 'react-native-ios-context-menu';
import { ToolTipMenuProps, Action } from './types'; import { ToolTipMenuProps, Action } from './types';
import { useSettings } from '../hooks/context/useSettings'; import { useSettings } from '../hooks/context/useSettings';
const ToolTipMenu = React.memo((props: ToolTipMenuProps, ref?: Ref<any>) => { const ToolTipMenu = (props: ToolTipMenuProps) => {
const { const {
title = '', title = '',
isMenuPrimaryAction = false, isMenuPrimaryAction = false,
renderPreview,
disabled = false, disabled = false,
onPress, onPress,
onMenuWillShow,
onMenuWillHide,
buttonStyle, buttonStyle,
onPressMenuItem, onPressMenuItem,
children, children,
@ -23,18 +19,6 @@ const ToolTipMenu = React.memo((props: ToolTipMenuProps, ref?: Ref<any>) => {
const { language } = useSettings(); const { language } = useSettings();
// Map Menu Items for iOS Context Menu
const mapMenuItemForContextMenuView = useCallback((action: Action) => {
if (!action.id) return null;
return {
actionKey: action.id.toString(),
actionTitle: action.text,
icon: action.icon?.iconValue ? ({ iconType: 'SYSTEM', iconValue: action.icon.iconValue } as IconConfig) : undefined,
state: action.menuState ?? undefined,
attributes: action.disabled ? ['disabled'] : [],
};
}, []);
// Map Menu Items for RN Menu (supports subactions and displayInline) // Map Menu Items for RN Menu (supports subactions and displayInline)
const mapMenuItemForMenuView = useCallback((action: Action): MenuAction | null => { const mapMenuItemForMenuView = useCallback((action: Action): MenuAction | null => {
if (!action.id) return null; if (!action.id) return null;
@ -88,11 +72,6 @@ const ToolTipMenu = React.memo((props: ToolTipMenuProps, ref?: Ref<any>) => {
return menuItem; return menuItem;
}, []); }, []);
const contextMenuItems = useMemo(() => {
const flattenedActions = props.actions.flat().filter(action => action.id);
return flattenedActions.map(mapMenuItemForContextMenuView).filter(item => item !== null) as MenuElementConfig[];
}, [props.actions, mapMenuItemForContextMenuView]);
const menuViewItemsIOS = useMemo(() => { const menuViewItemsIOS = useMemo(() => {
return props.actions return props.actions
.map(actionGroup => { .map(actionGroup => {
@ -119,13 +98,6 @@ const ToolTipMenu = React.memo((props: ToolTipMenuProps, ref?: Ref<any>) => {
return mergedActions.map(mapMenuItemForMenuView).filter(item => item !== null) as MenuAction[]; return mergedActions.map(mapMenuItemForMenuView).filter(item => item !== null) as MenuAction[];
}, [props.actions, mapMenuItemForMenuView]); }, [props.actions, mapMenuItemForMenuView]);
const handlePressMenuItemForContextMenuView = useCallback(
(event: OnPressMenuItemEventObject) => {
onPressMenuItem(event.nativeEvent.actionKey);
},
[onPressMenuItem],
);
const handlePressMenuItemForMenuView = useCallback( const handlePressMenuItemForMenuView = useCallback(
({ nativeEvent }: NativeActionEvent) => { ({ nativeEvent }: NativeActionEvent) => {
onPressMenuItem(nativeEvent.event); onPressMenuItem(nativeEvent.event);
@ -133,46 +105,6 @@ const ToolTipMenu = React.memo((props: ToolTipMenuProps, ref?: Ref<any>) => {
[onPressMenuItem], [onPressMenuItem],
); );
const renderContextMenuView = () => {
return (
<ContextMenuView
lazyPreview
accessibilityLabel={props.accessibilityLabel}
accessibilityHint={props.accessibilityHint}
accessibilityRole={props.accessibilityRole}
accessibilityState={props.accessibilityState}
accessibilityLanguage={language}
shouldEnableAggressiveCleanup
internalCleanupMode="automatic"
onPressMenuItem={handlePressMenuItemForContextMenuView}
onMenuWillShow={onMenuWillShow}
onMenuWillHide={onMenuWillHide}
useActionSheetFallback={false}
menuConfig={{
menuTitle: title,
menuItems: contextMenuItems,
}}
{...(renderPreview
? {
previewConfig: {
previewType: 'CUSTOM',
backgroundColor: 'white',
},
renderPreview: renderPreview as RenderItem,
}
: {})}
>
{onPress ? (
<Pressable accessibilityRole="button" onPress={onPress} {...restProps}>
{children}
</Pressable>
) : (
children
)}
</ContextMenuView>
);
};
const renderMenuView = () => { const renderMenuView = () => {
return ( return (
<MenuView <MenuView
@ -198,7 +130,7 @@ const ToolTipMenu = React.memo((props: ToolTipMenuProps, ref?: Ref<any>) => {
); );
}; };
return props.actions.length > 0 ? (Platform.OS === 'ios' && renderPreview ? renderContextMenuView() : renderMenuView()) : null; return props.actions.length > 0 ? renderMenuView() : null;
}); };
export default ToolTipMenu; export default ToolTipMenu;

View file

@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useRef, useState, memo } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage'; import AsyncStorage from '@react-native-async-storage/async-storage';
import Clipboard from '@react-native-clipboard/clipboard'; import Clipboard from '@react-native-clipboard/clipboard';
import { Linking, View, ViewStyle } from 'react-native'; import { Linking, View, ViewStyle } from 'react-native';
@ -36,7 +36,7 @@ interface TransactionListItemProps {
type NavigationProps = NativeStackNavigationProp<DetailViewStackParamList>; type NavigationProps = NativeStackNavigationProp<DetailViewStackParamList>;
export const TransactionListItem: React.FC<TransactionListItemProps> = React.memo( export const TransactionListItem: React.FC<TransactionListItemProps> = memo(
({ item, itemPriceUnit = BitcoinUnit.BTC, walletID, searchQuery, style, renderHighlightedText }) => { ({ item, itemPriceUnit = BitcoinUnit.BTC, walletID, searchQuery, style, renderHighlightedText }) => {
const [subtitleNumberOfLines, setSubtitleNumberOfLines] = useState(1); const [subtitleNumberOfLines, setSubtitleNumberOfLines] = useState(1);
const { colors } = useTheme(); const { colors } = useTheme();
@ -46,10 +46,10 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
const { language, selectedBlockExplorer } = useSettings(); const { language, selectedBlockExplorer } = useSettings();
const containerStyle = useMemo( const containerStyle = useMemo(
() => ({ () => ({
backgroundColor: 'transparent', backgroundColor: colors.background,
borderBottomColor: colors.lightBorder, borderBottomColor: colors.lightBorder,
}), }),
[colors.lightBorder], [colors.background, colors.lightBorder],
); );
const combinedStyle = useMemo(() => [containerStyle, style], [containerStyle, style]); const combinedStyle = useMemo(() => [containerStyle, style], [containerStyle, style]);
@ -81,28 +81,23 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
return sub || undefined; return sub || undefined;
}, [txMemo, item.confirmations, item.memo]); }, [txMemo, item.confirmations, item.memo]);
const formattedAmount = useMemo(() => {
return formatBalanceWithoutSuffix(item.value && item.value, itemPriceUnit, true).toString();
}, [item.value, itemPriceUnit]);
const rowTitle = useMemo(() => { const rowTitle = useMemo(() => {
if (item.type === 'user_invoice' || item.type === 'payment_request') { if (item.type === 'user_invoice' || item.type === 'payment_request') {
if (isNaN(Number(item.value))) {
item.value = 0;
}
const currentDate = new Date(); const currentDate = new Date();
const now = (currentDate.getTime() / 1000) | 0; // eslint-disable-line no-bitwise const now = Math.floor(currentDate.getTime() / 1000);
const invoiceExpiration = item.timestamp! + item.expire_time!; const invoiceExpiration = item.timestamp! + item.expire_time!;
if (invoiceExpiration > now || item.ispaid) {
if (invoiceExpiration > now) { return formattedAmount;
return formatBalanceWithoutSuffix(item.value && item.value, itemPriceUnit, true).toString();
} else { } else {
if (item.ispaid) { return loc.lnd.expired;
return formatBalanceWithoutSuffix(item.value && item.value, itemPriceUnit, true).toString();
} else {
return loc.lnd.expired;
}
} }
} else {
return formatBalanceWithoutSuffix(item.value && item.value, itemPriceUnit, true).toString();
} }
}, [item, itemPriceUnit]); return formattedAmount;
}, [item, formattedAmount]);
const rowTitleStyle = useMemo(() => { const rowTitleStyle = useMemo(() => {
let color = colors.successColor; let color = colors.successColor;
@ -198,10 +193,9 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
const { label: transactionTypeLabel, icon: avatar } = determineTransactionTypeAndAvatar(); const { label: transactionTypeLabel, icon: avatar } = determineTransactionTypeAndAvatar();
const amountWithUnit = useMemo(() => { const amountWithUnit = useMemo(() => {
const amount = formatBalanceWithoutSuffix(item.value && item.value, itemPriceUnit, true).toString(); const unitSuffix = itemPriceUnit === BitcoinUnit.BTC || itemPriceUnit === BitcoinUnit.SATS ? ` ${itemPriceUnit}` : ' ';
const unit = itemPriceUnit === BitcoinUnit.BTC || itemPriceUnit === BitcoinUnit.SATS ? ` ${itemPriceUnit}` : ' '; return `${formattedAmount}${unitSuffix}`;
return `${amount}${unit}`; }, [formattedAmount, itemPriceUnit]);
}, [item.value, itemPriceUnit]);
useEffect(() => { useEffect(() => {
setSubtitleNumberOfLines(1); setSubtitleNumberOfLines(1);
@ -226,7 +220,7 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
} }
const loaded = await LN.loadSuccessfulPayment(paymentHash); const loaded = await LN.loadSuccessfulPayment(paymentHash);
if (loaded) { if (loaded) {
navigate('ScanLndInvoiceRoot', { navigate('ScanLNDInvoiceRoot', {
screen: 'LnurlPaySuccess', screen: 'LnurlPaySuccess',
params: { params: {
paymentHash, paymentHash,
@ -252,7 +246,19 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
setSubtitleNumberOfLines(0); setSubtitleNumberOfLines(0);
}, []); }, []);
const subtitleProps = useMemo(() => ({ numberOfLines: subtitleNumberOfLines }), [subtitleNumberOfLines]); const handleOnDetailsPress = useCallback(() => {
if (walletID && item && item.hash) {
navigate('TransactionDetails', { tx: item, hash: item.hash, walletID });
} else {
const lightningWallet = wallets.find(wallet => wallet?.getID() === item.walletID);
if (lightningWallet) {
navigate('LNDViewInvoice', {
invoice: item,
walletID: lightningWallet.getID(),
});
}
}
}, [item, navigate, walletID, wallets]);
const handleOnCopyAmountTap = useCallback(() => Clipboard.setString(rowTitle.replace(/[\s\\-]/g, '')), [rowTitle]); const handleOnCopyAmountTap = useCallback(() => Clipboard.setString(rowTitle.replace(/[\s\\-]/g, '')), [rowTitle]);
const handleOnCopyTransactionID = useCallback(() => Clipboard.setString(item.hash), [item.hash]); const handleOnCopyTransactionID = useCallback(() => Clipboard.setString(item.hash), [item.hash]);
@ -283,6 +289,8 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
handleCopyOpenInBlockExplorerPress(); handleCopyOpenInBlockExplorerPress();
} else if (id === CommonToolTipActions.CopyTXID.id) { } else if (id === CommonToolTipActions.CopyTXID.id) {
handleOnCopyTransactionID(); handleOnCopyTransactionID();
} else if (id === CommonToolTipActions.Details.id) {
handleOnDetailsPress();
} }
}, },
[ [
@ -290,31 +298,40 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
handleOnCopyAmountTap, handleOnCopyAmountTap,
handleOnCopyNote, handleOnCopyNote,
handleOnCopyTransactionID, handleOnCopyTransactionID,
handleOnDetailsPress,
handleOnExpandNote, handleOnExpandNote,
handleOnViewOnBlockExplorer, handleOnViewOnBlockExplorer,
], ],
); );
const toolTipActions = useMemo((): Action[] => { const toolTipActions = useMemo((): Action[] => {
const actions: (Action | Action[])[] = []; const actions: (Action | Action[])[] = [
{
if (rowTitle !== loc.lnd.expired) { ...CommonToolTipActions.CopyAmount,
actions.push(CommonToolTipActions.CopyAmount); hidden: rowTitle === loc.lnd.expired,
} },
{
if (subtitle) { ...CommonToolTipActions.CopyNote,
actions.push(CommonToolTipActions.CopyNote); hidden: !subtitle,
} },
{
if (item.hash) { ...CommonToolTipActions.CopyTXID,
actions.push(CommonToolTipActions.CopyTXID, CommonToolTipActions.CopyBlockExplorerLink, [CommonToolTipActions.OpenInBlockExplorer]); hidden: !item.hash,
} },
{
if (subtitle && subtitleNumberOfLines === 1) { ...CommonToolTipActions.CopyBlockExplorerLink,
actions.push([CommonToolTipActions.ExpandNote]); hidden: !item.hash,
} },
[{ ...CommonToolTipActions.OpenInBlockExplorer, hidden: !item.hash }, CommonToolTipActions.Details],
[
{
...CommonToolTipActions.ExpandNote,
hidden: subtitleNumberOfLines !== 1,
},
],
];
return actions as Action[]; return actions as Action[];
}, [item.hash, subtitle, rowTitle, subtitleNumberOfLines]); }, [rowTitle, subtitle, item.hash, subtitleNumberOfLines]);
const accessibilityState = useMemo(() => { const accessibilityState = useMemo(() => {
return { return {
@ -322,6 +339,8 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
}; };
}, [subtitleNumberOfLines]); }, [subtitleNumberOfLines]);
const subtitleProps = useMemo(() => ({ numberOfLines: subtitleNumberOfLines }), [subtitleNumberOfLines]);
return ( return (
<ToolTipMenu <ToolTipMenu
isButton isButton
@ -343,8 +362,17 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
rightTitle={rowTitle} rightTitle={rowTitle}
rightTitleStyle={rowTitleStyle} rightTitleStyle={rowTitleStyle}
containerStyle={combinedStyle} containerStyle={combinedStyle}
testID="TransactionListItem"
/> />
</ToolTipMenu> </ToolTipMenu>
); );
}, },
(prevProps, nextProps) => {
return (
prevProps.item.hash === nextProps.item.hash &&
prevProps.item.received === nextProps.item.received &&
prevProps.itemPriceUnit === nextProps.itemPriceUnit &&
prevProps.walletID === nextProps.walletID
);
},
); );

View file

@ -108,13 +108,14 @@ const TransactionsNavigationHeader: React.FC<TransactionsNavigationHeaderProps>
]; ];
}, []); }, []);
const balance = useMemo(() => { const currentBalance = wallet ? wallet.getBalance() : 0;
const balanceFormatted = const formattedBalance = useMemo(() => {
unit === BitcoinUnit.LOCAL_CURRENCY return unit === BitcoinUnit.LOCAL_CURRENCY
? formatBalance(wallet.getBalance(), unit, true) ? formatBalance(currentBalance, unit, true)
: formatBalanceWithoutSuffix(wallet.getBalance(), unit, true); : formatBalanceWithoutSuffix(currentBalance, unit, true);
return !hideBalance && balanceFormatted; }, [unit, currentBalance]);
}, [unit, wallet, hideBalance]);
const balance = !wallet.hideBalance && formattedBalance;
const toolTipWalletBalanceActions = useMemo(() => { const toolTipWalletBalanceActions = useMemo(() => {
return hideBalance return hideBalance

View file

@ -1,4 +1,4 @@
import React, { forwardRef, useCallback, useImperativeHandle, useRef } from 'react'; import React, { forwardRef, useCallback, useImperativeHandle, useMemo, useRef } from 'react';
import { import {
Animated, Animated,
FlatList, FlatList,
@ -98,8 +98,12 @@ interface WalletCarouselItemProps {
isSelectedWallet?: boolean; isSelectedWallet?: boolean;
customStyle?: ViewStyle; customStyle?: ViewStyle;
horizontal?: boolean; horizontal?: boolean;
isPlaceHolder?: boolean;
searchQuery?: string; searchQuery?: string;
renderHighlightedText?: (text: string, query: string) => JSX.Element; renderHighlightedText?: (text: string, query: string) => JSX.Element;
animationsEnabled?: boolean;
onPressIn?: () => void;
onPressOut?: () => void;
} }
const iStyles = StyleSheet.create({ const iStyles = StyleSheet.create({
@ -161,21 +165,6 @@ const iStyles = StyleSheet.create({
}, },
}); });
interface WalletCarouselItemProps {
item: TWallet;
onPress: (item: TWallet) => void;
handleLongPress?: () => void;
isSelectedWallet?: boolean;
customStyle?: ViewStyle;
horizontal?: boolean;
isPlaceHolder?: boolean;
searchQuery?: string;
renderHighlightedText?: (text: string, query: string) => JSX.Element;
animationsEnabled?: boolean;
onPressIn?: () => void;
onPressOut?: () => void;
}
export const WalletCarouselItem: React.FC<WalletCarouselItemProps> = React.memo( export const WalletCarouselItem: React.FC<WalletCarouselItemProps> = React.memo(
({ ({
item, item,
@ -198,34 +187,31 @@ export const WalletCarouselItem: React.FC<WalletCarouselItemProps> = React.memo(
const itemWidth = width * 0.82 > 375 ? 375 : width * 0.82; const itemWidth = width * 0.82 > 375 ? 375 : width * 0.82;
const { isLargeScreen } = useIsLargeScreen(); const { isLargeScreen } = useIsLargeScreen();
const springConfig = useMemo(() => ({ useNativeDriver: true, tension: 100 }), []);
const animateScale = useCallback(
(toValue: number, callback?: () => void) => {
Animated.spring(scaleValue, { toValue, ...springConfig }).start(callback);
},
[scaleValue, springConfig],
);
const onPressedIn = useCallback(() => { const onPressedIn = useCallback(() => {
if (animationsEnabled) { if (animationsEnabled) {
Animated.spring(scaleValue, { animateScale(0.95);
toValue: 0.95,
useNativeDriver: true,
friction: 3,
tension: 100,
}).start();
} }
if (onPressIn) onPressIn(); if (onPressIn) onPressIn();
}, [scaleValue, animationsEnabled, onPressIn]); }, [animateScale, animationsEnabled, onPressIn]);
const onPressedOut = useCallback(() => { const onPressedOut = useCallback(() => {
if (animationsEnabled) { if (animationsEnabled) {
Animated.spring(scaleValue, { animateScale(1.0);
toValue: 1.0,
useNativeDriver: true,
friction: 3,
tension: 100,
}).start();
} }
if (onPressOut) onPressOut(); if (onPressOut) onPressOut();
}, [scaleValue, animationsEnabled, onPressOut]); }, [animateScale, animationsEnabled, onPressOut]);
const handlePress = useCallback(() => { const handlePress = useCallback(() => {
onPressedOut();
onPress(item); onPress(item);
}, [item, onPress, onPressedOut]); }, [item, onPress]);
const opacity = isSelectedWallet === false ? 0.5 : 1.0; const opacity = isSelectedWallet === false ? 0.5 : 1.0;
let image; let image;
@ -267,6 +253,8 @@ export const WalletCarouselItem: React.FC<WalletCarouselItemProps> = React.memo(
if (handleLongPress) handleLongPress(); if (handleLongPress) handleLongPress();
}} }}
onPress={handlePress} onPress={handlePress}
delayHoverIn={0}
delayHoverOut={0}
> >
<View style={[iStyles.shadowContainer, { backgroundColor: colors.background, shadowColor: colors.shadowColor }]}> <View style={[iStyles.shadowContainer, { backgroundColor: colors.background, shadowColor: colors.shadowColor }]}>
<LinearGradient colors={WalletGradient.gradientsFor(item.type)} style={iStyles.grad}> <LinearGradient colors={WalletGradient.gradientsFor(item.type)} style={iStyles.grad}>
@ -362,6 +350,10 @@ const WalletsCarousel = forwardRef<FlatListRefType, WalletsCarouselProps>((props
renderHighlightedText, renderHighlightedText,
isFlatList = true, isFlatList = true,
} = props; } = props;
const { width } = useWindowDimensions();
const itemWidth = React.useMemo(() => (width * 0.82 > 375 ? 375 : width * 0.82), [width]);
const renderItem = useCallback( const renderItem = useCallback(
({ item, index }: ListRenderItemInfo<TWallet>) => ({ item, index }: ListRenderItemInfo<TWallet>) =>
item ? ( item ? (
@ -379,7 +371,6 @@ const WalletsCarousel = forwardRef<FlatListRefType, WalletsCarouselProps>((props
); );
const flatListRef = useRef<FlatList<any>>(null); const flatListRef = useRef<FlatList<any>>(null);
useImperativeHandle(ref, (): any => { useImperativeHandle(ref, (): any => {
return { return {
scrollToEnd: (params: { animated?: boolean | null | undefined } | undefined) => flatListRef.current?.scrollToEnd(params), scrollToEnd: (params: { animated?: boolean | null | undefined } | undefined) => flatListRef.current?.scrollToEnd(params),
@ -401,10 +392,8 @@ const WalletsCarousel = forwardRef<FlatListRefType, WalletsCarouselProps>((props
getNativeScrollRef: () => flatListRef.current?.getNativeScrollRef(), getNativeScrollRef: () => flatListRef.current?.getNativeScrollRef(),
}; };
}, []); }, []);
const onScrollToIndexFailed = (error: { averageItemLength: number; index: number }): void => { const onScrollToIndexFailed = (error: { averageItemLength: number; index: number }): void => {
console.debug('onScrollToIndexFailed'); console.debug('onScrollToIndexFailed', error);
console.debug(error);
flatListRef.current?.scrollToOffset({ offset: error.averageItemLength * error.index, animated: true }); flatListRef.current?.scrollToOffset({ offset: error.averageItemLength * error.index, animated: true });
setTimeout(() => { setTimeout(() => {
if (data.length !== 0 && flatListRef.current !== null) { if (data.length !== 0 && flatListRef.current !== null) {
@ -413,16 +402,16 @@ const WalletsCarousel = forwardRef<FlatListRefType, WalletsCarouselProps>((props
}, 100); }, 100);
}; };
const { width } = useWindowDimensions();
const sliderHeight = 195; const sliderHeight = 195;
const itemWidth = width * 0.82 > 375 ? 375 : width * 0.82;
const keyExtractor = useCallback((item: TWallet, index: number) => (item?.getID ? item.getID() : index.toString()), []);
return isFlatList ? ( return isFlatList ? (
<FlatList <FlatList
ref={flatListRef} ref={flatListRef}
renderItem={renderItem} renderItem={renderItem}
extraData={data} extraData={data}
keyExtractor={(_, index) => index.toString()} keyExtractor={keyExtractor}
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
pagingEnabled={horizontal} pagingEnabled={horizontal}
disableIntervalMomentum={horizontal} disableIntervalMomentum={horizontal}
@ -433,6 +422,7 @@ const WalletsCarousel = forwardRef<FlatListRefType, WalletsCarouselProps>((props
showsHorizontalScrollIndicator={false} showsHorizontalScrollIndicator={false}
initialNumToRender={10} initialNumToRender={10}
scrollEnabled={scrollEnabled} scrollEnabled={scrollEnabled}
keyboardShouldPersistTaps="handled"
ListHeaderComponent={ListHeaderComponent} ListHeaderComponent={ListHeaderComponent}
style={{ minHeight: sliderHeight + 12 }} style={{ minHeight: sliderHeight + 12 }}
onScrollToIndexFailed={onScrollToIndexFailed} onScrollToIndexFailed={onScrollToIndexFailed}

View file

@ -40,9 +40,7 @@ const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: Ad
borderBottomColor: colors.lightBorder, borderBottomColor: colors.lightBorder,
backgroundColor: colors.elevated, backgroundColor: colors.elevated,
}, },
list: {
color: colors.buttonTextColor,
},
index: { index: {
color: colors.alternativeTextColor, color: colors.alternativeTextColor,
}, },
@ -151,24 +149,29 @@ const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: Ad
title={item.address} title={item.address}
actions={menuActions} actions={menuActions}
onPressMenuItem={onToolTipPress} onPressMenuItem={onToolTipPress}
// Revisit once RNMenu has renderPreview prop
renderPreview={renderPreview} renderPreview={renderPreview}
onPress={navigateToReceive} onPress={navigateToReceive}
isButton isButton
> >
<ListItem key={item.key} containerStyle={stylesHook.container}> <ListItem key={item.key} containerStyle={stylesHook.container}>
<ListItem.Content style={stylesHook.list}> <ListItem.Content>
<ListItem.Title style={stylesHook.list} numberOfLines={1} ellipsizeMode="middle"> <View style={styles.row}>
<Text style={[styles.index, stylesHook.index]}>{item.index + 1}</Text>{' '} <View style={styles.leftSection}>
<Text style={[stylesHook.address, styles.address]}>{item.address}</Text> <Text style={[styles.index, stylesHook.index]}>{item.index}</Text>
</ListItem.Title> </View>
<View style={styles.subtitle}> <View style={styles.middleSection}>
<Text style={[stylesHook.list, styles.balance, stylesHook.balance]}>{balance}</Text> <Text style={[stylesHook.address, styles.address]} numberOfLines={1} ellipsizeMode="middle">
{item.address}
</Text>
<Text style={[stylesHook.balance, styles.balance]}>{balance}</Text>
</View>
</View> </View>
</ListItem.Content> </ListItem.Content>
<View> <View style={styles.rightContainer}>
<AddressTypeBadge isInternal={item.isInternal} hasTransactions={hasTransactions} /> <AddressTypeBadge isInternal={item.isInternal} hasTransactions={hasTransactions} />
<Text style={[stylesHook.list, styles.balance, stylesHook.balance]}> <Text style={[stylesHook.balance, styles.balance]}>
{loc.addresses.transactions}: {item.transactions} {loc.addresses.transactions}: {item.transactions ?? 0}
</Text> </Text>
</View> </View>
</ListItem> </ListItem>
@ -179,20 +182,27 @@ const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: Ad
const styles = StyleSheet.create({ const styles = StyleSheet.create({
address: { address: {
fontWeight: 'bold', fontWeight: 'bold',
marginHorizontal: 40, marginHorizontal: 4,
}, },
index: { index: {
fontSize: 15, fontSize: 15,
}, },
balance: { balance: {
marginTop: 8, marginTop: 4,
marginLeft: 14,
}, },
subtitle: { row: {
flex: 1,
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'space-between', alignItems: 'center',
width: '100%', },
leftSection: {
marginRight: 8,
},
middleSection: {
flex: 1,
},
rightContainer: {
justifyContent: 'center',
alignItems: 'flex-end',
}, },
}); });

View file

@ -1,6 +1,6 @@
import { NativeStackNavigationOptions } from '@react-navigation/native-stack'; import { NativeStackNavigationOptions } from '@react-navigation/native-stack';
import React from 'react'; import React from 'react';
import { Image, Keyboard, StyleSheet, TouchableOpacity } from 'react-native'; import { Image, Keyboard, Platform, StyleSheet, TouchableOpacity } from 'react-native';
import loc from '../loc'; import loc from '../loc';
import { Theme } from './themes'; import { Theme } from './themes';
@ -59,7 +59,6 @@ const navigationStyle = (
{ {
closeButtonPosition, closeButtonPosition,
onCloseButtonPressed, onCloseButtonPressed,
headerBackVisible = true,
...opts ...opts
}: NativeStackNavigationOptions & { }: NativeStackNavigationOptions & {
closeButtonPosition?: CloseButtonPosition; closeButtonPosition?: CloseButtonPosition;
@ -78,11 +77,6 @@ const navigationStyle = (
let headerRight; let headerRight;
let headerLeft; let headerLeft;
if (!headerBackVisible) {
headerLeft = () => <></>;
opts.headerLeft = headerLeft;
}
if (closeButton === CloseButtonPosition.Right) { if (closeButton === CloseButtonPosition.Right) {
headerRight = () => ( headerRight = () => (
<TouchableOpacity <TouchableOpacity
@ -108,17 +102,24 @@ const navigationStyle = (
</TouchableOpacity> </TouchableOpacity>
); );
} }
const baseHeaderStyle = {
let options: NativeStackNavigationOptions = {
headerShadowVisible: false, headerShadowVisible: false,
headerTitleStyle: { headerTitleStyle: {
fontWeight: '600', fontWeight: '600' as const,
color: theme.colors.foregroundColor, color: theme.colors.foregroundColor,
}, },
headerBackTitleVisible: false,
headerTintColor: theme.colors.foregroundColor, headerTintColor: theme.colors.foregroundColor,
headerBackButtonDisplayMode: 'minimal',
};
const isLeftCloseButtonAndroid = closeButton === CloseButtonPosition.Left && Platform.OS === 'android';
const leftCloseButtonStyle = isLeftCloseButtonAndroid ? { headerBackImageSource: theme.closeImage } : { headerLeft };
let options: NativeStackNavigationOptions = {
...baseHeaderStyle,
...leftCloseButtonStyle,
headerBackButtonDisplayMode: 'minimal',
headerRight, headerRight,
headerLeft,
...opts, ...opts,
}; };

View file

@ -192,6 +192,40 @@ end
# =========================== # ===========================
platform :ios do platform :ios do
# Add helper methods for error handling and retries
def ensure_env_vars(vars)
vars.each do |var|
UI.user_error!("#{var} environment variable is missing") if ENV[var].nil? || ENV[var].empty?
end
end
def log_success(message)
UI.success("✅ #{message}")
end
def log_error(message)
UI.error("❌ #{message}")
end
# Method to safely call actions with retry logic
def with_retry(max_attempts = 3, action_name = "")
attempts = 0
begin
attempts += 1
yield
rescue => e
if attempts < max_attempts
wait_time = 10 * attempts
log_error("Attempt #{attempts}/#{max_attempts} for #{action_name} failed: #{e.message}")
UI.message("Retrying in #{wait_time} seconds...")
sleep(wait_time)
retry
else
log_error("#{action_name} failed after #{max_attempts} attempts: #{e.message}")
raise e
end
end
end
desc "Register new devices from a file" desc "Register new devices from a file"
lane :register_devices_from_txt do lane :register_devices_from_txt do
@ -238,26 +272,33 @@ platform :ios do
desc "Synchronize certificates and provisioning profiles" desc "Synchronize certificates and provisioning profiles"
lane :setup_provisioning_profiles do lane :setup_provisioning_profiles do
required_vars = ["GIT_ACCESS_TOKEN", "GIT_URL", "ITC_TEAM_ID", "ITC_TEAM_NAME", "KEYCHAIN_PASSWORD"]
ensure_env_vars(required_vars)
UI.message("Setting up provisioning profiles...") UI.message("Setting up provisioning profiles...")
platform = "ios"
# Iterate over app identifiers to fetch provisioning profiles # Iterate over app identifiers to fetch provisioning profiles
app_identifiers.each do |app_identifier| app_identifiers.each do |app_identifier|
match( with_retry(3, "Fetching provisioning profile for #{app_identifier}") do
git_basic_authorization: ENV["GIT_ACCESS_TOKEN"], UI.message("Fetching provisioning profile for #{app_identifier}...")
git_url: ENV["GIT_URL"], match(
type: "appstore", git_basic_authorization: ENV["GIT_ACCESS_TOKEN"],
clone_branch_directly: true, # Skip if the branch already exists git_url: ENV["GIT_URL"],
platform: platform, type: "appstore",
app_identifier: app_identifier, clone_branch_directly: true,
team_id: ENV["ITC_TEAM_ID"], platform: "ios",
team_name: ENV["ITC_TEAM_NAME"], app_identifier: app_identifier,
readonly: true, team_id: ENV["ITC_TEAM_ID"],
keychain_name: "temp_keychain", team_name: ENV["ITC_TEAM_NAME"],
keychain_password: ENV["KEYCHAIN_PASSWORD"] readonly: true,
) keychain_name: "temp_keychain",
keychain_password: ENV["KEYCHAIN_PASSWORD"]
)
log_success("Successfully fetched provisioning profile for #{app_identifier}")
end
end end
log_success("All provisioning profiles set up")
end end
desc "Fetch development certificates and provisioning profiles for Mac Catalyst" desc "Fetch development certificates and provisioning profiles for Mac Catalyst"
@ -402,48 +443,114 @@ lane :upload_bugsnag_sourcemaps do
end end
desc "Build the iOS app" desc "Build the iOS app"
lane :build_app_lane do lane :build_app_lane do
Dir.chdir(project_root) do Dir.chdir(project_root) do
UI.message("Building the application from: #{Dir.pwd}") UI.message("Building the application from: #{Dir.pwd}")
workspace_path = File.join(project_root, "ios", "BlueWallet.xcworkspace") workspace_path = File.join(project_root, "ios", "BlueWallet.xcworkspace")
export_options_path = File.join(project_root, "ios", "export_options.plist") export_options_path = File.join(project_root, "ios", "export_options.plist")
clear_derived_data_lane clear_derived_data_lane
begin # Determine which iOS version to use
build_ios_app( ios_version = determine_ios_version
scheme: "BlueWallet",
workspace: workspace_path,
export_method: "app-store",
include_bitcode: false,
configuration: "Release",
skip_profile_detection: false,
include_symbols: true,
export_team_id: ENV["ITC_TEAM_ID"],
export_options: export_options_path,
output_directory: File.join(project_root, "ios", "build"),
output_name: "BlueWallet_#{ENV['PROJECT_VERSION']}_#{ENV['NEW_BUILD_NUMBER']}.ipa",
buildlog_path: File.join(project_root, "ios", "build_logs"),
silent: false,
clean: true
)
rescue => e
UI.user_error!("build_ios_app failed: #{e.message}")
end
# Use File.join to construct paths without extra slashes UI.message("Using iOS version: #{ios_version}")
ipa_path = lane_context[SharedValues::IPA_OUTPUT_PATH] UI.message("Using export options from: #{export_options_path}")
# Define the IPA output path before building
ipa_directory = File.join(project_root, "ios", "build")
ipa_name = "BlueWallet_#{ENV['PROJECT_VERSION']}_#{ENV['NEW_BUILD_NUMBER']}.ipa"
ipa_path = File.join(ipa_directory, ipa_name)
begin
build_ios_app(
scheme: "BlueWallet",
workspace: workspace_path,
export_method: "app-store",
export_options: export_options_path,
output_directory: ipa_directory,
output_name: ipa_name,
buildlog_path: File.join(project_root, "ios", "build_logs"),
)
rescue => e
UI.user_error!("build_ios_app failed: #{e.message}")
end
if ipa_path && File.exist?(ipa_path) # Check for IPA path from both our defined path and fastlane's context
UI.message("IPA successfully found at: #{ipa_path}") ipa_path = lane_context[SharedValues::IPA_OUTPUT_PATH] || ipa_path
# Ensure the directory exists
FileUtils.mkdir_p(File.dirname(ipa_path)) unless Dir.exist?(File.dirname(ipa_path))
if ipa_path && File.exist?(ipa_path)
UI.message("IPA successfully found at: #{ipa_path}")
else
# Try to find any IPA file as fallback
Dir.chdir(project_root) do
fallback_ipa = Dir.glob("**/*.ipa").first
if fallback_ipa
ipa_path = File.join(project_root, fallback_ipa)
UI.message("Found fallback IPA at: #{ipa_path}")
else
UI.user_error!("No IPA file found after build")
end
end
end
# Set both environment variable and GitHub Actions output
ENV['IPA_OUTPUT_PATH'] = ipa_path ENV['IPA_OUTPUT_PATH'] = ipa_path
sh("echo 'IPA_OUTPUT_PATH=#{ipa_path}' >> $GITHUB_ENV") # Export for GitHub Actions # Set both standard output format and the newer GITHUB_OUTPUT format
else sh("echo 'ipa_output_path=#{ipa_path}' >> $GITHUB_OUTPUT") if ENV['GITHUB_OUTPUT']
UI.user_error!("IPA not found after build_ios_app.") sh("echo ::set-output name=ipa_output_path::#{ipa_path}")
# Also write path to a file that can be read by subsequent steps
ipa_path_file = "#{ipa_directory}/ipa_path.txt"
File.write(ipa_path_file, ipa_path)
UI.success("Saved IPA path to: #{ipa_path_file}")
end end
end end
end
desc "Delete temporary keychain"
lane :delete_temp_keychain do
UI.message("Deleting temporary keychain...")
delete_keychain(
name: "temp_keychain"
) if File.exist?(File.expand_path("~/Library/Keychains/temp_keychain-db"))
UI.message("Temporary keychain deleted successfully.")
end
# Helper method to determine which iOS version to use
private_lane :determine_ios_version do
# Get available iOS simulator runtimes
runtimes_output = sh("xcrun simctl list runtimes 2>&1", log: false) rescue ""
if runtimes_output.include?("iOS")
# Extract available iOS versions
ios_versions = runtimes_output.scan(/iOS ([0-9.]+)/)
.flatten
.map { |v| Gem::Version.new(v) }
.sort
.reverse
if ios_versions.any?
latest_version = ios_versions.first.to_s
UI.success("Found iOS simulator version: #{latest_version}")
latest_version # Implicit return - last expression is returned
else
# Default to a reasonable iOS version if none found
UI.important("No iOS simulator versions found. Using default version.")
"17.6" # Implicit return
end
else
# Default to a reasonable iOS version if no iOS runtimes
UI.important("No iOS simulator runtimes found. Using default version.")
"17.6" # Implicit return
end
end
end end
# =========================== # ===========================
# Global Lanes # Global Lanes

View file

@ -3,33 +3,39 @@
# URL of the Git repository to store the certificates # URL of the Git repository to store the certificates
git_url(ENV["GIT_URL"]) git_url(ENV["GIT_URL"])
# Define the type of match to run, could be one of 'appstore', 'adhoc', 'development', or 'enterprise'. # Define the type of match to run
# For example, use 'appstore' for App Store builds, 'adhoc' for Ad Hoc distribution, # Default to "appstore" but can be overridden
# 'development' for development builds, and 'enterprise' for In-House (enterprise) distribution. type(ENV["MATCH_TYPE"] || "appstore")
type("appstore")
app_identifier(["io.bluewallet.bluewallet", "io.bluewallet.bluewallet.watch", "io.bluewallet.bluewallet.watch.extension", "io.bluewallet.bluewallet.Stickers", "io.bluewallet.bluewallet.MarketWidget"]) # Replace with your app identifiers # App identifiers for all BlueWallet apps
app_identifier([
"io.bluewallet.bluewallet",
"io.bluewallet.bluewallet.watch",
"io.bluewallet.bluewallet.watch.extension",
"io.bluewallet.bluewallet.Stickers",
"io.bluewallet.bluewallet.MarketWidget"
])
# List of app identifiers to create provisioning profiles for. # Your Apple Developer account email address
# Replace with your app's bundle identifier(s).
# Your Apple Developer account email address.
username(ENV["APPLE_ID"]) username(ENV["APPLE_ID"])
# The ID of your Apple Developer team if you're part of multiple teams # The ID of your Apple Developer team
team_id(ENV["ITC_TEAM_ID"]) team_id(ENV["ITC_TEAM_ID"])
# Set this to true if match should only read existing certificates and profiles # Set readonly based on environment (default to true for safety)
# and not create new ones. # Set to false explicitly when new profiles need to be created
readonly(true) readonly(ENV["MATCH_READONLY"] == "false" ? false : true)
# Optional: The Git branch that is used for match. # Define the platform to use
# Default is 'master'.
# Optional: Path to a specific SSH key to be used by match.
# Only needed if you're using a private repository and match needs to use SSH keys for authentication.
# ssh_key("/path/to/your/private/key")
# Optional: Define the platform to use, can be 'ios', 'macos', or 'tvos'.
# For React Native projects, you'll typically use 'ios'.
platform("ios") platform("ios")
# Git basic authentication through access token
# This is useful for CI/CD environments where SSH keys aren't available
git_basic_authorization(ENV["GIT_ACCESS_TOKEN"])
# Storage mode (git by default)
storage_mode("git")
# Optional: The Git branch that is used for match
# Default is 'master'
# branch("main")

View file

@ -4,3 +4,4 @@
gem 'fastlane-plugin-browserstack' gem 'fastlane-plugin-browserstack'
gem 'fastlane-plugin-bugsnag_sourcemaps_upload' gem 'fastlane-plugin-bugsnag_sourcemaps_upload'
gem "fastlane-plugin-bugsnag"

1
gesture-handler.js Normal file
View file

@ -0,0 +1 @@
// Don't import react-native-gesture-handler on web

View file

@ -0,0 +1,2 @@
// Only import react-native-gesture-handler on native platforms
import 'react-native-gesture-handler';

View file

@ -1,5 +1,6 @@
import { Platform } from 'react-native'; import { Platform } from 'react-native';
import { check, request, PERMISSIONS, RESULTS } from 'react-native-permissions'; import { check, request, PERMISSIONS, RESULTS } from 'react-native-permissions';
import { navigationRef } from '../NavigationService.ts';
const isCameraAuthorizationStatusGranted = async () => { const isCameraAuthorizationStatusGranted = async () => {
const status = await check(Platform.OS === 'android' ? PERMISSIONS.ANDROID.CAMERA : PERMISSIONS.IOS.CAMERA); const status = await check(Platform.OS === 'android' ? PERMISSIONS.ANDROID.CAMERA : PERMISSIONS.IOS.CAMERA);
@ -10,4 +11,18 @@ const requestCameraAuthorization = () => {
return request(Platform.OS === 'android' ? PERMISSIONS.ANDROID.CAMERA : PERMISSIONS.IOS.CAMERA); return request(Platform.OS === 'android' ? PERMISSIONS.ANDROID.CAMERA : PERMISSIONS.IOS.CAMERA);
}; };
export { isCameraAuthorizationStatusGranted, requestCameraAuthorization }; const scanQrHelper = async (): Promise<string> => {
await requestCameraAuthorization();
return new Promise(resolve => {
if (navigationRef.isReady()) {
navigationRef.navigate('ScanQRCode', {
showFileImportButton: true,
onBarScanned: (data: string) => {
resolve(data);
},
});
}
});
};
export { isCameraAuthorizationStatusGranted, requestCameraAuthorization, scanQrHelper };

23
helpers/screenProtect.ts Normal file
View file

@ -0,0 +1,23 @@
// import { enableSecureView, disableSecureView, forbidAndroidShare, allowAndroidShare } from 'react-native-prevent-screenshot-ios-android';
// import { Platform } from 'react-native';
// import { isDesktop } from '../blue_modules/environment';
export const enableScreenProtect = () => {
// if (isDesktop) return;
// if (Platform.OS === 'ios') {
// enableSecureView();
// } else if (Platform.OS === 'android') {
// forbidAndroidShare();
// }
};
export const disableScreenProtect = () => {
// if (isDesktop) return;
// if (Platform.OS === 'ios') {
// disableSecureView();
// } else if (Platform.OS === 'android') {
// allowAndroidShare();
// }
};
// CURRENTLY UNUSED AS WE WAIT FOR NAV 7 SUPPORT

View file

@ -1,5 +1,3 @@
import 'react-native-gesture-handler'; // should be on top
import { CommonActions } from '@react-navigation/native'; import { CommonActions } from '@react-navigation/native';
import { useCallback, useEffect, useRef } from 'react'; import { useCallback, useEffect, useRef } from 'react';
import { AppState, AppStateStatus, Linking } from 'react-native'; import { AppState, AppStateStatus, Linking } from 'react-native';
@ -21,25 +19,42 @@ import loc from '../loc';
import { Chain } from '../models/bitcoinUnits'; import { Chain } from '../models/bitcoinUnits';
import { navigationRef } from '../NavigationService'; import { navigationRef } from '../NavigationService';
import ActionSheet from '../screen/ActionSheet'; import ActionSheet from '../screen/ActionSheet';
import { useStorage } from '../hooks/context/useStorage'; import { useStorage } from './context/useStorage';
import RNQRGenerator from 'rn-qr-generator'; import RNQRGenerator from 'rn-qr-generator';
import presentAlert from './Alert'; import presentAlert from '../components/Alert';
import useMenuElements from '../hooks/useMenuElements'; import useWidgetCommunication from './useWidgetCommunication';
import useWidgetCommunication from '../hooks/useWidgetCommunication'; import useWatchConnectivity from './useWatchConnectivity';
import useWatchConnectivity from '../hooks/useWatchConnectivity'; import useDeviceQuickActions from './useDeviceQuickActions';
import useDeviceQuickActions from '../hooks/useDeviceQuickActions'; import useHandoffListener from './useHandoffListener';
import useHandoffListener from '../hooks/useHandoffListener'; import useMenuElements from './useMenuElements';
const ClipboardContentType = Object.freeze({ const ClipboardContentType = Object.freeze({
BITCOIN: 'BITCOIN', BITCOIN: 'BITCOIN',
LIGHTNING: 'LIGHTNING', LIGHTNING: 'LIGHTNING',
}); });
const CompanionDelegates = () => { /**
const { wallets, addWallet, saveToDisk, fetchAndSaveWalletTransactions, refreshAllWalletTransactions, setSharedCosigner } = useStorage(); * Hook that initializes all companion listeners and functionality without rendering a component
*/
const useCompanionListeners = (skipIfNotInitialized = true) => {
const {
wallets,
addWallet,
saveToDisk,
fetchAndSaveWalletTransactions,
refreshAllWalletTransactions,
setSharedCosigner,
walletsInitialized,
} = useStorage();
const appState = useRef<AppStateStatus>(AppState.currentState); const appState = useRef<AppStateStatus>(AppState.currentState);
const clipboardContent = useRef<undefined | string>(); const clipboardContent = useRef<undefined | string>();
// We need to call hooks unconditionally before any conditional logic
// We'll use this check inside the effects to conditionally run logic
const shouldActivateListeners = !skipIfNotInitialized || walletsInitialized;
// Initialize other hooks regardless of activation status
// They'll handle their own conditional logic internally
useWatchConnectivity(); useWatchConnectivity();
useWidgetCommunication(); useWidgetCommunication();
useMenuElements(); useMenuElements();
@ -47,6 +62,8 @@ const CompanionDelegates = () => {
useHandoffListener(); useHandoffListener();
const processPushNotifications = useCallback(async () => { const processPushNotifications = useCallback(async () => {
if (!shouldActivateListeners) return false;
await new Promise(resolve => setTimeout(resolve, 200)); await new Promise(resolve => setTimeout(resolve, 200));
try { try {
const notifications2process = await getStoredNotifications(); const notifications2process = await getStoredNotifications();
@ -166,49 +183,58 @@ const CompanionDelegates = () => {
console.error('Failed to process push notifications:', error); console.error('Failed to process push notifications:', error);
} }
return false; return false;
}, [fetchAndSaveWalletTransactions, refreshAllWalletTransactions, wallets]); }, [fetchAndSaveWalletTransactions, refreshAllWalletTransactions, wallets, shouldActivateListeners]);
useEffect(() => { useEffect(() => {
if (!shouldActivateListeners) return;
initializeNotifications(processPushNotifications); initializeNotifications(processPushNotifications);
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, [shouldActivateListeners]);
const handleOpenURL = useCallback( const handleOpenURL = useCallback(
async (event: { url: string }): Promise<void> => { async (event: { url: string }): Promise<void> => {
const { url } = event; if (!shouldActivateListeners) return;
if (url) { try {
const decodedUrl = decodeURIComponent(url); if (!event.url) return;
const fileName = decodedUrl.split('/').pop()?.toLowerCase(); let decodedUrl: string;
try {
if (fileName && /\.(jpe?g|png)$/i.test(fileName)) { decodedUrl = decodeURIComponent(event.url);
} catch (e) {
console.error('Failed to decode URL, using original', e);
decodedUrl = event.url;
}
const fileName = decodedUrl.split('/').pop()?.toLowerCase() || '';
if (/\.(jpe?g|png)$/i.test(fileName)) {
let qrResult;
try { try {
if (!decodedUrl) { qrResult = await RNQRGenerator.detect({ uri: decodedUrl });
throw new Error(loc.send.qr_error_no_qrcode); } catch (e) {
console.error('QR detection first attempt failed:', e);
}
if (!qrResult || !qrResult.values || qrResult.values.length === 0) {
const altUrl = decodedUrl.replace(/^file:\/\//, '');
try {
qrResult = await RNQRGenerator.detect({ uri: altUrl });
} catch (e) {
console.error('QR detection second attempt failed:', e);
} }
const values = await RNQRGenerator.detect({ }
uri: decodedUrl, if (qrResult?.values?.length) {
}); triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
DeeplinkSchemaMatch.navigationRouteFor(
if (values && values.values.length > 0) { { url: qrResult.values[0] },
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess); (value: [string, any]) => navigationRef.navigate(...value),
DeeplinkSchemaMatch.navigationRouteFor( {
{ url: values.values[0] }, wallets,
(value: [string, any]) => navigationRef.navigate(...value), addWallet,
{ saveToDisk,
wallets, setSharedCosigner,
addWallet, },
saveToDisk, );
setSharedCosigner, } else {
}, throw new Error(loc.send.qr_error_no_qrcode);
);
} else {
throw new Error(loc.send.qr_error_no_qrcode);
}
} catch (error) {
console.error('Error detecting QR code:', error);
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
presentAlert({ message: loc.send.qr_error_no_qrcode });
} }
} else { } else {
DeeplinkSchemaMatch.navigationRouteFor(event, (value: [string, any]) => navigationRef.navigate(...value), { DeeplinkSchemaMatch.navigationRouteFor(event, (value: [string, any]) => navigationRef.navigate(...value), {
@ -218,12 +244,19 @@ const CompanionDelegates = () => {
setSharedCosigner, setSharedCosigner,
}); });
} }
} catch (err: any) {
console.error('Error in handleOpenURL:', err);
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
presentAlert({ message: err.message || loc.send.qr_error_no_qrcode });
} }
}, },
[wallets, addWallet, saveToDisk, setSharedCosigner], [wallets, addWallet, saveToDisk, setSharedCosigner, shouldActivateListeners],
); );
const showClipboardAlert = useCallback( const showClipboardAlert = useCallback(
({ contentType }: { contentType: undefined | string }) => { ({ contentType }: { contentType: undefined | string }) => {
if (!shouldActivateListeners) return;
triggerHapticFeedback(HapticFeedbackTypes.ImpactLight); triggerHapticFeedback(HapticFeedbackTypes.ImpactLight);
getClipboardContent().then(clipboard => { getClipboardContent().then(clipboard => {
if (!clipboard) return; if (!clipboard) return;
@ -246,12 +279,13 @@ const CompanionDelegates = () => {
); );
}); });
}, },
[handleOpenURL], [handleOpenURL, shouldActivateListeners],
); );
const handleAppStateChange = useCallback( const handleAppStateChange = useCallback(
async (nextAppState: AppStateStatus | undefined) => { async (nextAppState: AppStateStatus | undefined) => {
if (wallets.length === 0) return; if (!shouldActivateListeners || wallets.length === 0) return;
if ((appState.current.match(/background/) && nextAppState === 'active') || nextAppState === undefined) { if ((appState.current.match(/background/) && nextAppState === 'active') || nextAppState === undefined) {
setTimeout(() => A(A.ENUM.APP_UNSUSPENDED), 2000); setTimeout(() => A(A.ENUM.APP_UNSUSPENDED), 2000);
updateExchangeRate(); updateExchangeRate();
@ -291,10 +325,12 @@ const CompanionDelegates = () => {
appState.current = nextAppState; appState.current = nextAppState;
} }
}, },
[processPushNotifications, showClipboardAlert, wallets], [processPushNotifications, showClipboardAlert, wallets, shouldActivateListeners],
); );
const addListeners = useCallback(() => { const addListeners = useCallback(() => {
if (!shouldActivateListeners) return { urlSubscription: null, appStateSubscription: null };
const urlSubscription = Linking.addEventListener('url', handleOpenURL); const urlSubscription = Linking.addEventListener('url', handleOpenURL);
const appStateSubscription = AppState.addEventListener('change', handleAppStateChange); const appStateSubscription = AppState.addEventListener('change', handleAppStateChange);
@ -302,18 +338,16 @@ const CompanionDelegates = () => {
urlSubscription, urlSubscription,
appStateSubscription, appStateSubscription,
}; };
}, [handleOpenURL, handleAppStateChange]); }, [handleOpenURL, handleAppStateChange, shouldActivateListeners]);
useEffect(() => { useEffect(() => {
const subscriptions = addListeners(); const subscriptions = addListeners();
return () => { return () => {
subscriptions.urlSubscription?.remove(); subscriptions.urlSubscription?.remove?.();
subscriptions.appStateSubscription?.remove(); subscriptions.appStateSubscription?.remove?.();
}; };
}, [addListeners]); }, [addListeners]);
return null;
}; };
export default CompanionDelegates; export default useCompanionListeners;

View file

@ -1,23 +1,27 @@
import { useState, useEffect } from 'react'; import { useState, useEffect, useMemo } from 'react';
import debounce from '../blue_modules/debounce'; import debounce from '../blue_modules/debounce';
const useDebounce = <T>(value: T, delay: number): T => { // Overload signatures
function useDebounce<T extends (...args: any[]) => any>(callback: T, delay: number): T;
function useDebounce<T>(value: T, delay: number): T;
function useDebounce<T>(value: T, delay: number): T {
const isFn = typeof value === 'function';
const debouncedFunction = useMemo(() => {
return isFn ? debounce(value as unknown as (...args: any[]) => any, delay) : null;
}, [isFn, value, delay]);
const [debouncedValue, setDebouncedValue] = useState<T>(value); const [debouncedValue, setDebouncedValue] = useState<T>(value);
useEffect(() => { useEffect(() => {
const handler = debounce((val: T) => { if (!isFn) {
setDebouncedValue(val); const handler = setTimeout(() => setDebouncedValue(value), delay);
}, delay); return () => clearTimeout(handler);
}
}, [isFn, value, delay]);
handler(value); return isFn ? (debouncedFunction as unknown as T) : debouncedValue;
}
return () => {
handler.cancel();
};
}, [value, delay]);
return debouncedValue;
};
export default useDebounce; export default useDebounce;

View file

@ -7,7 +7,12 @@ import { requestCameraAuthorization } from '../helpers/scan-qr';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
// List of screens that require biometrics // List of screens that require biometrics
const requiresBiometrics = ['WalletExportRoot', 'WalletXpubRoot', 'ViewEditMultisigCosignersRoot', 'ExportMultisigCoordinationSetupRoot']; const requiresBiometrics = [
'WalletExportRoot',
'WalletXpubRoot',
'ViewEditMultisigCosigners',
'ExportMultisigCoordinationSetupRoot',
];
// List of screens that require wallet export to be saved // List of screens that require wallet export to be saved
const requiresWalletExportIsSaved = ['ReceiveDetailsRoot', 'WalletAddresses']; const requiresWalletExportIsSaved = ['ReceiveDetailsRoot', 'WalletAddresses'];
@ -17,8 +22,25 @@ export const useExtendedNavigation = <T extends NavigationProp<ParamListBase>>()
const { wallets, saveToDisk } = useStorage(); const { wallets, saveToDisk } = useStorage();
const { isBiometricUseEnabled } = useBiometrics(); const { isBiometricUseEnabled } = useBiometrics();
const enhancedNavigate: NavigationProp<ParamListBase>['navigate'] = useCallback( const enhancedNavigate = useCallback(
(screenOrOptions: any, params?: any, options?: { merge?: boolean }) => { (
...args:
| [string]
| [string, object | undefined]
| [string, object | undefined, { merge?: boolean }]
| [{ name: string; params?: object; path?: string; merge?: boolean }]
) => {
let screenOrOptions: any;
let params: any;
let options: { merge?: boolean } | undefined;
if (typeof args[0] === 'string') {
screenOrOptions = args[0];
params = args[1];
options = args[2];
} else {
screenOrOptions = args[0];
}
let screenName: string; let screenName: string;
if (typeof screenOrOptions === 'string') { if (typeof screenOrOptions === 'string') {
screenName = screenOrOptions; screenName = screenOrOptions;
@ -44,6 +66,13 @@ export const useExtendedNavigation = <T extends NavigationProp<ParamListBase>>()
}; };
(async () => { (async () => {
// NEW: If the current (active) screen is 'ScanQRCode', bypass all checks.
const currentRouteName = navigationRef.current?.getCurrentRoute()?.name;
if (currentRouteName === 'ScanQRCode') {
proceedWithNavigation();
return;
}
if (isRequiresBiometrics) { if (isRequiresBiometrics) {
const isBiometricsEnabled = await isBiometricUseEnabled(); const isBiometricsEnabled = await isBiometricUseEnabled();
if (isBiometricsEnabled) { if (isBiometricsEnabled) {
@ -53,8 +82,8 @@ export const useExtendedNavigation = <T extends NavigationProp<ParamListBase>>()
return; return;
} else { } else {
console.error('Biometric authentication failed'); console.error('Biometric authentication failed');
// Decide if navigation should proceed or not after failed authentication // Do not proceed if authentication fails.
return; // Prevent proceeding with the original navigation if bio fails return;
} }
} }
} }
@ -78,18 +107,17 @@ export const useExtendedNavigation = <T extends NavigationProp<ParamListBase>>()
await saveToDisk(); await saveToDisk();
proceedWithNavigation(); proceedWithNavigation();
} catch (error) { } catch (error) {
if (error) { // If there was an error (or the user cancelled), navigate to the wallet export screen.
originalNavigation.navigate('WalletExportRoot', { originalNavigation.navigate('WalletExportRoot', {
screen: 'WalletExport', screen: 'WalletExport',
params: { walletID }, params: { walletID },
}); });
}
} }
return; // Do not proceed with the original navigation if reminder was shown.
return; // Prevent proceeding with the original navigation if the reminder is shown
} }
} }
// If the target screen is ScanQRCode, request camera authorization.
if (screenName === 'ScanQRCode') { if (screenName === 'ScanQRCode') {
await requestCameraAuthorization(); await requestCameraAuthorization();
} }
@ -115,4 +143,4 @@ export const useExtendedNavigation = <T extends NavigationProp<ParamListBase>>()
// Usage example: // Usage example:
// type NavigationProps = NativeStackNavigationProp<SendDetailsStackParamList, 'SendDetails'>; // type NavigationProps = NativeStackNavigationProp<SendDetailsStackParamList, 'SendDetails'>;
// const navigation = useExtendedNavigation<NavigationProps>(); // const navigation = useExtendedNavigation<NavigationProps>();

View file

@ -23,20 +23,25 @@ const useHandoffListener = () => {
const handleUserActivity = useCallback( const handleUserActivity = useCallback(
(data: UserActivityData) => { (data: UserActivityData) => {
if (!data || !data.activityType) {
console.debug(`Invalid handoff data received: ${data ? JSON.stringify(data) : 'No data provided'}`);
return;
}
const { activityType, userInfo } = data; const { activityType, userInfo } = data;
const modifiedUserInfo = { ...(userInfo || {}), type: activityType };
try { try {
if (activityType === HandOffActivityType.ReceiveOnchain) { if (activityType === HandOffActivityType.ReceiveOnchain && modifiedUserInfo.address) {
navigate('ReceiveDetailsRoot', { navigate('ReceiveDetailsRoot', {
screen: 'ReceiveDetails', screen: 'ReceiveDetails',
params: { address: userInfo.address }, params: { address: modifiedUserInfo.address, type: activityType },
}); });
} else if (activityType === HandOffActivityType.Xpub) { } else if (activityType === HandOffActivityType.Xpub && modifiedUserInfo.xpub) {
navigate('WalletXpubRoot', { navigate('WalletXpubRoot', {
screen: 'WalletXpub', screen: 'WalletXpub',
params: { xpub: userInfo.xpub }, params: { xpub: modifiedUserInfo.xpub, type: activityType },
}); });
} else { } else {
console.debug(`Unhandled activity type: ${activityType}`); console.debug(`Unhandled or incomplete activity type/data: ${activityType}`, modifiedUserInfo);
} }
} catch (error) { } catch (error) {
console.error('Error handling user activity:', error); console.error('Error handling user activity:', error);
@ -50,9 +55,13 @@ const useHandoffListener = () => {
const activitySubscription = eventEmitter?.addListener('onUserActivityOpen', handleUserActivity); const activitySubscription = eventEmitter?.addListener('onUserActivityOpen', handleUserActivity);
EventEmitter.getMostRecentUserActivity?.() if (EventEmitter && EventEmitter.getMostRecentUserActivity) {
.then(handleUserActivity) EventEmitter.getMostRecentUserActivity()
.catch(() => console.debug('No userActivity object sent')); .then(handleUserActivity)
.catch(() => console.debug('No valid user activity object received'));
} else {
console.debug('EventEmitter native module is not available.');
}
return () => { return () => {
activitySubscription?.remove(); activitySubscription?.remove();

View file

@ -1,68 +1,168 @@
import { useCallback, useEffect, useMemo, useRef } from 'react'; import { useEffect, useCallback } from 'react';
import { NativeEventEmitter, NativeModules, Platform } from 'react-native'; import { NativeEventEmitter, NativeModules, Platform } from 'react-native';
import { navigationRef } from '../NavigationService';
import { CommonActions } from '@react-navigation/native'; import { CommonActions } from '@react-navigation/native';
import * as NavigationService from '../NavigationService';
import { useStorage } from './context/useStorage';
/* /*
Hook for managing iPadOS and macOS menu actions with keyboard shortcuts. Hook for managing iPadOS and macOS menu actions with keyboard shortcuts.
Uses MenuElementsEmitter for event handling. Uses MenuElementsEmitter for event handling and navigation state.
*/ */
type MenuActionHandler = () => void;
// Singleton setup - initialize once at module level
const { MenuElementsEmitter } = NativeModules; const { MenuElementsEmitter } = NativeModules;
const eventEmitter = let eventEmitter: NativeEventEmitter | null = null;
(Platform.OS === 'ios' || Platform.OS === 'macos') && MenuElementsEmitter ? new NativeEventEmitter(MenuElementsEmitter) : null; let listenersInitialized = false;
const useMenuElements = () => { // Registry for transaction handlers by screen ID
const { walletsInitialized } = useStorage(); const handlerRegistry = new Map<string, MenuActionHandler>();
const reloadTransactionsMenuActionRef = useRef<() => void>(() => {});
const setReloadTransactionsMenuActionFunction = useCallback((newFunction: () => void) => { // Store subscription references for proper cleanup
console.debug('Setting reloadTransactionsMenuActionFunction.'); let subscriptions: { remove: () => void }[] = [];
reloadTransactionsMenuActionRef.current = newFunction;
}, []);
const dispatchNavigate = useCallback((routeName: string, screen?: string) => { // Create a more robust emitter with error handling
NavigationService.dispatch(CommonActions.navigate({ name: routeName, params: screen ? { screen } : undefined })); try {
}, []); if (Platform.OS === 'ios' && MenuElementsEmitter) {
eventEmitter = new NativeEventEmitter(MenuElementsEmitter);
}
} catch (error) {
console.warn('[MenuElements] Failed to initialize event emitter: ', error);
eventEmitter = null;
}
const eventActions = useMemo( /**
() => ({ * Safely navigate using multiple fallback approaches
openSettings: () => dispatchNavigate('Settings'), */
addWallet: () => dispatchNavigate('AddWalletRoot'), function safeNavigate(routeName: string, params?: Record<string, any>): void {
importWallet: () => dispatchNavigate('AddWalletRoot', 'ImportWallet'), try {
reloadTransactions: () => { if (navigationRef.current?.isReady()) {
console.debug('Calling reloadTransactionsMenuActionFunction'); navigationRef.current.navigate(routeName as never, params as never);
reloadTransactionsMenuActionRef.current?.(); return;
}, }
}),
[dispatchNavigate],
);
if (navigationRef.isReady()) {
navigationRef.dispatch(
CommonActions.navigate({
name: routeName,
params,
}),
);
}
} catch (error) {
console.error(`[MenuElements] Navigation error:`, error);
}
}
// Cleanup event listeners to prevent memory leaks
function cleanupListeners(): void {
if (subscriptions.length > 0) {
subscriptions.forEach(subscription => {
try {
subscription.remove();
} catch (e) {
console.warn('[MenuElements] Error removing subscription:', e);
}
});
subscriptions = [];
listenersInitialized = false;
}
}
function initializeListeners(): void {
if (!eventEmitter || listenersInitialized) return;
cleanupListeners();
// Navigation actions
const globalActions = {
navigateToSettings: (): void => {
safeNavigate('Settings');
},
navigateToAddWallet: (): void => {
safeNavigate('AddWalletRoot');
},
navigateToImportWallet: (): void => {
safeNavigate('AddWalletRoot', { screen: 'ImportWallet' });
},
executeReloadTransactions: (): void => {
const currentRoute = navigationRef.current?.getCurrentRoute();
if (!currentRoute) return;
const screenName = currentRoute.name;
const params = (currentRoute.params as { walletID?: string }) || {};
const walletID = params.walletID;
const specificKey = walletID ? `${screenName}-${walletID}` : null;
const specificHandler = specificKey ? handlerRegistry.get(specificKey) : undefined;
const genericHandler = handlerRegistry.get(screenName);
const handler = specificHandler || genericHandler;
if (typeof handler === 'function') {
handler();
}
},
};
try {
subscriptions.push(eventEmitter.addListener('openSettings', globalActions.navigateToSettings));
subscriptions.push(eventEmitter.addListener('addWalletMenuAction', globalActions.navigateToAddWallet));
subscriptions.push(eventEmitter.addListener('importWalletMenuAction', globalActions.navigateToImportWallet));
subscriptions.push(eventEmitter.addListener('reloadTransactionsMenuAction', globalActions.executeReloadTransactions));
} catch (error) {
console.error('[MenuElements] Error setting up event listeners:', error);
}
listenersInitialized = true;
}
interface MenuElementsHook {
registerTransactionsHandler: (handler: MenuActionHandler, screenKey?: string) => boolean;
unregisterTransactionsHandler: (screenKey: string) => void;
isMenuElementsSupported: boolean;
}
const mountedComponents = new Set<string>();
const useMenuElements = (): MenuElementsHook => {
useEffect(() => { useEffect(() => {
if (!walletsInitialized || !eventEmitter) return; initializeListeners();
console.debug('Setting up menu event listeners'); const unsubscribe = navigationRef.addListener('state', () => {});
// Add permanent listeners only once
eventEmitter.removeAllListeners('openSettings');
eventEmitter.removeAllListeners('addWalletMenuAction');
eventEmitter.removeAllListeners('importWalletMenuAction');
eventEmitter.addListener('openSettings', eventActions.openSettings);
eventEmitter.addListener('addWalletMenuAction', eventActions.addWallet);
eventEmitter.addListener('importWalletMenuAction', eventActions.importWallet);
const reloadTransactionsListener = eventEmitter.addListener('reloadTransactionsMenuAction', eventActions.reloadTransactions);
return () => { return () => {
console.debug('Removing reloadTransactionsMenuAction listener'); unsubscribe();
reloadTransactionsListener.remove();
}; };
}, [walletsInitialized, eventActions]); }, []);
const registerTransactionsHandler = useCallback((handler: MenuActionHandler, screenKey?: string): boolean => {
if (typeof handler !== 'function') return false;
const key = screenKey || navigationRef.current?.getCurrentRoute()?.name;
if (!key) return false;
mountedComponents.add(key);
handlerRegistry.set(key, handler);
return true;
}, []);
const unregisterTransactionsHandler = useCallback((screenKey: string): void => {
if (!screenKey) return;
handlerRegistry.delete(screenKey);
mountedComponents.delete(screenKey);
}, []);
return { return {
setReloadTransactionsMenuActionFunction, registerTransactionsHandler,
unregisterTransactionsHandler,
isMenuElementsSupported: !!eventEmitter,
}; };
}; };

View file

@ -1,8 +1,28 @@
const useMenuElements = () => { import { useCallback } from 'react';
const setReloadTransactionsMenuActionFunction = (_: () => void) => {};
type MenuActionHandler = () => void;
interface MenuElementsHook {
registerTransactionsHandler: (handler: MenuActionHandler, screenKey?: string) => boolean;
unregisterTransactionsHandler: (screenKey: string) => void;
isMenuElementsSupported: boolean;
}
// Default implementation for platforms other than iOS
const useMenuElements = (): MenuElementsHook => {
const registerTransactionsHandler = useCallback((_handler: MenuActionHandler, _screenKey?: string): boolean => {
// Non-functional stub for non-iOS platforms
return false;
}, []);
const unregisterTransactionsHandler = useCallback((_screenKey: string): void => {
// No-op for non-supported platforms
}, []);
return { return {
setReloadTransactionsMenuActionFunction, registerTransactionsHandler,
unregisterTransactionsHandler,
isMenuElementsSupported: false, // Not supported on platforms other than iOS
}; };
}; };

View file

@ -1,3 +1,4 @@
import './gesture-handler';
import './shim.js'; import './shim.js';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
@ -12,7 +13,12 @@ if (!Error.captureStackTrace) {
Error.captureStackTrace = () => {}; Error.captureStackTrace = () => {};
} }
LogBox.ignoreLogs(['Require cycle:', 'Battery state `unknown` and monitoring disabled, this is normal for simulators and tvOS.']); LogBox.ignoreLogs([
'Require cycle:',
'Battery state `unknown` and monitoring disabled, this is normal for simulators and tvOS.',
'Open debugger to view warnings.',
'Non-serializable values were found in the navigation state',
]);
const BlueAppComponent = () => { const BlueAppComponent = () => {
useEffect(() => { useEffect(() => {

View file

@ -14,7 +14,6 @@
32F0A29A2311DBB20095C559 /* ComplicationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F0A2992311DBB20095C559 /* ComplicationController.swift */; }; 32F0A29A2311DBB20095C559 /* ComplicationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F0A2992311DBB20095C559 /* ComplicationController.swift */; };
6D2A6464258BA92D0092292B /* Stickers.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6D2A6463258BA92D0092292B /* Stickers.xcassets */; }; 6D2A6464258BA92D0092292B /* Stickers.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6D2A6463258BA92D0092292B /* Stickers.xcassets */; };
6D2A6468258BA92D0092292B /* Stickers.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6D2A6461258BA92C0092292B /* Stickers.appex */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 6D2A6468258BA92D0092292B /* Stickers.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6D2A6461258BA92C0092292B /* Stickers.appex */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
6D32C5C62596CE3A008C077C /* EventEmitter.m in Sources */ = {isa = PBXBuildFile; fileRef = 6D32C5C52596CE3A008C077C /* EventEmitter.m */; };
6D4AF15925D21172009DD853 /* MarketAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E6A254BAB1B007B5B82 /* MarketAPI.swift */; }; 6D4AF15925D21172009DD853 /* MarketAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E6A254BAB1B007B5B82 /* MarketAPI.swift */; };
6D4AF16D25D21192009DD853 /* Placeholders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4BFA254FBA0E00E9F9AA /* Placeholders.swift */; }; 6D4AF16D25D21192009DD853 /* Placeholders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4BFA254FBA0E00E9F9AA /* Placeholders.swift */; };
6D4AF17825D211A3009DD853 /* FiatUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D2AA8072568B8F40090B089 /* FiatUnit.swift */; }; 6D4AF17825D211A3009DD853 /* FiatUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D2AA8072568B8F40090B089 /* FiatUnit.swift */; };
@ -46,6 +45,8 @@
782F075B5DD048449E2DECE9 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = B9D9B3A7B2CB4255876B67AF /* libz.tbd */; }; 782F075B5DD048449E2DECE9 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = B9D9B3A7B2CB4255876B67AF /* libz.tbd */; };
849047CA2702A32A008EE567 /* Handoff.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849047C92702A32A008EE567 /* Handoff.swift */; }; 849047CA2702A32A008EE567 /* Handoff.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849047C92702A32A008EE567 /* Handoff.swift */; };
84E05A842721191B001A0D3A /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 84E05A832721191B001A0D3A /* Settings.bundle */; }; 84E05A842721191B001A0D3A /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 84E05A832721191B001A0D3A /* Settings.bundle */; };
B409AB042D71DFAA00BA06F8 /* MenuElementsEmitter.m in Sources */ = {isa = PBXBuildFile; fileRef = B409AB032D71DFAA00BA06F8 /* MenuElementsEmitter.m */; };
B409AB062D71E07500BA06F8 /* MenuElementsEmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B409AB052D71E07500BA06F8 /* MenuElementsEmitter.swift */; };
B40D4E34225841EC00428FCC /* Interface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B40D4E32225841EC00428FCC /* Interface.storyboard */; }; B40D4E34225841EC00428FCC /* Interface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B40D4E32225841EC00428FCC /* Interface.storyboard */; };
B40D4E36225841ED00428FCC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B40D4E35225841ED00428FCC /* Assets.xcassets */; }; B40D4E36225841ED00428FCC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B40D4E35225841ED00428FCC /* Assets.xcassets */; };
B40D4E3D225841ED00428FCC /* BlueWalletWatch Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = B40D4E3C225841ED00428FCC /* BlueWalletWatch Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; B40D4E3D225841ED00428FCC /* BlueWalletWatch Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = B40D4E3C225841ED00428FCC /* BlueWalletWatch Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
@ -113,12 +114,11 @@
B440340F2BCC40A400162242 /* fiatUnits.json in Resources */ = {isa = PBXBuildFile; fileRef = B440340E2BCC40A400162242 /* fiatUnits.json */; }; B440340F2BCC40A400162242 /* fiatUnits.json in Resources */ = {isa = PBXBuildFile; fileRef = B440340E2BCC40A400162242 /* fiatUnits.json */; };
B44034102BCC40A400162242 /* fiatUnits.json in Resources */ = {isa = PBXBuildFile; fileRef = B440340E2BCC40A400162242 /* fiatUnits.json */; }; B44034102BCC40A400162242 /* fiatUnits.json in Resources */ = {isa = PBXBuildFile; fileRef = B440340E2BCC40A400162242 /* fiatUnits.json */; };
B44034112BCC40A400162242 /* fiatUnits.json in Resources */ = {isa = PBXBuildFile; fileRef = B440340E2BCC40A400162242 /* fiatUnits.json */; }; B44034112BCC40A400162242 /* fiatUnits.json in Resources */ = {isa = PBXBuildFile; fileRef = B440340E2BCC40A400162242 /* fiatUnits.json */; };
B44305BC2D6A04B2004675CC /* CustomSegmentedControl.m in Sources */ = {isa = PBXBuildFile; fileRef = B44305BB2D6A04B2004675CC /* CustomSegmentedControl.m */; };
B450109C2C0FCD8A00619044 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B450109B2C0FCD8A00619044 /* Utilities.swift */; }; B450109C2C0FCD8A00619044 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B450109B2C0FCD8A00619044 /* Utilities.swift */; };
B450109D2C0FCD9F00619044 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B450109B2C0FCD8A00619044 /* Utilities.swift */; }; B450109D2C0FCD9F00619044 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B450109B2C0FCD8A00619044 /* Utilities.swift */; };
B450109F2C0FCDA500619044 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B450109B2C0FCD8A00619044 /* Utilities.swift */; }; B450109F2C0FCDA500619044 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B450109B2C0FCD8A00619044 /* Utilities.swift */; };
B45010A62C1507DE00619044 /* CustomSegmentedControlManager.m in Sources */ = {isa = PBXBuildFile; fileRef = B45010A52C1507DE00619044 /* CustomSegmentedControlManager.m */; };
B4549F362B82B10D002E3153 /* ci_post_clone.sh in Resources */ = {isa = PBXBuildFile; fileRef = B4549F352B82B10D002E3153 /* ci_post_clone.sh */; }; B4549F362B82B10D002E3153 /* ci_post_clone.sh in Resources */ = {isa = PBXBuildFile; fileRef = B4549F352B82B10D002E3153 /* ci_post_clone.sh */; };
B45942C42CDECF2400B3DC2E /* MenuElementsEmitter.m in Sources */ = {isa = PBXBuildFile; fileRef = B4C075292CDDB3C500322A84 /* MenuElementsEmitter.m */; };
B461B852299599F800E431AA /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = B461B851299599F800E431AA /* AppDelegate.mm */; }; B461B852299599F800E431AA /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = B461B851299599F800E431AA /* AppDelegate.mm */; };
B4742E972CCDBE8300380EEE /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = B4742E962CCDBE8300380EEE /* Localizable.xcstrings */; }; B4742E972CCDBE8300380EEE /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = B4742E962CCDBE8300380EEE /* Localizable.xcstrings */; };
B4742E982CCDBE8300380EEE /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = B4742E962CCDBE8300380EEE /* Localizable.xcstrings */; }; B4742E982CCDBE8300380EEE /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = B4742E962CCDBE8300380EEE /* Localizable.xcstrings */; };
@ -156,6 +156,8 @@
B4AB225E2B02AD12001F4328 /* XMLParserDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4AB225C2B02AD12001F4328 /* XMLParserDelegate.swift */; }; B4AB225E2B02AD12001F4328 /* XMLParserDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4AB225C2B02AD12001F4328 /* XMLParserDelegate.swift */; };
B4B1A4622BFA73110072E3BB /* WidgetHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4B1A4612BFA73110072E3BB /* WidgetHelper.swift */; }; B4B1A4622BFA73110072E3BB /* WidgetHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4B1A4612BFA73110072E3BB /* WidgetHelper.swift */; };
B4B1A4642BFA73110072E3BB /* WidgetHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4B1A4612BFA73110072E3BB /* WidgetHelper.swift */; }; B4B1A4642BFA73110072E3BB /* WidgetHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4B1A4612BFA73110072E3BB /* WidgetHelper.swift */; };
B4B3EC222D69FF6C00327F3D /* CustomSegmentedControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4B3EC202D69FF6C00327F3D /* CustomSegmentedControl.swift */; };
B4B3EC252D69FF8700327F3D /* EventEmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4B3EC232D69FF8700327F3D /* EventEmitter.swift */; };
B4D0B2622C1DEA11006B6B1B /* ReceivePageInterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4D0B2612C1DEA11006B6B1B /* ReceivePageInterfaceController.swift */; }; B4D0B2622C1DEA11006B6B1B /* ReceivePageInterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4D0B2612C1DEA11006B6B1B /* ReceivePageInterfaceController.swift */; };
B4D0B2642C1DEA99006B6B1B /* ReceiveType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4D0B2632C1DEA99006B6B1B /* ReceiveType.swift */; }; B4D0B2642C1DEA99006B6B1B /* ReceiveType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4D0B2632C1DEA99006B6B1B /* ReceiveType.swift */; };
B4D0B2662C1DEB7F006B6B1B /* ReceiveInterfaceMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4D0B2652C1DEB7F006B6B1B /* ReceiveInterfaceMode.swift */; }; B4D0B2662C1DEB7F006B6B1B /* ReceiveInterfaceMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4D0B2652C1DEB7F006B6B1B /* ReceiveInterfaceMode.swift */; };
@ -294,8 +296,6 @@
6D2A6463258BA92D0092292B /* Stickers.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Stickers.xcassets; sourceTree = "<group>"; }; 6D2A6463258BA92D0092292B /* Stickers.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Stickers.xcassets; sourceTree = "<group>"; };
6D2A6465258BA92D0092292B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 6D2A6465258BA92D0092292B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
6D2AA8072568B8F40090B089 /* FiatUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiatUnit.swift; sourceTree = "<group>"; }; 6D2AA8072568B8F40090B089 /* FiatUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiatUnit.swift; sourceTree = "<group>"; };
6D32C5C42596CE2F008C077C /* EventEmitter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EventEmitter.h; sourceTree = "<group>"; };
6D32C5C52596CE3A008C077C /* EventEmitter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EventEmitter.m; sourceTree = "<group>"; };
6D333B3A252FE1A3004D72DF /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; 6D333B3A252FE1A3004D72DF /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
6D333B3C252FE1A3004D72DF /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; 6D333B3C252FE1A3004D72DF /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
6D4AF18225D215D0009DD853 /* BlueWalletWatch-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BlueWalletWatch-Bridging-Header.h"; sourceTree = "<group>"; }; 6D4AF18225D215D0009DD853 /* BlueWalletWatch-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BlueWalletWatch-Bridging-Header.h"; sourceTree = "<group>"; };
@ -336,6 +336,8 @@
9F1F51A83D044F3BB26A35FC /* libRNSVG-tvOS.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = "libRNSVG-tvOS.a"; sourceTree = "<group>"; }; 9F1F51A83D044F3BB26A35FC /* libRNSVG-tvOS.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = "libRNSVG-tvOS.a"; sourceTree = "<group>"; };
A7C4B1FDAD264618BAF8C335 /* libRNCWebView.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNCWebView.a; sourceTree = "<group>"; }; A7C4B1FDAD264618BAF8C335 /* libRNCWebView.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNCWebView.a; sourceTree = "<group>"; };
AB2325650CE04F018697ACFE /* libRNReactNativeHapticFeedback.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNReactNativeHapticFeedback.a; sourceTree = "<group>"; }; AB2325650CE04F018697ACFE /* libRNReactNativeHapticFeedback.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNReactNativeHapticFeedback.a; sourceTree = "<group>"; };
B409AB032D71DFAA00BA06F8 /* MenuElementsEmitter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = MenuElementsEmitter.m; path = MenuElementsEmitter/MenuElementsEmitter.m; sourceTree = SOURCE_ROOT; };
B409AB052D71E07500BA06F8 /* MenuElementsEmitter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MenuElementsEmitter.swift; path = MenuElementsEmitter/MenuElementsEmitter.swift; sourceTree = SOURCE_ROOT; };
B40D4E30225841EC00428FCC /* BlueWalletWatch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BlueWalletWatch.app; sourceTree = BUILT_PRODUCTS_DIR; }; B40D4E30225841EC00428FCC /* BlueWalletWatch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BlueWalletWatch.app; sourceTree = BUILT_PRODUCTS_DIR; };
B40D4E33225841EC00428FCC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Interface.storyboard; sourceTree = "<group>"; }; B40D4E33225841EC00428FCC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Interface.storyboard; sourceTree = "<group>"; };
B40D4E35225841ED00428FCC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; B40D4E35225841ED00428FCC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@ -372,9 +374,8 @@
B44033F82BCC379200162242 /* WidgetDataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetDataStore.swift; sourceTree = "<group>"; }; B44033F82BCC379200162242 /* WidgetDataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetDataStore.swift; sourceTree = "<group>"; };
B44033FF2BCC37F800162242 /* Bundle+decode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+decode.swift"; sourceTree = "<group>"; }; B44033FF2BCC37F800162242 /* Bundle+decode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+decode.swift"; sourceTree = "<group>"; };
B440340E2BCC40A400162242 /* fiatUnits.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = fiatUnits.json; path = ../../../models/fiatUnits.json; sourceTree = "<group>"; }; B440340E2BCC40A400162242 /* fiatUnits.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = fiatUnits.json; path = ../../../models/fiatUnits.json; sourceTree = "<group>"; };
B44305BB2D6A04B2004675CC /* CustomSegmentedControl.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CustomSegmentedControl.m; sourceTree = "<group>"; };
B450109B2C0FCD8A00619044 /* Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = "<group>"; }; B450109B2C0FCD8A00619044 /* Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = "<group>"; };
B45010A52C1507DE00619044 /* CustomSegmentedControlManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CustomSegmentedControlManager.m; sourceTree = "<group>"; };
B45010A92C15080500619044 /* CustomSegmentedControlManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CustomSegmentedControlManager.h; sourceTree = "<group>"; };
B4549F352B82B10D002E3153 /* ci_post_clone.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = ci_post_clone.sh; sourceTree = "<group>"; }; B4549F352B82B10D002E3153 /* ci_post_clone.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = ci_post_clone.sh; sourceTree = "<group>"; };
B461B850299599F800E431AA /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = BlueWallet/AppDelegate.h; sourceTree = "<group>"; }; B461B850299599F800E431AA /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = BlueWallet/AppDelegate.h; sourceTree = "<group>"; };
B461B851299599F800E431AA /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = BlueWallet/AppDelegate.mm; sourceTree = "<group>"; }; B461B851299599F800E431AA /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = BlueWallet/AppDelegate.mm; sourceTree = "<group>"; };
@ -396,8 +397,8 @@
B4AB225C2B02AD12001F4328 /* XMLParserDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XMLParserDelegate.swift; sourceTree = "<group>"; }; B4AB225C2B02AD12001F4328 /* XMLParserDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XMLParserDelegate.swift; sourceTree = "<group>"; };
B4B1A4612BFA73110072E3BB /* WidgetHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetHelper.swift; sourceTree = "<group>"; }; B4B1A4612BFA73110072E3BB /* WidgetHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetHelper.swift; sourceTree = "<group>"; };
B4B31A352C77BBA000663334 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Interface.strings; sourceTree = "<group>"; }; B4B31A352C77BBA000663334 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Interface.strings; sourceTree = "<group>"; };
B4C075282CDDB3BE00322A84 /* MenuElementsEmitter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MenuElementsEmitter.h; sourceTree = "<group>"; }; B4B3EC202D69FF6C00327F3D /* CustomSegmentedControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomSegmentedControl.swift; sourceTree = "<group>"; };
B4C075292CDDB3C500322A84 /* MenuElementsEmitter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MenuElementsEmitter.m; sourceTree = "<group>"; }; B4B3EC232D69FF8700327F3D /* EventEmitter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventEmitter.swift; sourceTree = "<group>"; };
B4D0B2612C1DEA11006B6B1B /* ReceivePageInterfaceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReceivePageInterfaceController.swift; sourceTree = "<group>"; }; B4D0B2612C1DEA11006B6B1B /* ReceivePageInterfaceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReceivePageInterfaceController.swift; sourceTree = "<group>"; };
B4D0B2632C1DEA99006B6B1B /* ReceiveType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReceiveType.swift; sourceTree = "<group>"; }; B4D0B2632C1DEA99006B6B1B /* ReceiveType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReceiveType.swift; sourceTree = "<group>"; };
B4D0B2652C1DEB7F006B6B1B /* ReceiveInterfaceMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReceiveInterfaceMode.swift; sourceTree = "<group>"; }; B4D0B2652C1DEB7F006B6B1B /* ReceiveInterfaceMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReceiveInterfaceMode.swift; sourceTree = "<group>"; };
@ -489,7 +490,6 @@
13B07FAE1A68108700A75B9A /* BlueWallet */ = { 13B07FAE1A68108700A75B9A /* BlueWallet */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
B4C0752B2CDDB3CC00322A84 /* MenuElementsEmitter */,
B461B850299599F800E431AA /* AppDelegate.h */, B461B850299599F800E431AA /* AppDelegate.h */,
B461B851299599F800E431AA /* AppDelegate.mm */, B461B851299599F800E431AA /* AppDelegate.mm */,
32C7944323B8879D00BE2AFA /* BlueWalletRelease.entitlements */, 32C7944323B8879D00BE2AFA /* BlueWalletRelease.entitlements */,
@ -501,8 +501,6 @@
32B5A3292334450100F8D608 /* Bridge.swift */, 32B5A3292334450100F8D608 /* Bridge.swift */,
32B5A3282334450100F8D608 /* BlueWallet-Bridging-Header.h */, 32B5A3282334450100F8D608 /* BlueWallet-Bridging-Header.h */,
6DF25A9E249DB97E001D06F5 /* LaunchScreen.storyboard */, 6DF25A9E249DB97E001D06F5 /* LaunchScreen.storyboard */,
6D32C5C42596CE2F008C077C /* EventEmitter.h */,
6D32C5C52596CE3A008C077C /* EventEmitter.m */,
84E05A832721191B001A0D3A /* Settings.bundle */, 84E05A832721191B001A0D3A /* Settings.bundle */,
B4742E962CCDBE8300380EEE /* Localizable.xcstrings */, B4742E962CCDBE8300380EEE /* Localizable.xcstrings */,
); );
@ -677,6 +675,15 @@
name = Products; name = Products;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
B409AB072D71E07C00BA06F8 /* MenuElementsEmitter */ = {
isa = PBXGroup;
children = (
B409AB052D71E07500BA06F8 /* MenuElementsEmitter.swift */,
B409AB032D71DFAA00BA06F8 /* MenuElementsEmitter.m */,
);
path = MenuElementsEmitter;
sourceTree = "<group>";
};
B40D4E31225841EC00428FCC /* BlueWalletWatch */ = { B40D4E31225841EC00428FCC /* BlueWalletWatch */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -786,6 +793,15 @@
path = Shared; path = Shared;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
B44305BD2D6A04B9004675CC /* SegmentedControl */ = {
isa = PBXGroup;
children = (
B44305BB2D6A04B2004675CC /* CustomSegmentedControl.m */,
B4B3EC202D69FF6C00327F3D /* CustomSegmentedControl.swift */,
);
path = SegmentedControl;
sourceTree = "<group>";
};
B450109A2C0FCD7E00619044 /* Utilities */ = { B450109A2C0FCD7E00619044 /* Utilities */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -798,21 +814,14 @@
B45010A12C1504E900619044 /* Components */ = { B45010A12C1504E900619044 /* Components */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
B409AB072D71E07C00BA06F8 /* MenuElementsEmitter */,
B44305BD2D6A04B9004675CC /* SegmentedControl */,
B4B3EC232D69FF8700327F3D /* EventEmitter.swift */,
B4B1A4612BFA73110072E3BB /* WidgetHelper.swift */, B4B1A4612BFA73110072E3BB /* WidgetHelper.swift */,
B45010A82C1507F000619044 /* SegmentedControl */,
); );
path = Components; path = Components;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
B45010A82C1507F000619044 /* SegmentedControl */ = {
isa = PBXGroup;
children = (
B45010A52C1507DE00619044 /* CustomSegmentedControlManager.m */,
B45010A92C15080500619044 /* CustomSegmentedControlManager.h */,
);
path = SegmentedControl;
sourceTree = "<group>";
};
B4549F2E2B80FEA1002E3153 /* ci_scripts */ = { B4549F2E2B80FEA1002E3153 /* ci_scripts */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -829,15 +838,6 @@
path = BlueWalletUITests; path = BlueWalletUITests;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
B4C0752B2CDDB3CC00322A84 /* MenuElementsEmitter */ = {
isa = PBXGroup;
children = (
B4C075292CDDB3C500322A84 /* MenuElementsEmitter.m */,
B4C075282CDDB3BE00322A84 /* MenuElementsEmitter.h */,
);
path = MenuElementsEmitter;
sourceTree = "<group>";
};
FAA856B639C61E61D2CF90A8 /* Pods */ = { FAA856B639C61E61D2CF90A8 /* Pods */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -1239,28 +1239,30 @@
B44033EE2BCC374500162242 /* Numeric+abbreviated.swift in Sources */, B44033EE2BCC374500162242 /* Numeric+abbreviated.swift in Sources */,
B48630E82CCEE92400A8425C /* PriceWidget.swift in Sources */, B48630E82CCEE92400A8425C /* PriceWidget.swift in Sources */,
B44033DD2BCC36C300162242 /* LatestTransaction.swift in Sources */, B44033DD2BCC36C300162242 /* LatestTransaction.swift in Sources */,
6D32C5C62596CE3A008C077C /* EventEmitter.m in Sources */,
B49A28C12CD199FC006B08E4 /* SwiftTCPClient.swift in Sources */, B49A28C12CD199FC006B08E4 /* SwiftTCPClient.swift in Sources */,
B44033FE2BCC37D700162242 /* MarketAPI.swift in Sources */, B44033FE2BCC37D700162242 /* MarketAPI.swift in Sources */,
B450109C2C0FCD8A00619044 /* Utilities.swift in Sources */, B450109C2C0FCD8A00619044 /* Utilities.swift in Sources */,
B409AB042D71DFAA00BA06F8 /* MenuElementsEmitter.m in Sources */,
B48630E52CCEE8B800A8425C /* PriceView.swift in Sources */, B48630E52CCEE8B800A8425C /* PriceView.swift in Sources */,
B48630E72CCEE91900A8425C /* PriceWidgetProvider.swift in Sources */, B48630E72CCEE91900A8425C /* PriceWidgetProvider.swift in Sources */,
B4B3EC252D69FF8700327F3D /* EventEmitter.swift in Sources */,
B49A28C02CD199C7006B08E4 /* MarketAPI+Electrum.swift in Sources */, B49A28C02CD199C7006B08E4 /* MarketAPI+Electrum.swift in Sources */,
B48630ED2CCEEEB000A8425C /* WalletAppShortcuts.swift in Sources */, B48630ED2CCEEEB000A8425C /* WalletAppShortcuts.swift in Sources */,
B45010A62C1507DE00619044 /* CustomSegmentedControlManager.m in Sources */, B409AB062D71E07500BA06F8 /* MenuElementsEmitter.swift in Sources */,
B44033CE2BCC352900162242 /* UserDefaultsGroup.swift in Sources */, B44033CE2BCC352900162242 /* UserDefaultsGroup.swift in Sources */,
13B07FC11A68108700A75B9A /* main.m in Sources */, 13B07FC11A68108700A75B9A /* main.m in Sources */,
B45942C42CDECF2400B3DC2E /* MenuElementsEmitter.m in Sources */,
B461B852299599F800E431AA /* AppDelegate.mm in Sources */, B461B852299599F800E431AA /* AppDelegate.mm in Sources */,
B44033F42BCC377F00162242 /* WidgetData.swift in Sources */, B44033F42BCC377F00162242 /* WidgetData.swift in Sources */,
B49A28C52CD1A894006B08E4 /* MarketData.swift in Sources */, B49A28C52CD1A894006B08E4 /* MarketData.swift in Sources */,
B49A28BF2CD18A9A006B08E4 /* FiatUnitEnum.swift in Sources */, B49A28BF2CD18A9A006B08E4 /* FiatUnitEnum.swift in Sources */,
B44305BC2D6A04B2004675CC /* CustomSegmentedControl.m in Sources */,
B44033C42BCC332400162242 /* Balance.swift in Sources */, B44033C42BCC332400162242 /* Balance.swift in Sources */,
B48630EE2CCEEEE900A8425C /* PriceIntent.swift in Sources */, B48630EE2CCEEEE900A8425C /* PriceIntent.swift in Sources */,
B44034072BCC38A000162242 /* FiatUnit.swift in Sources */, B44034072BCC38A000162242 /* FiatUnit.swift in Sources */,
B44034002BCC37F800162242 /* Bundle+decode.swift in Sources */, B44034002BCC37F800162242 /* Bundle+decode.swift in Sources */,
B44033E22BCC36CB00162242 /* Placeholders.swift in Sources */, B44033E22BCC36CB00162242 /* Placeholders.swift in Sources */,
B4793DBB2CEDACBD00C92C2E /* Chain.swift in Sources */, B4793DBB2CEDACBD00C92C2E /* Chain.swift in Sources */,
B4B3EC222D69FF6C00327F3D /* CustomSegmentedControl.swift in Sources */,
B4B1A4622BFA73110072E3BB /* WidgetHelper.swift in Sources */, B4B1A4622BFA73110072E3BB /* WidgetHelper.swift in Sources */,
B48630E12CCEE7C800A8425C /* PriceWidgetEntryView.swift in Sources */, B48630E12CCEE7C800A8425C /* PriceWidgetEntryView.swift in Sources */,
B44033DA2BCC369A00162242 /* Colors.swift in Sources */, B44033DA2BCC369A00162242 /* Colors.swift in Sources */,
@ -1453,7 +1455,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1703137999; CURRENT_PROJECT_VERSION = 1703169999;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = A7W54YZ4WU; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = A7W54YZ4WU;
@ -1471,7 +1473,7 @@
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.finance"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.finance";
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = io.bluewallet.bluewallet; INFOPLIST_KEY_WKCompanionAppBundleIdentifier = io.bluewallet.bluewallet;
INFOPLIST_KEY_WKExtensionDelegateClassName = "$(PRODUCT_BUNDLE_IDENTIFIER).ExtensionDelegate"; INFOPLIST_KEY_WKExtensionDelegateClassName = "$(PRODUCT_BUNDLE_IDENTIFIER).ExtensionDelegate";
IPHONEOS_DEPLOYMENT_TARGET = 14.0; IPHONEOS_DEPLOYMENT_TARGET = 15.6;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
@ -1481,7 +1483,7 @@
"$(SDKROOT)/System/iOSSupport/usr/lib/swift", "$(SDKROOT)/System/iOSSupport/usr/lib/swift",
"$(inherited)", "$(inherited)",
); );
MARKETING_VERSION = 7.0.9; MARKETING_VERSION = 7.1.5;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"$(inherited)", "$(inherited)",
"-ObjC", "-ObjC",
@ -1516,7 +1518,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Distribution"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Distribution";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1703137999; CURRENT_PROJECT_VERSION = 1703169999;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = A7W54YZ4WU; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = A7W54YZ4WU;
@ -1529,7 +1531,7 @@
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.finance"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.finance";
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = io.bluewallet.bluewallet; INFOPLIST_KEY_WKCompanionAppBundleIdentifier = io.bluewallet.bluewallet;
INFOPLIST_KEY_WKExtensionDelegateClassName = "$(PRODUCT_BUNDLE_IDENTIFIER).ExtensionDelegate"; INFOPLIST_KEY_WKExtensionDelegateClassName = "$(PRODUCT_BUNDLE_IDENTIFIER).ExtensionDelegate";
IPHONEOS_DEPLOYMENT_TARGET = 14.0; IPHONEOS_DEPLOYMENT_TARGET = 15.6;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
@ -1539,7 +1541,7 @@
"$(SDKROOT)/System/iOSSupport/usr/lib/swift", "$(SDKROOT)/System/iOSSupport/usr/lib/swift",
"$(inherited)", "$(inherited)",
); );
MARKETING_VERSION = 7.0.9; MARKETING_VERSION = 7.1.5;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"$(inherited)", "$(inherited)",
"-ObjC", "-ObjC",
@ -1575,20 +1577,20 @@
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1703137999; CURRENT_PROJECT_VERSION = 1703169999;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf; DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = A7W54YZ4WU; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = A7W54YZ4WU;
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = Stickers/Info.plist; INFOPLIST_FILE = Stickers/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.1; IPHONEOS_DEPLOYMENT_TARGET = 15.6;
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"$(SDKROOT)/usr/lib/swift", "$(SDKROOT)/usr/lib/swift",
"$(SDKROOT)/System/iOSSupport/usr/lib/swift", "$(SDKROOT)/System/iOSSupport/usr/lib/swift",
"$(inherited)", "$(inherited)",
); );
MARKETING_VERSION = 7.0.9; MARKETING_VERSION = 7.1.5;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES; PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES;
@ -1618,20 +1620,20 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1703137999; CURRENT_PROJECT_VERSION = 1703169999;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = A7W54YZ4WU; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = A7W54YZ4WU;
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = Stickers/Info.plist; INFOPLIST_FILE = Stickers/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.1; IPHONEOS_DEPLOYMENT_TARGET = 15.6;
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"$(SDKROOT)/usr/lib/swift", "$(SDKROOT)/usr/lib/swift",
"$(SDKROOT)/System/iOSSupport/usr/lib/swift", "$(SDKROOT)/System/iOSSupport/usr/lib/swift",
"$(inherited)", "$(inherited)",
); );
MARKETING_VERSION = 7.0.9; MARKETING_VERSION = 7.1.5;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES; PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.Stickers; PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.Stickers;
@ -1662,7 +1664,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1703137999; CURRENT_PROJECT_VERSION = 1703169999;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf; DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
@ -1681,7 +1683,7 @@
"$(SDKROOT)/System/iOSSupport/usr/lib/swift", "$(SDKROOT)/System/iOSSupport/usr/lib/swift",
"$(inherited)", "$(inherited)",
); );
MARKETING_VERSION = 7.0.9; MARKETING_VERSION = 7.1.5;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES; PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES;
@ -1718,7 +1720,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Distribution"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Distribution";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1703137999; CURRENT_PROJECT_VERSION = 1703169999;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
@ -1737,7 +1739,7 @@
"$(SDKROOT)/System/iOSSupport/usr/lib/swift", "$(SDKROOT)/System/iOSSupport/usr/lib/swift",
"$(inherited)", "$(inherited)",
); );
MARKETING_VERSION = 7.0.9; MARKETING_VERSION = 7.1.5;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES; PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.MarketWidget; PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.MarketWidget;
@ -1905,7 +1907,7 @@
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1703137999; CURRENT_PROJECT_VERSION = 1703169999;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf; DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
@ -1925,7 +1927,7 @@
"$(SDKROOT)/System/iOSSupport/usr/lib/swift", "$(SDKROOT)/System/iOSSupport/usr/lib/swift",
"$(inherited)", "$(inherited)",
); );
MARKETING_VERSION = 7.0.9; MARKETING_VERSION = 7.1.5;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES; PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES;
@ -1958,7 +1960,7 @@
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1703137999; CURRENT_PROJECT_VERSION = 1703169999;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
@ -1978,7 +1980,7 @@
"$(SDKROOT)/System/iOSSupport/usr/lib/swift", "$(SDKROOT)/System/iOSSupport/usr/lib/swift",
"$(inherited)", "$(inherited)",
); );
MARKETING_VERSION = 7.0.9; MARKETING_VERSION = 7.1.5;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES; PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch.extension; PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch.extension;
@ -2010,7 +2012,7 @@
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1703137999; CURRENT_PROJECT_VERSION = 1703169999;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf; DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
@ -2024,7 +2026,7 @@
"$(SDKROOT)/System/iOSSupport/usr/lib/swift", "$(SDKROOT)/System/iOSSupport/usr/lib/swift",
"$(inherited)", "$(inherited)",
); );
MARKETING_VERSION = 7.0.9; MARKETING_VERSION = 7.1.5;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES; PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES;
@ -2059,7 +2061,7 @@
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1703137999; CURRENT_PROJECT_VERSION = 1703169999;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
@ -2073,7 +2075,7 @@
"$(SDKROOT)/System/iOSSupport/usr/lib/swift", "$(SDKROOT)/System/iOSSupport/usr/lib/swift",
"$(inherited)", "$(inherited)",
); );
MARKETING_VERSION = 7.0.9; MARKETING_VERSION = 7.1.5;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES; PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch; PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch;

View file

@ -6,12 +6,9 @@
#import "RNQuickActionManager.h" #import "RNQuickActionManager.h"
#import <UserNotifications/UserNotifications.h> #import <UserNotifications/UserNotifications.h>
#import <RNCPushNotificationIOS.h> #import <RNCPushNotificationIOS.h>
#import "EventEmitter.h"
#import "MenuElementsEmitter.h"
#import <React/RCTRootView.h> #import <React/RCTRootView.h>
#import <Bugsnag/Bugsnag.h> #import <Bugsnag/Bugsnag.h>
#import "BlueWallet-Swift.h" #import "BlueWallet-Swift.h"
#import "CustomSegmentedControlManager.h"
@interface AppDelegate() <UNUserNotificationCenterDelegate> @interface AppDelegate() <UNUserNotificationCenterDelegate>
@ -23,8 +20,6 @@
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{ {
[MenuElementsEmitter sharedInstance];
[CustomSegmentedControlManager registerIfNecessary];
[self clearFilesIfNeeded]; [self clearFilesIfNeeded];
self.userDefaultsGroup = [[NSUserDefaults alloc] initWithSuiteName:@"group.io.bluewallet.bluewallet"]; self.userDefaultsGroup = [[NSUserDefaults alloc] initWithSuiteName:@"group.io.bluewallet.bluewallet"];
@ -154,27 +149,42 @@
- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity - (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity
restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler
{ {
NSDictionary *userActivityData = @{@"activityType": userActivity.activityType, @"userInfo": userActivity.userInfo}; // Validate userActivity and its type
if (!userActivity || !userActivity.activityType) {
NSLog(@"[Handoff] Invalid or missing userActivity");
return NO;
}
NSDictionary *userActivityData = @{@"activityType": userActivity.activityType ?: @"",
@"userInfo": userActivity.userInfo ?: @{}};
// Save activity data to userDefaults for potential later use
[self.userDefaultsGroup setValue:userActivityData forKey:@"onUserActivityOpen"]; [self.userDefaultsGroup setValue:userActivityData forKey:@"onUserActivityOpen"];
// Check if the activity type matches the allowed types // Check if the activity type matches one of the allowed types
if ([userActivity.activityType isEqualToString:@"io.bluewallet.bluewallet.receiveonchain"] || if ([userActivity.activityType isEqualToString:@"io.bluewallet.bluewallet.receiveonchain"] ||
[userActivity.activityType isEqualToString:@"io.bluewallet.bluewallet.xpub"] || [userActivity.activityType isEqualToString:@"io.bluewallet.bluewallet.xpub"] ||
[userActivity.activityType isEqualToString:@"io.bluewallet.bluewallet.blockexplorer"]) { [userActivity.activityType isEqualToString:@"io.bluewallet.bluewallet.blockexplorer"]) {
[EventEmitter.sharedInstance sendUserActivity:userActivityData]; if ([EventEmitter.shared respondsToSelector:@selector(sendUserActivity:)]) {
[EventEmitter.shared sendUserActivity:userActivityData];
} else {
NSLog(@"[Handoff] EventEmitter does not implement sendUserActivity:");
}
return YES; return YES;
} }
if (userActivity.activityType == NSUserActivityTypeBrowsingWeb) { // Forward web browsing activities to LinkingManager
if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
return [RCTLinkingManager application:application return [RCTLinkingManager application:application
continueUserActivity:userActivity continueUserActivity:userActivity
restorationHandler:restorationHandler]; restorationHandler:restorationHandler];
} }
// If activity type does not match any of the specified types, do nothing NSLog(@"[Handoff] Unhandled user activity type: %@", userActivity.activityType);
return NO; return NO;
} }
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options { - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
return [RCTLinkingManager application:app openURL:url options:options]; return [RCTLinkingManager application:app openURL:url options:options];
} }
@ -195,7 +205,7 @@
-(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler -(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler
{ {
NSDictionary *userInfo = notification.request.content.userInfo; NSDictionary *userInfo = notification.request.content.userInfo;
completionHandler(UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge); completionHandler(UNNotificationPresentationOptionSound | UNNotificationPresentationOptionList | UNNotificationPresentationOptionBanner | UNNotificationPresentationOptionBadge);
} }
- (void)buildMenuWithBuilder:(id<UIMenuBuilder>)builder { - (void)buildMenuWithBuilder:(id<UIMenuBuilder>)builder {
@ -244,25 +254,59 @@
} }
- (void)openSettings:(UIKeyCommand *)keyCommand { - (void)openSettings:(UIKeyCommand *)keyCommand {
[MenuElementsEmitter.sharedInstance openSettings]; // Safely access the MenuElementsEmitter
MenuElementsEmitter *emitter = [MenuElementsEmitter shared];
if (emitter) {
NSLog(@"[MenuElements] AppDelegate: openSettings called, calling emitter");
// Force on main thread for consistency
dispatch_async(dispatch_get_main_queue(), ^{
[emitter openSettings];
});
} else {
NSLog(@"[MenuElements] AppDelegate: MenuElementsEmitter not available for openSettings");
}
} }
- (void)addWalletAction:(UIKeyCommand *)keyCommand { - (void)addWalletAction:(UIKeyCommand *)keyCommand {
// Implement the functionality for adding a wallet // Safely access the MenuElementsEmitter
[MenuElementsEmitter.sharedInstance addWalletMenuAction]; MenuElementsEmitter *emitter = [MenuElementsEmitter shared];
NSLog(@"Add Wallet action performed"); if (emitter) {
NSLog(@"[MenuElements] AppDelegate: addWalletAction called, calling emitter");
// Force on main thread for consistency
dispatch_async(dispatch_get_main_queue(), ^{
[emitter addWalletMenuAction];
});
} else {
NSLog(@"[MenuElements] AppDelegate: MenuElementsEmitter not available for addWalletAction");
}
} }
- (void)importWalletAction:(UIKeyCommand *)keyCommand { - (void)importWalletAction:(UIKeyCommand *)keyCommand {
// Implement the functionality for adding a wallet // Safely access the MenuElementsEmitter
[MenuElementsEmitter.sharedInstance importWalletMenuAction]; MenuElementsEmitter *emitter = [MenuElementsEmitter shared];
NSLog(@"Import Wallet action performed"); if (emitter) {
NSLog(@"[MenuElements] AppDelegate: importWalletAction called, calling emitter");
// Force on main thread for consistency
dispatch_async(dispatch_get_main_queue(), ^{
[emitter importWalletMenuAction];
});
} else {
NSLog(@"[MenuElements] AppDelegate: MenuElementsEmitter not available for importWalletAction");
}
} }
- (void)reloadTransactionsAction:(UIKeyCommand *)keyCommand { - (void)reloadTransactionsAction:(UIKeyCommand *)keyCommand {
// Implement the functionality for adding a wallet // Safely access the MenuElementsEmitter
[MenuElementsEmitter.sharedInstance reloadTransactionsMenuAction]; MenuElementsEmitter *emitter = [MenuElementsEmitter shared];
NSLog(@"Reload Transactions action performed"); if (emitter) {
NSLog(@"[MenuElements] AppDelegate: reloadTransactionsAction called, calling emitter");
// Force on main thread for consistency
dispatch_async(dispatch_get_main_queue(), ^{
[emitter reloadTransactionsMenuAction];
});
} else {
NSLog(@"[MenuElements] AppDelegate: MenuElementsEmitter not available for reloadTransactionsAction");
}
} }
- (void)showHelp:(id)sender { - (void)showHelp:(id)sender {

View file

@ -0,0 +1,32 @@
import Foundation
import React
@objc(EventEmitter)
class EventEmitter: RCTEventEmitter {
static let sharedInstance = EventEmitter()
override class func requiresMainQueueSetup() -> Bool {
return true
}
@objc static func shared() -> EventEmitter {
return sharedInstance
}
override func supportedEvents() -> [String]! {
return ["onUserActivityOpen"]
}
@objc func sendUserActivity(_ userInfo: [String: Any]) {
sendEvent(withName: "onUserActivityOpen", body: userInfo)
}
@objc func getMostRecentUserActivity(_ resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: RCTPromiseRejectBlock) {
if let defaults = UserDefaults(suiteName: "group.io.bluewallet.bluewallet") {
resolve(defaults.value(forKey: "onUserActivityOpen"))
} else {
resolve(nil)
}
}
}

View file

@ -0,0 +1,9 @@
#import <React/RCTViewManager.h>
@interface RCT_EXTERN_MODULE(CustomSegmentedControlManager, RCTViewManager)
RCT_EXPORT_VIEW_PROPERTY(values, NSArray)
RCT_EXPORT_VIEW_PROPERTY(selectedIndex, NSNumber)
RCT_EXPORT_VIEW_PROPERTY(onChangeEvent, RCTDirectEventBlock)
@end

View file

@ -0,0 +1,70 @@
import UIKit
import React
class CustomSegmentedControl: UISegmentedControl {
@objc var onChangeEvent: RCTDirectEventBlock?
override init(frame: CGRect) {
super.init(frame: frame)
addTarget(self, action: #selector(onChange(_:)), for: .valueChanged)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
addTarget(self, action: #selector(onChange(_:)), for: .valueChanged)
}
@objc func setValues(_ values: [String]) {
removeAllSegments()
for (index, title) in values.enumerated() {
insertSegment(withTitle: title, at: index, animated: false)
}
}
@objc func setSelectedIndex(_ selectedIndex: NSNumber) {
self.selectedSegmentIndex = selectedIndex.intValue
}
@objc func onChange(_ sender: UISegmentedControl) {
onChangeEvent?(["selectedIndex": sender.selectedSegmentIndex])
}
}
@objc(CustomSegmentedControlManager)
class CustomSegmentedControlManager: RCTViewManager {
static var isRegistered = false
override func view() -> UIView! {
// Ensure native module is registered before returning the view.
CustomSegmentedControlManager.registerIfNecessary()
return CustomSegmentedControl(frame: .zero)
}
@objc static func registerIfNecessary() {
if !isRegistered {
isRegistered = true
}
}
// Changed from static to instance method.
override func constantsToExport() -> [AnyHashable: Any]! {
return [
"bubblingEventTypes": [
"onChangeEvent": [
"phasedRegistrationNames": [
"bubbled": "onChangeEvent",
"captured": "onChangeEventCapture"
]
]
]
]
}
override class func moduleName() -> String! {
return "CustomSegmentedControl"
}
override class func requiresMainQueueSetup() -> Bool {
return true
}
}

View file

@ -1,15 +0,0 @@
//
// SegmentedControlManager.h
// BlueWallet
//
// Created by Marcos Rodriguez on 6/8/24.
// Copyright © 2024 BlueWallet. All rights reserved.
//
#import <React/RCTViewManager.h>
@interface CustomSegmentedControlManager : RCTViewManager
+ (void)registerIfNecessary;
@end

View file

@ -1,70 +0,0 @@
#import "CustomSegmentedControlManager.h"
#import <React/RCTBridge.h>
#import <React/RCTEventDispatcher.h>
#import <React/UIView+React.h>
@interface CustomSegmentedControl : UISegmentedControl
@property (nonatomic, copy) RCTDirectEventBlock onChangeEvent;
- (void)setValues:(NSArray<NSString *> *)values;
- (void)setSelectedIndex:(NSNumber *)selectedIndex;
@end
@implementation CustomSegmentedControl
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self addTarget:self action:@selector(onChange:) forControlEvents:UIControlEventValueChanged];
}
return self;
}
- (void)setValues:(NSArray<NSString *> *)values {
@try {
[self removeAllSegments];
for (NSUInteger i = 0; i < values.count; i++) {
[self insertSegmentWithTitle:values[i] atIndex:i animated:NO];
}
} @catch (NSException *exception) {
NSLog(@"Error setting property 'values': %@", exception.reason);
}
}
- (void)setSelectedIndex:(NSNumber *)selectedIndex {
@try {
self.selectedSegmentIndex = selectedIndex.integerValue;
} @catch (NSException *exception) {
NSLog(@"Error setting property 'selectedIndex': %@", exception.reason);
}
}
- (void)onChange:(UISegmentedControl *)sender {
if (self.onChangeEvent) {
self.onChangeEvent(@{@"selectedIndex": @(self.selectedSegmentIndex)});
}
}
@end
@implementation CustomSegmentedControlManager
static BOOL isRegistered = NO;
RCT_EXPORT_MODULE(CustomSegmentedControl)
- (UIView *)view {
return [CustomSegmentedControl new];
}
RCT_EXPORT_VIEW_PROPERTY(values, NSArray)
RCT_EXPORT_VIEW_PROPERTY(selectedIndex, NSNumber)
RCT_EXPORT_VIEW_PROPERTY(onChangeEvent, RCTDirectEventBlock)
+ (void)registerIfNecessary {
if (!isRegistered) {
isRegistered = YES;
// Registration logic if necessary
}
}
@end

View file

@ -1,17 +0,0 @@
//
// EventEmitter.h
// BlueWallet
//
// Created by Marcos Rodriguez on 12/25/20.
// Copyright © 2020 BlueWallet. All rights reserved.
//
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
@interface EventEmitter : RCTEventEmitter <RCTBridgeModule>
+ (EventEmitter *)sharedInstance;
- (void)sendUserActivity:(NSDictionary *)userInfo;
@end

View file

@ -1,54 +0,0 @@
//
// EventEmitter.m
// BlueWallet
//
// Created by Marcos Rodriguez on 12/25/20.
// Copyright © 2020 BlueWallet. All rights reserved.
//
#import "EventEmitter.h"
static EventEmitter *sharedInstance;
@implementation EventEmitter
RCT_EXPORT_MODULE();
+ (BOOL)requiresMainQueueSetup {
return YES;
}
+ (instancetype)sharedInstance {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (void)removeListeners:(double)count {
}
- (instancetype)init {
self = [super init];
return self;
}
- (NSArray<NSString *> *)supportedEvents {
return @[@"onUserActivityOpen"];
}
- (void)sendUserActivity:(NSDictionary *)userInfo
{
[self sendEventWithName:@"onUserActivityOpen" body:userInfo];
}
RCT_EXPORT_METHOD(getMostRecentUserActivity:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.io.bluewallet.bluewallet"];
resolve([defaults valueForKey:@"onUserActivityOpen"]);
}
@end

33
ios/EventEmitter.swift Normal file
View file

@ -0,0 +1,33 @@
import Foundation
import React
@objc(EventEmitter)
class EventEmitter: RCTEventEmitter {
static let sharedInstance = EventEmitter()
override class func requiresMainQueueSetup() -> Bool {
return true
}
@objc static func shared() -> EventEmitter {
return sharedInstance
}
override func supportedEvents() -> [String]! {
return ["onUserActivityOpen"]
}
@objc func sendUserActivity(_ userInfo: [String: Any]) {
// Removed unnecessary type check; directly sending the event with userInfo.
sendEvent(withName: "onUserActivityOpen", body: userInfo)
}
@objc func getMostRecentUserActivity(_ resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: RCTPromiseRejectBlock) {
if let defaults = UserDefaults(suiteName: "group.io.bluewallet.bluewallet") {
resolve(defaults.value(forKey: "onUserActivityOpen"))
} else {
resolve(nil)
}
}
}

View file

@ -1,17 +0,0 @@
//
// MenuElementsEmitter.h
// BlueWallet
//
#import <React/RCTEventEmitter.h>
@interface MenuElementsEmitter : RCTEventEmitter
+ (instancetype)sharedInstance;
- (void)openSettings;
- (void)addWalletMenuAction;
- (void)importWalletMenuAction;
- (void)reloadTransactionsMenuAction;
@end

View file

@ -1,27 +0,0 @@
//
// MenuElementsEmitter.h
// BlueWallet
//
// Created by Marcos Rodriguez on 11/7/24.
// Copyright © 2024 BlueWallet. All rights reserved.
//
//
// MenuElementsEmitter.h
// BlueWallet
//
#import <React/RCTEventEmitter.h>
@interface MenuElementsEmitter : RCTEventEmitter
+ (instancetype)sharedInstance;
- (void)openSettings;
- (void)addWalletMenuAction;
- (void)importWalletMenuAction;
- (void)reloadTransactionsMenuAction;
- (NSArray<NSString *> *)supportedEvents;
@end

View file

@ -1,62 +1,13 @@
// #import <React/RCTBridgeModule.h>
// MenuElementsEmitter.m #import <React/RCTEventEmitter.h>
// BlueWallet
//
// Created by Marcos Rodriguez on 11/7/24.
// Copyright © 2024 BlueWallet. All rights reserved.
//
#import "MenuElementsEmitter.h" @interface RCT_EXTERN_MODULE(MenuElementsEmitter, RCTEventEmitter)
static MenuElementsEmitter *sharedInstance; RCT_EXTERN_METHOD(supportedEvents)
RCT_EXTERN_METHOD(openSettings)
@implementation MenuElementsEmitter RCT_EXTERN_METHOD(addWalletMenuAction)
RCT_EXTERN_METHOD(importWalletMenuAction)
RCT_EXPORT_MODULE(); RCT_EXTERN_METHOD(reloadTransactionsMenuAction)
RCT_EXTERN_METHOD(shared)
+ (BOOL)requiresMainQueueSetup {
return YES;
}
+ (instancetype)sharedInstance {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (instancetype)init {
self = [super init];
if (self) {
sharedInstance = self;
}
return self;
}
- (NSArray<NSString *> *)supportedEvents {
return @[
@"openSettings",
@"addWalletMenuAction",
@"importWalletMenuAction",
@"reloadTransactionsMenuAction"
];
}
- (void)openSettings {
[self sendEventWithName:@"openSettings" body:nil];
}
- (void)addWalletMenuAction {
[self sendEventWithName:@"addWalletMenuAction" body:nil];
}
- (void)importWalletMenuAction {
[self sendEventWithName:@"importWalletMenuAction" body:nil];
}
- (void)reloadTransactionsMenuAction {
[self sendEventWithName:@"reloadTransactionsMenuAction" body:nil];
}
@end @end

View file

@ -0,0 +1,134 @@
import Foundation
import React
@objc(MenuElementsEmitter)
class MenuElementsEmitter: RCTEventEmitter {
// Use a weak reference for the singleton to prevent retain cycles
private static weak var sharedInstance: MenuElementsEmitter?
// Use LRU cache with a max size to prevent unbounded growth
private var lastEventTime: [String: TimeInterval] = [:]
private let throttleInterval: TimeInterval = 0.3 // 300ms throttle
private let maxCacheSize = 10 // Limit the cache size
// Track listener state without needing constant bridge access
private var hasListeners = false
override init() {
super.init()
MenuElementsEmitter.sharedInstance = self
NSLog("[MenuElements] MenuElementsEmitter initialized")
}
deinit {
NSLog("[MenuElements] MenuElementsEmitter deallocated")
// Ensure all event listeners are removed in deinit
self.removeAllListeners()
}
override class func requiresMainQueueSetup() -> Bool {
return true
}
override func supportedEvents() -> [String]! {
return ["openSettings", "addWalletMenuAction", "importWalletMenuAction", "reloadTransactionsMenuAction"]
}
@objc static func shared() -> MenuElementsEmitter? {
if sharedInstance == nil {
NSLog("[MenuElements] Warning: Attempting to use sharedInstance when it's nil")
}
return sharedInstance
}
override func startObserving() {
hasListeners = true
NSLog("[MenuElements] Started observing events, bridge: \(self.bridge != nil ? "available" : "unavailable")")
}
override func stopObserving() {
hasListeners = false
NSLog("[MenuElements] Stopped observing events")
// Clear cache when stopping observation
lastEventTime.removeAll()
}
private func limitCacheSize() {
if lastEventTime.count > maxCacheSize {
// Remove oldest entries if cache is too large
let sortedKeys = lastEventTime.sorted(by: { $0.value < $1.value })
for i in 0..<(lastEventTime.count - maxCacheSize) {
lastEventTime.removeValue(forKey: sortedKeys[i].key)
}
}
}
private func canEmitEvent(named eventName: String) -> Bool {
let now = Date().timeIntervalSince1970
if let lastTime = lastEventTime[eventName], now - lastTime < throttleInterval {
NSLog("[MenuElements] Throttling event: \(eventName)")
return false
}
lastEventTime[eventName] = now
limitCacheSize() // Keep cache size in check
let canEmit = hasListeners && bridge != nil
if (!canEmit) {
NSLog("[MenuElements] Cannot emit event: \(eventName), hasListeners: \(hasListeners), bridge: \(bridge != nil ? "available" : "unavailable")")
}
return canEmit
}
private func safelyEmitEvent(withName name: String) {
guard canEmitEvent(named: name) else { return }
NSLog("[MenuElements] Emitting event: \(name)")
// Use weak self to avoid retain cycles
DispatchQueue.main.async { [weak self] in
guard let self = self, self.bridge != nil, self.hasListeners else {
NSLog("[MenuElements] Failed to emit event: \(name) - bridge or listeners not available")
return
}
self.sendEvent(withName: name, body: nil)
NSLog("[MenuElements] Event sent: \(name)")
}
}
func removeAllListeners() {
NSLog("[MenuElements] Removing all listeners")
// Clean up resources
lastEventTime.removeAll()
}
@objc func openSettings() {
NSLog("[MenuElements] openSettings method called")
safelyEmitEvent(withName: "openSettings")
}
@objc func addWalletMenuAction() {
NSLog("[MenuElements] addWalletMenuAction method called")
safelyEmitEvent(withName: "addWalletMenuAction")
}
@objc func importWalletMenuAction() {
NSLog("[MenuElements] importWalletMenuAction method called")
safelyEmitEvent(withName: "importWalletMenuAction")
}
@objc func reloadTransactionsMenuAction() {
safelyEmitEvent(withName: "reloadTransactionsMenuAction")
}
override func invalidate() {
NSLog("[MenuElements] Module invalidated")
if MenuElementsEmitter.sharedInstance === self {
MenuElementsEmitter.sharedInstance = nil
}
removeAllListeners()
super.invalidate()
}
}

File diff suppressed because it is too large Load diff

View file

@ -30,8 +30,8 @@ class MarketAPI {
return "https://www.bnr.ro/nbrfxrates.xml" return "https://www.bnr.ro/nbrfxrates.xml"
case "Kraken": case "Kraken":
return "https://api.kraken.com/0/public/Ticker?pair=XXBTZ\(endPointKey.uppercased())" return "https://api.kraken.com/0/public/Ticker?pair=XXBTZ\(endPointKey.uppercased())"
default: default: // CoinDesk
return "https://api.coindesk.com/v1/bpi/currentprice/\(endPointKey).json" return "https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=\(endPointKey)"
} }
} }
@ -131,8 +131,14 @@ class MarketAPI {
throw CurrencyError(errorDescription: "Data formatting error for source: \(source)") throw CurrencyError(errorDescription: "Data formatting error for source: \(source)")
} }
} }
default: default: // CoinDesk
throw CurrencyError(errorDescription: "Unsupported data source \(source)") if let rateDouble = json[endPointKey] as? Double {
let lastUpdatedString = ISO8601DateFormatter().string(from: Date())
latestRateDataStore = WidgetDataStore(rate: String(rateDouble), lastUpdate: lastUpdatedString, rateDouble: rateDouble)
return latestRateDataStore
} else {
throw CurrencyError(errorDescription: "Data formatting error for source: \(source)")
}
} }
} }

View file

@ -12,7 +12,6 @@
"storage_is_encrypted": "وحدة التخزين مشفرة. أنت بحاجة إلى كلمة المرور لفك تشفيرها", "storage_is_encrypted": "وحدة التخزين مشفرة. أنت بحاجة إلى كلمة المرور لفك تشفيرها",
"yes": "نعم", "yes": "نعم",
"no": "لا", "no": "لا",
"save": "حفظ",
"seed": "عبارة الاسترداد", "seed": "عبارة الاسترداد",
"success": "نجاح", "success": "نجاح",
"wallet_key": "مفتاح المحفظة", "wallet_key": "مفتاح المحفظة",
@ -210,13 +209,8 @@
"set_electrum_server_as_default": "هل تريد تعيين {server} كخادم Electrum الافتراضي؟", "set_electrum_server_as_default": "هل تريد تعيين {server} كخادم Electrum الافتراضي؟",
"electrum_settings_server": "خادم Electrum", "electrum_settings_server": "خادم Electrum",
"electrum_status": "الحالة", "electrum_status": "الحالة",
"electrum_clear_alert_title": "محو السجل؟",
"electrum_clear_alert_message": "هل تريد مسح سجل خوادم Electrum؟",
"electrum_clear_alert_cancel": "الغاء",
"electrum_clear_alert_ok": "موافق",
"electrum_reset": "إعادة تعيين إلى الافتراضي",
"electrum_unable_to_connect": "تعذر الاتصال بـ {server}.", "electrum_unable_to_connect": "تعذر الاتصال بـ {server}.",
"electrum_reset_to_default": "هل أنت متأكد من رغبتك في إعادة تعيين إعدادات Electrum إلى الإعدادات الافتراضية؟", "electrum_reset": "إعادة تعيين إلى الافتراضي",
"encrypt_decrypt": "فك تشفير وحدة التخزين", "encrypt_decrypt": "فك تشفير وحدة التخزين",
"encrypt_decrypt_q": "هل أنت متأكد أنك تريد فك تشفير وحدة التخزين الخاصة بك؟ سيسمح إجراء ذلك بالوصول إلى محافظك دون كلمة مرور.", "encrypt_decrypt_q": "هل أنت متأكد أنك تريد فك تشفير وحدة التخزين الخاصة بك؟ سيسمح إجراء ذلك بالوصول إلى محافظك دون كلمة مرور.",
"encrypt_enc_and_pass": "مشفرة ومحمية بكلمة مرور", "encrypt_enc_and_pass": "مشفرة ومحمية بكلمة مرور",
@ -337,7 +331,6 @@
"details_export_history": "تصدير السجل ل ملف CSV", "details_export_history": "تصدير السجل ل ملف CSV",
"details_master_fingerprint": "البصمة الرئيسية", "details_master_fingerprint": "البصمة الرئيسية",
"details_multisig_type": "متعدد التواقيع", "details_multisig_type": "متعدد التواقيع",
"details_no_cancel": "لا، إلغاء",
"details_show_xpub": "إظهار عنوان XPUB للمحفظة", "details_show_xpub": "إظهار عنوان XPUB للمحفظة",
"details_show_addresses": "عرض العناوين", "details_show_addresses": "عرض العناوين",
"details_title": "المحفظة", "details_title": "المحفظة",

View file

@ -12,7 +12,6 @@
"storage_is_encrypted": "Ваша сховішча зашыфравана. Для расшыфроўкі патрабуецца пароль.", "storage_is_encrypted": "Ваша сховішча зашыфравана. Для расшыфроўкі патрабуецца пароль.",
"yes": "Так", "yes": "Так",
"no": "Не", "no": "Не",
"save": "Захаваць",
"seed": "Семя", "seed": "Семя",
"success": "Посьпех", "success": "Посьпех",
"wallet_key": "Ключ ад кашалька" "wallet_key": "Ключ ад кашалька"
@ -44,7 +43,6 @@
"create_to": "Да" "create_to": "Да"
}, },
"settings": { "settings": {
"electrum_clear_alert_cancel": "Адмяніць",
"save": "Захаваць" "save": "Захаваць"
}, },
"wallets": { "wallets": {

View file

@ -11,7 +11,6 @@
"storage_is_encrypted": "Вашият портфейл е криптиран. Необходима е парола за декриптиране", "storage_is_encrypted": "Вашият портфейл е криптиран. Необходима е парола за декриптиране",
"yes": "Да", "yes": "Да",
"no": "Не", "no": "Не",
"save": "Запази",
"seed": "Сиид", "seed": "Сиид",
"success": "Успех", "success": "Успех",
"wallet_key": "Парола на портфейла" "wallet_key": "Парола на портфейла"
@ -168,13 +167,8 @@
"electrum_saved": "Промените бяха запазени успешно. Моля, рестартирайте Блу Уолет за да видите промените.", "electrum_saved": "Промените бяха запазени успешно. Моля, рестартирайте Блу Уолет за да видите промените.",
"set_electrum_server_as_default": "Задайте {server} като Електрум сървър по подразбиране? ", "set_electrum_server_as_default": "Задайте {server} като Електрум сървър по подразбиране? ",
"electrum_status": "Статус", "electrum_status": "Статус",
"electrum_clear_alert_title": "Изчисти историята?",
"electrum_clear_alert_message": "Искате ли да изтриете електрум сървър историята?",
"electrum_clear_alert_cancel": "Отказ",
"electrum_clear_alert_ok": "Ок",
"electrum_reset": "Начални настройки",
"electrum_unable_to_connect": "Не възможно свързване със сървър {server}.", "electrum_unable_to_connect": "Не възможно свързване със сървър {server}.",
"electrum_reset_to_default": "Сигурни ли сте, че искате да върнете Електрум към първоначалните настройки?", "electrum_reset": "Начални настройки",
"encrypt_decrypt": "Декриптирай хранилището", "encrypt_decrypt": "Декриптирай хранилището",
"encrypt_decrypt_q": "Сигурни ли сте, че искате да декриптирате хранилището? Това ще направи портфейлите ви достъпни без парола.", "encrypt_decrypt_q": "Сигурни ли сте, че искате да декриптирате хранилището? Това ще направи портфейлите ви достъпни без парола.",
"encrypt_enc_and_pass": "Криптиран и защитен с парола", "encrypt_enc_and_pass": "Криптиран и защитен с парола",

View file

@ -11,7 +11,6 @@
"storage_is_encrypted": "جاگه زفت کردنی ایسا ریس رزم هڌ. سی گۊشیڌنس وا رزمسه داشته بۊی.", "storage_is_encrypted": "جاگه زفت کردنی ایسا ریس رزم هڌ. سی گۊشیڌنس وا رزمسه داشته بۊی.",
"yes": "هری", "yes": "هری",
"no": "نه", "no": "نه",
"save": "زفت کردن",
"seed": "سید", "seed": "سید",
"success": "سر ٱنجوم گرهڌ", "success": "سر ٱنجوم گرهڌ",
"wallet_key": "کیلیت کیف پیل", "wallet_key": "کیلیت کیف پیل",
@ -187,13 +186,8 @@
"set_electrum_server_as_default": "{server} سی سرور پؽش فرز الکترام ساموو بۊوه؟", "set_electrum_server_as_default": "{server} سی سرور پؽش فرز الکترام ساموو بۊوه؟",
"electrum_settings_server": "سرور الکترام", "electrum_settings_server": "سرور الکترام",
"electrum_status": "وزیت", "electrum_status": "وزیت",
"electrum_clear_alert_title": "ویرگار پاک بۊ؟",
"electrum_clear_alert_message": "اخۊی ویرگار سرورا الکترام نه پاک کۊنی؟",
"electrum_clear_alert_cancel": "لقو",
"electrum_clear_alert_ok": "هری",
"electrum_reset": "ورگندن به پؽش فرز",
"electrum_unable_to_connect": "نا مۉفق منه منپیز به {server}", "electrum_unable_to_connect": "نا مۉفق منه منپیز به {server}",
"electrum_reset_to_default": "الن اخۊی سامووا الکترام نه به هالت پؽش فرز وورنشۊوی کۊنی؟", "electrum_reset": "ورگندن به پؽش فرز",
"encrypt_decrypt": "رزم گوشایی جاگه زفت کردنی", "encrypt_decrypt": "رزم گوشایی جاگه زفت کردنی",
"encrypt_title": "امنیت", "encrypt_title": "امنیت",
"encrypt_tstorage": "جاگه زفت کردنی", "encrypt_tstorage": "جاگه زفت کردنی",

View file

@ -12,7 +12,6 @@
"storage_is_encrypted": "L'informació està xifrada. Es requereix la contrasenya per a desxifrar-la.", "storage_is_encrypted": "L'informació està xifrada. Es requereix la contrasenya per a desxifrar-la.",
"yes": "Si", "yes": "Si",
"no": "No", "no": "No",
"save": "Desar",
"seed": "Llavor", "seed": "Llavor",
"success": "Èxit", "success": "Èxit",
"wallet_key": "Clau del moneder", "wallet_key": "Clau del moneder",
@ -168,9 +167,6 @@
"use_ssl": "Utilitza SSL", "use_ssl": "Utilitza SSL",
"electrum_settings_server": "Servidor Electrum", "electrum_settings_server": "Servidor Electrum",
"electrum_status": "estat", "electrum_status": "estat",
"electrum_clear_alert_title": "Netejar l'historial?",
"electrum_clear_alert_cancel": "Cancel·lar",
"electrum_clear_alert_ok": "D'acord",
"electrum_reset": "Restableix la configuració predeterminada", "electrum_reset": "Restableix la configuració predeterminada",
"encrypt_title": "Seguretat", "encrypt_title": "Seguretat",
"encrypt_use": "Utilitza {type}", "encrypt_use": "Utilitza {type}",
@ -237,7 +233,6 @@
"details_delete_wallet": "Eliminar Moneder", "details_delete_wallet": "Eliminar Moneder",
"details_export_backup": "Exportar / Guardar", "details_export_backup": "Exportar / Guardar",
"details_master_fingerprint": "Petjada digital mestre", "details_master_fingerprint": "Petjada digital mestre",
"details_no_cancel": "No, cancel·lar",
"details_show_xpub": "Mostrar wallet XPUB", "details_show_xpub": "Mostrar wallet XPUB",
"details_title": "Detalls del moneder", "details_title": "Detalls del moneder",
"wallets": "moneders", "wallets": "moneders",

View file

@ -10,12 +10,11 @@
"never": "Nikdy", "never": "Nikdy",
"of": "{number} z(e) {total}", "of": "{number} z(e) {total}",
"ok": "OK", "ok": "OK",
"customize": "Přizpůsobit",
"enter_url": "Zadejte URL", "enter_url": "Zadejte URL",
"storage_is_encrypted": "Vaše úložiště je zašifrované. Zadejte heslo k odemčení.", "storage_is_encrypted": "Vaše úložiště je zašifrované. Zadejte heslo k odemčení.",
"yes": "Ano", "yes": "Ano",
"no": "Ne", "no": "Ne",
"save": "Uložit", "save": "Uložit",
"seed": "Seed", "seed": "Seed",
"success": "Úspěch", "success": "Úspěch",
"wallet_key": "Klíč peněženky", "wallet_key": "Klíč peněženky",
@ -28,6 +27,8 @@
"enter_amount": "Zadejte částku", "enter_amount": "Zadejte částku",
"qr_custom_input_button": "Klepněte 10× k zadání vlastního vstupu", "qr_custom_input_button": "Klepněte 10× k zadání vlastního vstupu",
"unlock": "Odemknout", "unlock": "Odemknout",
"port": "Port",
"ssl_port": "SSL port",
"suggested": "Doporučené" "suggested": "Doporučené"
}, },
"azteco": { "azteco": {
@ -74,6 +75,7 @@
"please_pay": "Zaplaťte prosím", "please_pay": "Zaplaťte prosím",
"preimage": "Předobraz", "preimage": "Předobraz",
"sats": "sats.", "sats": "sats.",
"date_time": "Datum a čas",
"wasnt_paid_and_expired": "Tato faktura nebyla zaplacena a její platnost vypršela." "wasnt_paid_and_expired": "Tato faktura nebyla zaplacena a její platnost vypršela."
}, },
"plausibledeniability": { "plausibledeniability": {
@ -106,7 +108,7 @@
"minSats": "Minimální množství je {min} sats", "minSats": "Minimální množství je {min} sats",
"minSatsFull": "Minimální částka je {min} sats nebo {currency} ", "minSatsFull": "Minimální částka je {min} sats nebo {currency} ",
"qrcode_for_the_address": "QR kód pro adresu", "qrcode_for_the_address": "QR kód pro adresu",
"bip47_explanation": "Platební kódy jsou univerzální adresou, která zabraňuje odhalení adres vaší peněženky. Nejsou podporovány všemi službami." "bip47_explanation": "Platební kódy jsou univerzální adresy, které zabraňují prozrazení adres vaší peněženky. Ne všechny služby je však podporují."
}, },
"send": { "send": {
"provided_address_is_invoice": "Zdá se, že tato adresa je určena pro Lightning fakturu. Přejděte prosím do své Lightning peněženky, abyste mohli provést platbu této faktury.", "provided_address_is_invoice": "Zdá se, že tato adresa je určena pro Lightning fakturu. Přejděte prosím do své Lightning peněženky, abyste mohli provést platbu této faktury.",
@ -134,6 +136,9 @@
"details_add_recc_rem_all_alert_description": "Jste si jisti, že chcete odebrat všechny příjemce?", "details_add_recc_rem_all_alert_description": "Jste si jisti, že chcete odebrat všechny příjemce?",
"details_add_rec_rem_all": "Odebrat všechny příjemce", "details_add_rec_rem_all": "Odebrat všechny příjemce",
"details_recipients_title": "Příjemci", "details_recipients_title": "Příjemci",
"details_recipient_title": "Příjemce č. {number} z(e) {total}",
"please_complete_recipient_title": "Nekompletní příjemce",
"please_complete_recipient_details": "Vyplňte prosím všechny detaily příjemce č. {number} před přidáním nového příjemce.",
"details_address": "Adresa", "details_address": "Adresa",
"details_address_field_is_not_valid": "Adresa není správně vyplněna.", "details_address_field_is_not_valid": "Adresa není správně vyplněna.",
"details_adv_fee_bump": "Povolit navýšení poplatku", "details_adv_fee_bump": "Povolit navýšení poplatku",
@ -180,6 +185,7 @@
"input_total": "Celkem:", "input_total": "Celkem:",
"permission_camera_message": "K použití fotoaparátu potřebujeme vaše povolení", "permission_camera_message": "K použití fotoaparátu potřebujeme vaše povolení",
"psbt_sign": "Podepsat transakci", "psbt_sign": "Podepsat transakci",
"invalid_psbt": "Byla poskytnuta neplatná PSBT.",
"open_settings": "Otevřít nastavení", "open_settings": "Otevřít nastavení",
"permission_storage_denied_message": "BlueWallet nemůže tento soubor uložit. Otevřete prosím nastavení zařízení a povolte funkci Oprávnění k ukládání.", "permission_storage_denied_message": "BlueWallet nemůže tento soubor uložit. Otevřete prosím nastavení zařízení a povolte funkci Oprávnění k ukládání.",
"permission_storage_title": "Povolení k přístupu do úložiště", "permission_storage_title": "Povolení k přístupu do úložiště",
@ -190,7 +196,7 @@
"outdated_rate": "Kurz byl naposledy aktualizován: {date}", "outdated_rate": "Kurz byl naposledy aktualizován: {date}",
"psbt_tx_open": "Otevřít podepsanou transakci", "psbt_tx_open": "Otevřít podepsanou transakci",
"psbt_tx_scan": "Skenovat podepsanou transakci", "psbt_tx_scan": "Skenovat podepsanou transakci",
"qr_error_no_qrcode": "Ve vybraném obrázku se nám nepodařilo najít QR kód. Ujistěte se, že obrázek obsahuje pouze QR kód a žádný další obsah, například text nebo tlačítka.", "qr_error_no_qrcode": "Ve vybraném obrázku nebyl nalezen platný QR kód. Zajistěte, aby obrázek obsahoval pouze QR kód a žádný další obsah jako třeba text nebo tlačítka.",
"reset_amount": "Vynulovat částku", "reset_amount": "Vynulovat částku",
"reset_amount_confirm": "Chcete částku vynulovat?", "reset_amount_confirm": "Chcete částku vynulovat?",
"success_done": "Hotovo", "success_done": "Hotovo",
@ -250,15 +256,11 @@
"electrum_status": "Stav", "electrum_status": "Stav",
"electrum_preferred_server": "Upřednostňovaný server", "electrum_preferred_server": "Upřednostňovaný server",
"electrum_preferred_server_description": "Zadejte server, který má vaše peněženka používat pro všechny bitcoinové aktivity. Po nastavení bude vaše peněženka používat výhradně tento server ke kontrole zůstatků, odesílání transakcí a načítání síťových dat. Před nastavením se ujistěte, že tomuto serveru důvěřujete.", "electrum_preferred_server_description": "Zadejte server, který má vaše peněženka používat pro všechny bitcoinové aktivity. Po nastavení bude vaše peněženka používat výhradně tento server ke kontrole zůstatků, odesílání transakcí a načítání síťových dat. Před nastavením se ujistěte, že tomuto serveru důvěřujete.",
"electrum_clear_alert_title": "Smazat historii?",
"electrum_clear_alert_message": "Chcete vymazat historii serverů Electrum?",
"electrum_clear_alert_cancel": "Zrušit",
"electrum_clear_alert_ok": "Ok",
"electrum_reset": "Obnovit do výchozího nastavení",
"electrum_unable_to_connect": "Nelze se připojit k {server}.", "electrum_unable_to_connect": "Nelze se připojit k {server}.",
"electrum_history": "Historie", "electrum_history": "Historie",
"electrum_reset_to_default": "Opravdu chcete obnovit nastavení Electrum na výchozí hodnoty?", "electrum_reset_to_default": "Toto nastavení nechá aplikaci BlueWallet náhodně vybrat server ze seznamu navrhovaných.",
"electrum_clear": "Vymazat historii", "electrum_reset": "Obnovit do výchozího nastavení",
"electrum_reset_to_default_and_clear_history": "Obnovit výchozí nastavení a vymazat historii",
"encrypt_decrypt": "Dešifrovat úložiště", "encrypt_decrypt": "Dešifrovat úložiště",
"encrypt_decrypt_q": "Opravdu chcete dešifrovat úložiště? To umožní přistupovat k vašim peněženkám bez hesla.", "encrypt_decrypt_q": "Opravdu chcete dešifrovat úložiště? To umožní přistupovat k vašim peněženkám bez hesla.",
"encrypt_enc_and_pass": "Šifrovat a chránit heslem", "encrypt_enc_and_pass": "Šifrovat a chránit heslem",
@ -272,6 +274,8 @@
"encrypt_title": "Zabezpečení", "encrypt_title": "Zabezpečení",
"encrypt_tstorage": "Úložiště", "encrypt_tstorage": "Úložiště",
"encrypt_use": "Použít {type}", "encrypt_use": "Použít {type}",
"set_as_preferred": "Nastavit jako preferovaný",
"set_as_preferred_electrum": "Nastavení {host}:{port} jako upřednostňovaného serveru zakáže náhodné připojování k navrhovanému serveru.",
"encrypted_feature_disabled": "Tato funkce nemůže být použita, pokud je povoleno zašifrované úložiště.", "encrypted_feature_disabled": "Tato funkce nemůže být použita, pokud je povoleno zašifrované úložiště.",
"encrypt_use_expl": "{type} bude použit k potvrzení vaší identity před provedením transakce, odemknutím, exportem nebo smazáním peněženky. {type} nebude použit k odemknutí zašifrovaného úložiště.", "encrypt_use_expl": "{type} bude použit k potvrzení vaší identity před provedením transakce, odemknutím, exportem nebo smazáním peněženky. {type} nebude použit k odemknutí zašifrovaného úložiště.",
"biometrics_fail": "Pokud {type} není povolen, nebo selže při odemykání, můžete jako alternativu použít přístupový kód vašeho zařízení.", "biometrics_fail": "Pokud {type} není povolen, nebo selže při odemykání, můžete jako alternativu použít přístupový kód vašeho zařízení.",
@ -291,6 +295,7 @@
"network": "Síť", "network": "Síť",
"network_broadcast": "Odeslat transakci", "network_broadcast": "Odeslat transakci",
"network_electrum": "Electrum server", "network_electrum": "Electrum server",
"electrum_suggested_description": "Pokud nebude nastaven preferovaný server, bude náhodně vybrán navrhovaný server.",
"not_a_valid_uri": "Neplatná URI", "not_a_valid_uri": "Neplatná URI",
"notifications": "Oznámení", "notifications": "Oznámení",
"open_link_in_explorer": "Otevřít odkaz v průzkumníku", "open_link_in_explorer": "Otevřít odkaz v průzkumníku",
@ -402,7 +407,6 @@
"add_wallet_name": "Název peněženky", "add_wallet_name": "Název peněženky",
"add_wallet_type": "Typ", "add_wallet_type": "Typ",
"add_wallet_seed_length": "Délka seedu", "add_wallet_seed_length": "Délka seedu",
"add_wallet_seed_length_message": "Zvolte délku seed fráze, kterou chcete použít pro tuto peněženku.",
"add_wallet_seed_length_12": "12 slov", "add_wallet_seed_length_12": "12 slov",
"add_wallet_seed_length_24": "24 slov", "add_wallet_seed_length_24": "24 slov",
"clipboard_bitcoin": "Ve schránce máte bitcoinovou adresu. Chcete ji použít pro transakci?", "clipboard_bitcoin": "Ve schránce máte bitcoinovou adresu. Chcete ji použít pro transakci?",
@ -422,7 +426,6 @@
"details_export_history": "Exportovat historii do CSV", "details_export_history": "Exportovat historii do CSV",
"details_master_fingerprint": "Hlavní otisk", "details_master_fingerprint": "Hlavní otisk",
"details_multisig_type": "multisig", "details_multisig_type": "multisig",
"details_no_cancel": "Ne, zrušit",
"details_show_xpub": "Zobrazit XPUB peněženky", "details_show_xpub": "Zobrazit XPUB peněženky",
"details_show_addresses": "Zobrazit adresy", "details_show_addresses": "Zobrazit adresy",
"details_title": "Peněženka", "details_title": "Peněženka",
@ -493,7 +496,9 @@
"identity_pubkey": "Identity Pubkey", "identity_pubkey": "Identity Pubkey",
"xpub_title": "XPUB peněženky", "xpub_title": "XPUB peněženky",
"manage_wallets_search_placeholder": "Prohledat peněženky, poznámky", "manage_wallets_search_placeholder": "Prohledat peněženky, poznámky",
"more_info": "Další informace" "more_info": "Další informace",
"details_delete_wallet_error_message": "Došlo k problému během potvrzování, zda byla tato peněženka odstraněna z oznámení. Toto může být způsobeno problémem se sítí nebo kvůli špatnému připojení. Pokud budete pokračovat, je možné, že i nadále budete dostávat oznámení o transakcích souvisejících s touto peněženkou, přestože už bude odstraněna.",
"details_delete_anyway": "Přesto odstranit"
}, },
"total_balance_view": { "total_balance_view": {
"display_in_bitcoin": "Zobrazit v bitcoinech", "display_in_bitcoin": "Zobrazit v bitcoinech",
@ -508,6 +513,10 @@
"default_label": "Vícepodpisové Úložiště", "default_label": "Vícepodpisové Úložiště",
"multisig_vault_explain": "Nejlepší zabezpečení pro velké částky", "multisig_vault_explain": "Nejlepší zabezpečení pro velké částky",
"provide_signature": "Poskytnout podpis", "provide_signature": "Poskytnout podpis",
"provide_signature_details": "K podpisu této transakce použijte své zařízení a peněženku, ve které je klíč umístěn",
"provide_signature_details_bluewallet": "V aplikaci BlueWallet přejděte do nabídky Odeslat a vyberte",
"provide_signature_next_steps": "Naskenujte nebo importujte podepsanou transakci",
"provide_signature_next_steps_details": "Poté, co vaše peněženka úspěšně podepsala transakci, naskenujte uvedený QR kód, nebo importujte doprovodný soubor, a poté zkontrolujte veškeré podrobnosti transakce před tím, než ji odešlete.",
"vault_key": "{number}. klíč Úložiště", "vault_key": "{number}. klíč Úložiště",
"required_keys_out_of_total": "Požadovaných klíčů z celkového počtu", "required_keys_out_of_total": "Požadovaných klíčů z celkového počtu",
"fee": "Poplatek: {number}", "fee": "Poplatek: {number}",
@ -654,6 +663,8 @@
"bip47": { "bip47": {
"payment_code": "Platební kód", "payment_code": "Platební kód",
"contacts": "Kontakty", "contacts": "Kontakty",
"bip47_explain": "Znovu použitelný a sdílitelný kód",
"bip47_explain_subtitle": "BIP47",
"purpose": "Opakovaně použitelný kód, který je možné sdílet (BIP47)", "purpose": "Opakovaně použitelný kód, který je možné sdílet (BIP47)",
"pay_this_contact": "Zaplatit tomuto kontaktu", "pay_this_contact": "Zaplatit tomuto kontaktu",
"rename_contact": "Přejmenovat kontakt", "rename_contact": "Přejmenovat kontakt",

View file

@ -11,7 +11,6 @@
"storage_is_encrypted": "Mae'r storfa wedi encryptio. Mae angen Cyfrinair i'w ddad-gryptio. ", "storage_is_encrypted": "Mae'r storfa wedi encryptio. Mae angen Cyfrinair i'w ddad-gryptio. ",
"yes": "Ie", "yes": "Ie",
"no": "Na", "no": "Na",
"save": "Safio",
"seed": "Hadyn", "seed": "Hadyn",
"success": "Llwyddiant", "success": "Llwyddiant",
"wallet_key": "Allwedd waled" "wallet_key": "Allwedd waled"
@ -109,8 +108,6 @@
"default_wallets": "Gweld Pob Waled", "default_wallets": "Gweld Pob Waled",
"electrum_connected": "Wedi Cysylltu", "electrum_connected": "Wedi Cysylltu",
"electrum_connected_not": "Heb Gysylltu", "electrum_connected_not": "Heb Gysylltu",
"electrum_clear_alert_cancel": "Canslo",
"electrum_clear_alert_ok": "Iawn",
"encrypt_decrypt": "Dadgryptio'r Storfa", "encrypt_decrypt": "Dadgryptio'r Storfa",
"encrypt_title": "Diogelwch", "encrypt_title": "Diogelwch",
"encrypt_tstorage": "Storfa", "encrypt_tstorage": "Storfa",
@ -163,7 +160,6 @@
"details_connected_to": "Wedi cysylltu efo", "details_connected_to": "Wedi cysylltu efo",
"details_delete": "Gwaredu", "details_delete": "Gwaredu",
"details_delete_wallet": "Gwaredu Waled", "details_delete_wallet": "Gwaredu Waled",
"details_no_cancel": "Na, canslo",
"details_show_addresses": "Dangos cyfeiriadau", "details_show_addresses": "Dangos cyfeiriadau",
"wallets": "Waledi", "wallets": "Waledi",
"details_type": "Math", "details_type": "Math",

View file

@ -6,7 +6,6 @@
"never": "aldrig", "never": "aldrig",
"ok": "OK", "ok": "OK",
"storage_is_encrypted": "Lageret er krypteret. Indtast adgangskode for at dekryptere", "storage_is_encrypted": "Lageret er krypteret. Indtast adgangskode for at dekryptere",
"save": "save",
"success": "Succes" "success": "Succes"
}, },
"azteco": { "azteco": {
@ -60,7 +59,6 @@
"settings": { "settings": {
"about": "Andet", "about": "Andet",
"currency": "Valuta", "currency": "Valuta",
"electrum_clear_alert_cancel": "Annuller",
"header": "indstillinger", "header": "indstillinger",
"language": "Sprog", "language": "Sprog",
"lightning_settings": "Lightning settings", "lightning_settings": "Lightning settings",
@ -87,7 +85,6 @@
"details_are_you_sure": "Er du sikker?", "details_are_you_sure": "Er du sikker?",
"details_delete": "Slet", "details_delete": "Slet",
"details_export_backup": "Eksporter / backup", "details_export_backup": "Eksporter / backup",
"details_no_cancel": "Nej, annuller",
"details_show_xpub": "Vis wallet XPUB", "details_show_xpub": "Vis wallet XPUB",
"details_yes_delete": "Ja, slet", "details_yes_delete": "Ja, slet",
"export_title": "wallet eksport", "export_title": "wallet eksport",

View file

@ -10,12 +10,11 @@
"never": "nie", "never": "nie",
"of": "{number} von {total}", "of": "{number} von {total}",
"ok": "OK", "ok": "OK",
"customize": "Anpassen",
"enter_url": "URL eingeben", "enter_url": "URL eingeben",
"storage_is_encrypted": "Zum Entschlüsseln des Speichers das Passwort eingeben.", "storage_is_encrypted": "Zum Entschlüsseln des Speichers das Passwort eingeben.",
"yes": "Ja", "yes": "Ja",
"no": "Nein", "no": "Nein",
"save": "Speichern", "save": "Speichern...",
"seed": "Seed", "seed": "Seed",
"success": "Erfolg", "success": "Erfolg",
"wallet_key": "Wallet Schlüssel", "wallet_key": "Wallet Schlüssel",
@ -255,8 +254,9 @@
"electrum_preferred_server_description": "Den Server eingeben, der die Wallet für alle Bitcoin-Aktivitäten verwenden soll. Sobald festgelegt, wird die Wallet um Salden abzufragen, Transaktionen zu senden und Netzwerkdaten abzurufen ausschliesslich diesen Server verwenden. Prüfen sie vorher, dass der Server vertrauenswürdig ist.", "electrum_preferred_server_description": "Den Server eingeben, der die Wallet für alle Bitcoin-Aktivitäten verwenden soll. Sobald festgelegt, wird die Wallet um Salden abzufragen, Transaktionen zu senden und Netzwerkdaten abzurufen ausschliesslich diesen Server verwenden. Prüfen sie vorher, dass der Server vertrauenswürdig ist.",
"electrum_unable_to_connect": "Verbindung zu {server} kann nicht hergestellt werden.", "electrum_unable_to_connect": "Verbindung zu {server} kann nicht hergestellt werden.",
"electrum_history": "Historie", "electrum_history": "Historie",
"electrum_reset_to_default": "BlueWallet wählt zufällig einen Server aus der Vorschlagsliste oder Historie aus. Der Serververlauf bleibt dadurch unverändert.", "electrum_reset_to_default": "Dies lässt BlueWallet zufällig einen Server aus der Liste der Server auswählen.",
"electrum_reset": "Zurücksetzten", "electrum_reset": "Zurücksetzten",
"electrum_reset_to_default_and_clear_history": "Auf die Standardeinstellungen zurücksetzen und den Verlauf löschen.",
"encrypt_decrypt": "Speicher entschlüsseln", "encrypt_decrypt": "Speicher entschlüsseln",
"encrypt_decrypt_q": "Die Speicherverschlüsselung wirklich aufheben? Hiermit werden die Wallet ohne Passwortschutz direkt benutzbar. ", "encrypt_decrypt_q": "Die Speicherverschlüsselung wirklich aufheben? Hiermit werden die Wallet ohne Passwortschutz direkt benutzbar. ",
"encrypt_enc_and_pass": "Verschlüsselt und passwortgeschützt", "encrypt_enc_and_pass": "Verschlüsselt und passwortgeschützt",
@ -403,7 +403,6 @@
"add_wallet_name": "Wallet Name", "add_wallet_name": "Wallet Name",
"add_wallet_type": "Typ", "add_wallet_type": "Typ",
"add_wallet_seed_length": "Seedlänge", "add_wallet_seed_length": "Seedlänge",
"add_wallet_seed_length_message": "Länge der Seed-Phrase für diese Wallet wählen.",
"add_wallet_seed_length_12": "12 Wörter", "add_wallet_seed_length_12": "12 Wörter",
"add_wallet_seed_length_24": "24 Wörter", "add_wallet_seed_length_24": "24 Wörter",
"clipboard_bitcoin": "In der Zwischenablage ist eine Bitcoin Adresse. Soll diese für eine Transaktion verwendet werden?", "clipboard_bitcoin": "In der Zwischenablage ist eine Bitcoin Adresse. Soll diese für eine Transaktion verwendet werden?",
@ -423,7 +422,6 @@
"details_export_history": "Verlauf als CSV exportieren", "details_export_history": "Verlauf als CSV exportieren",
"details_master_fingerprint": "Fingerabdruckkennung", "details_master_fingerprint": "Fingerabdruckkennung",
"details_multisig_type": "Mehrfachsignatur", "details_multisig_type": "Mehrfachsignatur",
"details_no_cancel": "Nein, abbrechnen",
"details_show_xpub": "Wallet xPub zeigen", "details_show_xpub": "Wallet xPub zeigen",
"details_show_addresses": "Adressen anzeigen", "details_show_addresses": "Adressen anzeigen",
"details_title": "Wallet", "details_title": "Wallet",
@ -494,7 +492,9 @@
"identity_pubkey": "Pubkey-Identität", "identity_pubkey": "Pubkey-Identität",
"xpub_title": "Wallet xPub", "xpub_title": "Wallet xPub",
"manage_wallets_search_placeholder": "Suche in Wallets, Notizen", "manage_wallets_search_placeholder": "Suche in Wallets, Notizen",
"more_info": "Mehr Infos" "more_info": "Mehr Infos",
"details_delete_wallet_error_message": "Bei der Bestätigung, ob diese Wallet aus den Benachrichtigungen entfernt wurde, trat ein Fehler auf möglicherweise wegen Netzwerkproblemen oder schlechter Verbindung. Falls du fortfährst, erhältst du möglicherweise weiterhin Benachrichtigungen für Transaktionen dieser Wallet, auch nachdem sie gelöscht wurde.",
"details_delete_anyway": "Trotzdem löschen"
}, },
"total_balance_view": { "total_balance_view": {
"display_in_bitcoin": "In bitcoin anzeigen", "display_in_bitcoin": "In bitcoin anzeigen",
@ -670,7 +670,7 @@
"notification_tx_unconfirmed": "Benachrichtigungstransaktion noch unbestätigt. Bitte warten", "notification_tx_unconfirmed": "Benachrichtigungstransaktion noch unbestätigt. Bitte warten",
"failed_create_notif_tx": "On-Chain Transaktion konnte nicht in erstellt werden", "failed_create_notif_tx": "On-Chain Transaktion konnte nicht in erstellt werden",
"onchain_tx_needed": "On-Chain Transaktion benötigt.", "onchain_tx_needed": "On-Chain Transaktion benötigt.",
"notif_tx_sent" : "Benachrichtigungstransaktion ist gesendet. Auf Bestätigung warten.", "notif_tx_sent": "Benachrichtigungstransaktion ist gesendet. Auf Bestätigung warten.",
"notif_tx": "Benachrichtigungstransaktion", "notif_tx": "Benachrichtigungstransaktion",
"not_found": "Zahlungscode nicht gefunden" "not_found": "Zahlungscode nicht gefunden"
} }

View file

@ -11,7 +11,6 @@
"storage_is_encrypted": "Το αρχείο σου είναι κρυπτογραφημένο. Χρειάζεται ένας κωδικός για να αποκρυπτογραφηθεί.", "storage_is_encrypted": "Το αρχείο σου είναι κρυπτογραφημένο. Χρειάζεται ένας κωδικός για να αποκρυπτογραφηθεί.",
"yes": "Ναι", "yes": "Ναι",
"no": "Όχι", "no": "Όχι",
"save": "Αποθήκευση",
"success": "Επιτυχία", "success": "Επιτυχία",
"close": "Κλείσιμο", "close": "Κλείσιμο",
"refresh": "Ανανέωση", "refresh": "Ανανέωση",
@ -168,11 +167,8 @@
"use_ssl": "Χρήση SSL", "use_ssl": "Χρήση SSL",
"electrum_settings_server": "Διακομιστής Electrum", "electrum_settings_server": "Διακομιστής Electrum",
"electrum_status": "Κατάσταση", "electrum_status": "Κατάσταση",
"electrum_clear_alert_title": "Καθαρισμός ιστορικού;",
"electrum_clear_alert_cancel": "Ακύρωση",
"electrum_clear_alert_ok": "Εντάξει",
"electrum_reset": "Επαναφορά προεπιλογής",
"electrum_unable_to_connect": "Αδυναμία σύνδεσης στον {server}.", "electrum_unable_to_connect": "Αδυναμία σύνδεσης στον {server}.",
"electrum_reset": "Επαναφορά προεπιλογής",
"encrypt_decrypt": "Αποκρυπτογράφηση αποθηκευτικού χώρου", "encrypt_decrypt": "Αποκρυπτογράφηση αποθηκευτικού χώρου",
"encrypt_title": "Ασφάλεια", "encrypt_title": "Ασφάλεια",
"encrypt_use": "Χρήση {type}", "encrypt_use": "Χρήση {type}",
@ -243,7 +239,6 @@
"details_delete": "Διαγραφή", "details_delete": "Διαγραφή",
"details_delete_wallet": "Διαγραφή πορτοφολιού", "details_delete_wallet": "Διαγραφή πορτοφολιού",
"details_export_backup": "Εξήγαγε / δημιούργησε αντίγραφο ασφαλείας", "details_export_backup": "Εξήγαγε / δημιούργησε αντίγραφο ασφαλείας",
"details_no_cancel": "Όχι, ακύρωσε",
"details_show_xpub": "Προβολή XPUB του πορτοφολιού", "details_show_xpub": "Προβολή XPUB του πορτοφολιού",
"details_show_addresses": "Εμφάνιση διευθύνσεων", "details_show_addresses": "Εμφάνιση διευθύνσεων",
"details_title": "Πορτοφόλι", "details_title": "Πορτοφόλι",

View file

@ -14,7 +14,7 @@
"storage_is_encrypted": "Your storage is encrypted. Password is required to decrypt it.", "storage_is_encrypted": "Your storage is encrypted. Password is required to decrypt it.",
"yes": "Yes", "yes": "Yes",
"no": "No", "no": "No",
"save": "Save", "save": "Save...",
"seed": "Seed", "seed": "Seed",
"success": "Success", "success": "Success",
"wallet_key": "Wallet key", "wallet_key": "Wallet key",
@ -136,6 +136,9 @@
"details_add_recc_rem_all_alert_description": "Are you sure you want to remove all recipients?", "details_add_recc_rem_all_alert_description": "Are you sure you want to remove all recipients?",
"details_add_rec_rem_all": "Remove All Recipients", "details_add_rec_rem_all": "Remove All Recipients",
"details_recipients_title": "Recipients", "details_recipients_title": "Recipients",
"details_recipient_title": "Recipient #{number} of #{total}",
"please_complete_recipient_title": "Incomplete Recipient",
"please_complete_recipient_details": "Please complete the details of recipient #{number} before adding a new recipient.",
"details_address": "Address", "details_address": "Address",
"details_address_field_is_not_valid": "The address is not valid.", "details_address_field_is_not_valid": "The address is not valid.",
"details_adv_fee_bump": "Allow Fee Bump", "details_adv_fee_bump": "Allow Fee Bump",
@ -182,6 +185,7 @@
"input_total": "Total:", "input_total": "Total:",
"permission_camera_message": "We need your permission to use your camera.", "permission_camera_message": "We need your permission to use your camera.",
"psbt_sign": "Sign a transaction", "psbt_sign": "Sign a transaction",
"invalid_psbt": "Invalid PSBT provided.",
"open_settings": "Open Settings", "open_settings": "Open Settings",
"permission_storage_denied_message": "BlueWallet is unable to save this file. Please open your device settings and enable Storage Permission.", "permission_storage_denied_message": "BlueWallet is unable to save this file. Please open your device settings and enable Storage Permission.",
"permission_storage_title": "Storage Access Permission", "permission_storage_title": "Storage Access Permission",
@ -239,6 +243,7 @@
"electrum_connected": "Connected", "electrum_connected": "Connected",
"electrum_connected_not": "Not Connected", "electrum_connected_not": "Not Connected",
"electrum_error_connect": "Cannot connect to the provided Electrum server", "electrum_error_connect": "Cannot connect to the provided Electrum server",
"electrum_error_connect_tor": "Cannot connect to the provided Electrum server. Please make sure Orbot app is connected and try again.",
"lndhub_uri": "E.g., {example}", "lndhub_uri": "E.g., {example}",
"electrum_host": "E.g., {example}", "electrum_host": "E.g., {example}",
"electrum_offline_mode": "Offline Mode", "electrum_offline_mode": "Offline Mode",
@ -285,6 +290,7 @@
"language_isRTL": "Restarting BlueWallet is required for the language orientation to take effect.", "language_isRTL": "Restarting BlueWallet is required for the language orientation to take effect.",
"license": "License", "license": "License",
"lightning_error_lndhub_uri": "Invalid LNDhub URI", "lightning_error_lndhub_uri": "Invalid LNDhub URI",
"lightning_error_lndhub_uri_tor": "Invalid LNDhub URI. Please make sure Orbot app is connected and try again.",
"lightning_saved": "Your changes have been saved successfully.", "lightning_saved": "Your changes have been saved successfully.",
"lightning_settings": "Lightning Settings", "lightning_settings": "Lightning Settings",
"lightning_settings_explain": "To connect to your own LND node, please install LNDhub and put its URL here in settings. Please note that only wallets created after saving changes will connect to the specified LNDhub.", "lightning_settings_explain": "To connect to your own LND node, please install LNDhub and put its URL here in settings. Please note that only wallets created after saving changes will connect to the specified LNDhub.",
@ -422,7 +428,6 @@
"details_export_history": "Export History to CSV", "details_export_history": "Export History to CSV",
"details_master_fingerprint": "Master Fingerprint", "details_master_fingerprint": "Master Fingerprint",
"details_multisig_type": "multisig", "details_multisig_type": "multisig",
"details_no_cancel": "No, cancel",
"details_show_xpub": "Show Wallet XPUB", "details_show_xpub": "Show Wallet XPUB",
"details_show_addresses": "Show addresses", "details_show_addresses": "Show addresses",
"details_title": "Wallet", "details_title": "Wallet",
@ -493,7 +498,9 @@
"identity_pubkey": "Identity Pubkey", "identity_pubkey": "Identity Pubkey",
"xpub_title": "Wallet XPUB", "xpub_title": "Wallet XPUB",
"manage_wallets_search_placeholder": "Search wallets, memos", "manage_wallets_search_placeholder": "Search wallets, memos",
"more_info": "More Info" "more_info": "More Info",
"details_delete_wallet_error_message": "There was an issue confirming if this wallet was removed from notifications—this could be due to a network issue or poor connection. If you continue, you might still receive notifications for transactions related to this wallet, even after it is deleted.",
"details_delete_anyway": "Delete anyway"
}, },
"total_balance_view": { "total_balance_view": {
"display_in_bitcoin": "Display in Bitcoin", "display_in_bitcoin": "Display in Bitcoin",
@ -508,6 +515,10 @@
"default_label": "Multisig Vault", "default_label": "Multisig Vault",
"multisig_vault_explain": "Best security for large amounts", "multisig_vault_explain": "Best security for large amounts",
"provide_signature": "Provide signature", "provide_signature": "Provide signature",
"provide_signature_details": "Use your device and wallet where the key resides to sign this transaction",
"provide_signature_details_bluewallet": "In BlueWallet, go to the Send screen menu and select ",
"provide_signature_next_steps": "Scan or Import Signed Transaction",
"provide_signature_next_steps_details": "Once your wallet has successfully signed the transaction, scan the provided QR code or import the accompanying file, and then review all the transaction details before broadcasting it.",
"vault_key": "Vault Key {number}", "vault_key": "Vault Key {number}",
"required_keys_out_of_total": "Required keys out of the total", "required_keys_out_of_total": "Required keys out of the total",
"fee": "Fee: {number}", "fee": "Fee: {number}",

View file

@ -12,7 +12,6 @@
"storage_is_encrypted": "Tu almacenamiento está cifrado. Se requiere la contraseña para descifrarlo.", "storage_is_encrypted": "Tu almacenamiento está cifrado. Se requiere la contraseña para descifrarlo.",
"yes": "Sí", "yes": "Sí",
"no": "No", "no": "No",
"save": "Guardar",
"seed": "Semilla", "seed": "Semilla",
"success": "Completado", "success": "Completado",
"wallet_key": "Llave de la cartera" "wallet_key": "Llave de la cartera"
@ -201,13 +200,8 @@
"set_electrum_server_as_default": "¿Establecer {server} como servidor Electrum por defecto?", "set_electrum_server_as_default": "¿Establecer {server} como servidor Electrum por defecto?",
"electrum_settings_server": "Servidor Electrum", "electrum_settings_server": "Servidor Electrum",
"electrum_status": "Estado", "electrum_status": "Estado",
"electrum_clear_alert_title": "¿Limpiar historial?",
"electrum_clear_alert_message": "¿Quieres eliminar el historial de los servidores de Electrum?",
"electrum_clear_alert_cancel": "Cancelar",
"electrum_clear_alert_ok": "Ok",
"electrum_reset": "Restablecer valores predeterminados",
"electrum_unable_to_connect": "Imposible conectar a {server}. ", "electrum_unable_to_connect": "Imposible conectar a {server}. ",
"electrum_reset_to_default": "¿Estás seguro de querer reiniciar sus ajustes de Electrum por defecto? ", "electrum_reset": "Restablecer valores predeterminados",
"encrypt_decrypt": "Desencriptar almacenamiento", "encrypt_decrypt": "Desencriptar almacenamiento",
"encrypt_decrypt_q": "¿Seguro que quieres desencriptar tu almacenamiento? Al hacerlo, se podrá acceder a tus carteras sin contraseña.", "encrypt_decrypt_q": "¿Seguro que quieres desencriptar tu almacenamiento? Al hacerlo, se podrá acceder a tus carteras sin contraseña.",
"encrypt_enc_and_pass": "Encriptado y protegido mediante contraseña", "encrypt_enc_and_pass": "Encriptado y protegido mediante contraseña",
@ -327,7 +321,6 @@
"details_export_history": "Exportar el historial a CSV", "details_export_history": "Exportar el historial a CSV",
"details_master_fingerprint": "Huella dactilar maestra", "details_master_fingerprint": "Huella dactilar maestra",
"details_multisig_type": "multifirma", "details_multisig_type": "multifirma",
"details_no_cancel": "No, cancelar",
"details_show_xpub": "Mostrar el XPUB de la cartera", "details_show_xpub": "Mostrar el XPUB de la cartera",
"details_show_addresses": "Mostrar dirección", "details_show_addresses": "Mostrar dirección",
"details_title": "Cartera", "details_title": "Cartera",

View file

@ -136,6 +136,9 @@
"details_add_recc_rem_all_alert_description": "¿Estás seguro de que quieres eliminar todos los destinatarios?", "details_add_recc_rem_all_alert_description": "¿Estás seguro de que quieres eliminar todos los destinatarios?",
"details_add_rec_rem_all": "Eliminar todos los destinatarios", "details_add_rec_rem_all": "Eliminar todos los destinatarios",
"details_recipients_title": "Destinatarios", "details_recipients_title": "Destinatarios",
"details_recipient_title": "Destinatario #{number} de #{total}",
"please_complete_recipient_title": "Destinatario incompleto",
"please_complete_recipient_details": "Completa los detalles del destinatario #{number} antes de agregar un nuevo destinatario.",
"details_address": "Dirección", "details_address": "Dirección",
"details_address_field_is_not_valid": "La dirección no es válida.", "details_address_field_is_not_valid": "La dirección no es válida.",
"details_adv_fee_bump": "Permitir aumento de tarifas", "details_adv_fee_bump": "Permitir aumento de tarifas",
@ -182,6 +185,7 @@
"input_total": "Total:", "input_total": "Total:",
"permission_camera_message": "Necesitamos tu permiso para usar tu cámara", "permission_camera_message": "Necesitamos tu permiso para usar tu cámara",
"psbt_sign": "Firmar una transacción", "psbt_sign": "Firmar una transacción",
"invalid_psbt": "PSBT proporcionado no válido.",
"open_settings": "Abrir configuraciones", "open_settings": "Abrir configuraciones",
"permission_storage_denied_message": "BlueWallet no puede guardar este archivo. Por favor, abre la configuración de tu dispositivo y activa el permiso de almacenamiento.", "permission_storage_denied_message": "BlueWallet no puede guardar este archivo. Por favor, abre la configuración de tu dispositivo y activa el permiso de almacenamiento.",
"permission_storage_title": "Permiso de acceso de almacenamiento", "permission_storage_title": "Permiso de acceso de almacenamiento",
@ -239,6 +243,7 @@
"electrum_connected": "Conectado", "electrum_connected": "Conectado",
"electrum_connected_not": "No Conectado", "electrum_connected_not": "No Conectado",
"electrum_error_connect": "No se puede conectar al servidor Electrum proporcionado", "electrum_error_connect": "No se puede conectar al servidor Electrum proporcionado",
"electrum_error_connect_tor": "No se puede conectar al servidor Electrum proporcionado. Asegúrate de que la aplicación Orbot esté conectada y vuelve a intentarlo.",
"lndhub_uri": "Ej.: {example}", "lndhub_uri": "Ej.: {example}",
"electrum_host": "Ej.: {example}", "electrum_host": "Ej.: {example}",
"electrum_offline_mode": "Modo offline", "electrum_offline_mode": "Modo offline",
@ -285,6 +290,7 @@
"language_isRTL": "Es necesario reiniciar BlueWallet para que la orientación del idioma surta efecto.", "language_isRTL": "Es necesario reiniciar BlueWallet para que la orientación del idioma surta efecto.",
"license": "Licencia", "license": "Licencia",
"lightning_error_lndhub_uri": "URI de LNDhub no válido", "lightning_error_lndhub_uri": "URI de LNDhub no válido",
"lightning_error_lndhub_uri_tor": "La URL de LNDhub no es válida. Asegúrate de que la aplicación Orbot esté conectada y vuelve a intentarlo.",
"lightning_saved": "Tus cambios han sido guardados correctamente.", "lightning_saved": "Tus cambios han sido guardados correctamente.",
"lightning_settings": "Configuración de Lightning", "lightning_settings": "Configuración de Lightning",
"lightning_settings_explain": "Para conectarte a tu propio nodo LND, instala LNDhub y coloca su URL aquí en la configuración. Ten en cuenta que solo las billeteras creadas después de guardar los cambios se conectarán al LNDhub especificado.", "lightning_settings_explain": "Para conectarte a tu propio nodo LND, instala LNDhub y coloca su URL aquí en la configuración. Ten en cuenta que solo las billeteras creadas después de guardar los cambios se conectarán al LNDhub especificado.",
@ -422,7 +428,6 @@
"details_export_history": "Exportar historial a CSV", "details_export_history": "Exportar historial a CSV",
"details_master_fingerprint": "Huella Digital Maestra", "details_master_fingerprint": "Huella Digital Maestra",
"details_multisig_type": "multifirma", "details_multisig_type": "multifirma",
"details_no_cancel": "No, cancelar",
"details_show_xpub": "Mostrar el XPUB de la Billetera", "details_show_xpub": "Mostrar el XPUB de la Billetera",
"details_show_addresses": "Mostrar direcciones", "details_show_addresses": "Mostrar direcciones",
"details_title": "Billetera", "details_title": "Billetera",
@ -493,7 +498,9 @@
"identity_pubkey": "Identidad Pubkey", "identity_pubkey": "Identidad Pubkey",
"xpub_title": "XPUB de la billetera", "xpub_title": "XPUB de la billetera",
"manage_wallets_search_placeholder": "Buscar billeteras, notas", "manage_wallets_search_placeholder": "Buscar billeteras, notas",
"more_info": "Más información" "more_info": "Más información",
"details_delete_wallet_error_message": "Hubo un problema al confirmar si esta billetera se eliminó de las notificaciones, lo que podría deberse a un problema de red o a una mala conexión. Si continúas, es posible que aún recibas notificaciones de transacciones relacionadas con esta billetera, incluso después de que se elimine.",
"details_delete_anyway": "Borrar de todos modos"
}, },
"total_balance_view": { "total_balance_view": {
"display_in_bitcoin": "Mostrar en Bitcoin", "display_in_bitcoin": "Mostrar en Bitcoin",
@ -508,6 +515,10 @@
"default_label": "Bóveda Multifirma", "default_label": "Bóveda Multifirma",
"multisig_vault_explain": "La mejor seguridad para grandes cantidades", "multisig_vault_explain": "La mejor seguridad para grandes cantidades",
"provide_signature": "Proporcionar firma", "provide_signature": "Proporcionar firma",
"provide_signature_details": "Usa tu dispositivo y billetera donde reside la llave para firmar esta transacción",
"provide_signature_details_bluewallet": "En BlueWallet, ve al menú de la pantalla Enviar y selecciona",
"provide_signature_next_steps": "Escanea o importa la transacción firmada",
"provide_signature_next_steps_details": "Una vez que tu billetera haya firmado con éxito la transacción, escanea el código QR proporcionado o importa el archivo que lo acompaña, y luego revisa todos los detalles de la transacción antes de transmitirla.",
"vault_key": "Clave de la Bóveda {number}", "vault_key": "Clave de la Bóveda {number}",
"required_keys_out_of_total": "Llaves requeridas del total", "required_keys_out_of_total": "Llaves requeridas del total",
"fee": "Tarifa: {number}", "fee": "Tarifa: {number}",
@ -669,7 +680,7 @@
"notification_tx_unconfirmed": "La transacción de notificación aún no está confirmada, espera", "notification_tx_unconfirmed": "La transacción de notificación aún no está confirmada, espera",
"failed_create_notif_tx": "No se pudo crear una transacción en cadena", "failed_create_notif_tx": "No se pudo crear una transacción en cadena",
"onchain_tx_needed": "Se necesita transacción en cadena", "onchain_tx_needed": "Se necesita transacción en cadena",
"notif_tx_sent" : "Transacción de notificación enviada. Espera a que se confirme", "notif_tx_sent": "Transacción de notificación enviada. Espera a que se confirme",
"notif_tx": "Transacción de notificación", "notif_tx": "Transacción de notificación",
"not_found": "Código de pago no encontrado" "not_found": "Código de pago no encontrado"
} }

View file

@ -11,7 +11,6 @@
"storage_is_encrypted": "Sinu hoidla on krüpteeritud. Dekrüpteerimiseks on vajalik sisestada parool.", "storage_is_encrypted": "Sinu hoidla on krüpteeritud. Dekrüpteerimiseks on vajalik sisestada parool.",
"yes": "Jah", "yes": "Jah",
"no": "Ei", "no": "Ei",
"save": "Salvesta",
"seed": "Seeme", "seed": "Seeme",
"success": "Toiming õnnestus", "success": "Toiming õnnestus",
"wallet_key": "Rahakoti võti" "wallet_key": "Rahakoti võti"
@ -41,7 +40,6 @@
"create_to": "Sihtkoht" "create_to": "Sihtkoht"
}, },
"settings": { "settings": {
"electrum_clear_alert_cancel": "Katkesta",
"save": "Salvesta" "save": "Salvesta"
}, },
"wallets": { "wallets": {

View file

@ -12,7 +12,6 @@
"storage_is_encrypted": "فضای ذخیره‌سازی شما رمزگذاری شده است. برای رمزگشایی آن به گذرواژه نیاز است.", "storage_is_encrypted": "فضای ذخیره‌سازی شما رمزگذاری شده است. برای رمزگشایی آن به گذرواژه نیاز است.",
"yes": "بله", "yes": "بله",
"no": "خیر", "no": "خیر",
"save": "ذخیره",
"seed": "سید", "seed": "سید",
"success": "موفقیت‌آمیز بود", "success": "موفقیت‌آمیز بود",
"wallet_key": "کلید کیف پول", "wallet_key": "کلید کیف پول",
@ -210,13 +209,8 @@
"set_electrum_server_as_default": "آیا {server} به‌عنوان سرور پیش‌فرض الکترام تعیین شود؟", "set_electrum_server_as_default": "آیا {server} به‌عنوان سرور پیش‌فرض الکترام تعیین شود؟",
"electrum_settings_server": "سرور الکترام", "electrum_settings_server": "سرور الکترام",
"electrum_status": "وضعیت", "electrum_status": "وضعیت",
"electrum_clear_alert_title": "تاریخچه پاک شود؟",
"electrum_clear_alert_message": "آیا می‌خواهید تاریخچهٔ سرورهای الکترام را پاک کنید؟",
"electrum_clear_alert_cancel": "لغو",
"electrum_clear_alert_ok": "بله",
"electrum_reset": "بازنشانی به پیش‌فرض",
"electrum_unable_to_connect": "ناموفق در اتصال به {server}", "electrum_unable_to_connect": "ناموفق در اتصال به {server}",
"electrum_reset_to_default": "آیا از بازنشانی تنظیمات الکترام به حالت پیش‌فرض اطمینان دارید؟", "electrum_reset": "بازنشانی به پیش‌فرض",
"encrypt_decrypt": "رمزگشایی فضای ذخیره‌سازی", "encrypt_decrypt": "رمزگشایی فضای ذخیره‌سازی",
"encrypt_decrypt_q": "آیا از رمزگشایی فضای ذخیره‌سازی خود اطمینان دارید؟ این کار اجازه می‌دهد تا کیف پول‌های شما بدون گذرواژه قابل‌دسترسی باشند.", "encrypt_decrypt_q": "آیا از رمزگشایی فضای ذخیره‌سازی خود اطمینان دارید؟ این کار اجازه می‌دهد تا کیف پول‌های شما بدون گذرواژه قابل‌دسترسی باشند.",
"encrypt_enc_and_pass": "رمزگذاری و محافظت با گذرواژه", "encrypt_enc_and_pass": "رمزگذاری و محافظت با گذرواژه",
@ -338,7 +332,6 @@
"details_export_history": "گرفتن خروجی تاریخچه به فرمت CSV", "details_export_history": "گرفتن خروجی تاریخچه به فرمت CSV",
"details_master_fingerprint": "اثر انگشت اصلی", "details_master_fingerprint": "اثر انگشت اصلی",
"details_multisig_type": "چندامضایی", "details_multisig_type": "چندامضایی",
"details_no_cancel": "خیر، لغو کن",
"details_show_xpub": "نمایش XPUB کیف پول", "details_show_xpub": "نمایش XPUB کیف پول",
"details_show_addresses": "نمایش آدرس‌ها", "details_show_addresses": "نمایش آدرس‌ها",
"details_title": "کیف پول", "details_title": "کیف پول",

View file

@ -13,7 +13,6 @@
"storage_is_encrypted": "Tallennustilasi on salattu. Sen purkamiseksi vaaditaan salasana", "storage_is_encrypted": "Tallennustilasi on salattu. Sen purkamiseksi vaaditaan salasana",
"yes": "Kyllä", "yes": "Kyllä",
"no": "Ei", "no": "Ei",
"save": "Tallenna",
"seed": "Siemen", "seed": "Siemen",
"success": "Onnistui", "success": "Onnistui",
"wallet_key": "Lompakkoavain", "wallet_key": "Lompakkoavain",
@ -177,7 +176,6 @@
"outdated_rate": "Vaihtokurssi päivitettiin viimeksi: {date}", "outdated_rate": "Vaihtokurssi päivitettiin viimeksi: {date}",
"psbt_tx_open": "Avaa allekirjoitettu siirtotapahtuma", "psbt_tx_open": "Avaa allekirjoitettu siirtotapahtuma",
"psbt_tx_scan": "Skannaa allekirjoitettu siirtotapahtuma", "psbt_tx_scan": "Skannaa allekirjoitettu siirtotapahtuma",
"qr_error_no_qrcode": "Kuvasta ei löytynyt QR-koodia. Varmista että kuva sisältää ainoastaan QR-koodin eikä muita tietoja kuten tekstia tai nappeja.",
"reset_amount": "Nollaa määrä", "reset_amount": "Nollaa määrä",
"reset_amount_confirm": "Haluaisitko nollata määrän?", "reset_amount_confirm": "Haluaisitko nollata määrän?",
"success_done": "Valmis", "success_done": "Valmis",
@ -223,13 +221,8 @@
"set_electrum_server_as_default": "Asetetaanko {server} oletus Electrum-palvelimeksi?", "set_electrum_server_as_default": "Asetetaanko {server} oletus Electrum-palvelimeksi?",
"electrum_settings_server": "Electrum-palvelin", "electrum_settings_server": "Electrum-palvelin",
"electrum_status": "Tila", "electrum_status": "Tila",
"electrum_clear_alert_title": "Tyhjennä historia?",
"electrum_clear_alert_message": "Haluatko tyhjentää Electrum-palvelinten historian?",
"electrum_clear_alert_cancel": "Peruuta",
"electrum_clear_alert_ok": "Ok",
"electrum_reset": "Palauta oletusasetuksiin",
"electrum_unable_to_connect": " Ei saada yhteyttä {server}. ", "electrum_unable_to_connect": " Ei saada yhteyttä {server}. ",
"electrum_reset_to_default": "Haluatko varmasti palauttaa Electrumin asetukset oletusarvoihin? ", "electrum_reset": "Palauta oletusasetuksiin",
"encrypt_decrypt": "Pura tallennustilan salaus", "encrypt_decrypt": "Pura tallennustilan salaus",
"encrypt_decrypt_q": "Haluatko varmasti purkaa tallennustilan salauksen? Tämä mahdollistaa lompakkoihisi pääsyn ilman salasanaa.", "encrypt_decrypt_q": "Haluatko varmasti purkaa tallennustilan salauksen? Tämä mahdollistaa lompakkoihisi pääsyn ilman salasanaa.",
"encrypt_enc_and_pass": "Salattu ja Salasanalla suojattu", "encrypt_enc_and_pass": "Salattu ja Salasanalla suojattu",
@ -357,7 +350,6 @@
"details_export_history": "Vie historia CSV:ksi", "details_export_history": "Vie historia CSV:ksi",
"details_master_fingerprint": "Pää sormenjälki", "details_master_fingerprint": "Pää sormenjälki",
"details_multisig_type": "multisig", "details_multisig_type": "multisig",
"details_no_cancel": "Ei, peruuta",
"details_show_xpub": "Näytä lompakon XPUB", "details_show_xpub": "Näytä lompakon XPUB",
"details_show_addresses": "Näytä osoitteet", "details_show_addresses": "Näytä osoitteet",
"details_title": "Lompakko", "details_title": "Lompakko",

View file

@ -10,12 +10,10 @@
"never": "Jamais", "never": "Jamais",
"of": "{number} sur {total}", "of": "{number} sur {total}",
"ok": "OK", "ok": "OK",
"customize": "Personnaliser",
"enter_url": "Entrer une URL", "enter_url": "Entrer une URL",
"storage_is_encrypted": "L'espace de stockage est chiffré. le mot de passe est requis pour le déchiffrer.", "storage_is_encrypted": "L'espace de stockage est chiffré. le mot de passe est requis pour le déchiffrer.",
"yes": "Oui", "yes": "Oui",
"no": "Non", "no": "Non",
"save": "Enregistrer",
"seed": "Graine", "seed": "Graine",
"success": "Succès", "success": "Succès",
"wallet_key": "Clé du portefeuille", "wallet_key": "Clé du portefeuille",
@ -255,8 +253,9 @@
"electrum_preferred_server_description": "Saisissez le serveur que vous souhaitez que votre portefeuille utilise pour toutes les activités Bitcoin. Une fois défini, votre portefeuille utilisera exclusivement ce serveur pour vérifier les soldes, envoyer des transactions et récupérer les données du réseau. Assurez-vous de faire confiance à ce serveur avant de le configurer. ", "electrum_preferred_server_description": "Saisissez le serveur que vous souhaitez que votre portefeuille utilise pour toutes les activités Bitcoin. Une fois défini, votre portefeuille utilisera exclusivement ce serveur pour vérifier les soldes, envoyer des transactions et récupérer les données du réseau. Assurez-vous de faire confiance à ce serveur avant de le configurer. ",
"electrum_unable_to_connect": "Impossible de se connecter à {server}.", "electrum_unable_to_connect": "Impossible de se connecter à {server}.",
"electrum_history": "Historique", "electrum_history": "Historique",
"electrum_reset_to_default": "Cela permettra à BlueWallet de choisir au hasard un serveur dans la liste et l'historique suggérés. L'historique de votre serveur restera inchangé. ", "electrum_reset_to_default": "Cela permettra à BlueWallet de choisir au hasard un serveur dans la liste des serveurs.",
"electrum_reset": "Réinitialiser au valeurs par défaut", "electrum_reset": "Réinitialiser au valeurs par défaut",
"electrum_reset_to_default_and_clear_history": "Réinitialiser les paramètres par défaut et effacer l'historique",
"encrypt_decrypt": "Déchiffrer le stockage", "encrypt_decrypt": "Déchiffrer le stockage",
"encrypt_decrypt_q": "Etes-vous sûr de vouloir déchiffrer le stockage ? L'accès à vos portefeuilles pourra alors se faire sans mot de passe.", "encrypt_decrypt_q": "Etes-vous sûr de vouloir déchiffrer le stockage ? L'accès à vos portefeuilles pourra alors se faire sans mot de passe.",
"encrypt_enc_and_pass": "Chiffré et protégé par un mot de passe", "encrypt_enc_and_pass": "Chiffré et protégé par un mot de passe",
@ -403,7 +402,6 @@
"add_wallet_name": "nom", "add_wallet_name": "nom",
"add_wallet_type": "type", "add_wallet_type": "type",
"add_wallet_seed_length": "Longueur de graîne", "add_wallet_seed_length": "Longueur de graîne",
"add_wallet_seed_length_message": "Choisissez la longueur de la phrase de départ que vous souhaitez utiliser pour ce portefeuille.",
"add_wallet_seed_length_12": "12 mots", "add_wallet_seed_length_12": "12 mots",
"add_wallet_seed_length_24": "24 mots", "add_wallet_seed_length_24": "24 mots",
"clipboard_bitcoin": "Vous avez une adresse bitcoin dans votre presse-papier. Voulez vous l'utiliser pour une transaction ?", "clipboard_bitcoin": "Vous avez une adresse bitcoin dans votre presse-papier. Voulez vous l'utiliser pour une transaction ?",
@ -423,7 +421,6 @@
"details_export_history": "Exporter l'historique au format CSV", "details_export_history": "Exporter l'historique au format CSV",
"details_master_fingerprint": "Empreinte maitresse", "details_master_fingerprint": "Empreinte maitresse",
"details_multisig_type": "multisig", "details_multisig_type": "multisig",
"details_no_cancel": "Non, annuler",
"details_show_xpub": "Afficher XPUB du portefeuille", "details_show_xpub": "Afficher XPUB du portefeuille",
"details_show_addresses": "Montrer les adresses", "details_show_addresses": "Montrer les adresses",
"details_title": "Portefeuille", "details_title": "Portefeuille",
@ -670,7 +667,7 @@
"notification_tx_unconfirmed": "La transaction de notification n'est pas encore confirmée, veuillez patienter", "notification_tx_unconfirmed": "La transaction de notification n'est pas encore confirmée, veuillez patienter",
"failed_create_notif_tx": "Échec de la création d'une transaction on-chain", "failed_create_notif_tx": "Échec de la création d'une transaction on-chain",
"onchain_tx_needed": "Transaction on-chain nécessaire", "onchain_tx_needed": "Transaction on-chain nécessaire",
"notif_tx_sent" : "Transaction de notification envoyée. Veuillez attendre qu'elle soit confirmée", "notif_tx_sent": "Transaction de notification envoyée. Veuillez attendre qu'elle soit confirmée",
"notif_tx": "Transaction de notification ", "notif_tx": "Transaction de notification ",
"not_found": "Code de paiement introuvable" "not_found": "Code de paiement introuvable"
} }

View file

@ -10,12 +10,10 @@
"never": "אף פעם", "never": "אף פעם",
"of": "{number} מתוך {total}", "of": "{number} מתוך {total}",
"ok": "אישור", "ok": "אישור",
"customize": "התאמה אישית",
"enter_url": "הכנסת URL", "enter_url": "הכנסת URL",
"storage_is_encrypted": "האחסון שלך מוצפן. נדרשת סיסמה לפענוח שלו.", "storage_is_encrypted": "האחסון שלך מוצפן. נדרשת סיסמה לפענוח שלו.",
"yes": "כן", "yes": "כן",
"no": "לא", "no": "לא",
"save": "שמירה",
"seed": "גרעין", "seed": "גרעין",
"success": "הצלחה", "success": "הצלחה",
"wallet_key": "מפתח ארנק", "wallet_key": "מפתח ארנק",
@ -28,6 +26,8 @@
"enter_amount": "הכנסת סכום", "enter_amount": "הכנסת סכום",
"qr_custom_input_button": "הקישו 10 פעמים כדי להכניס קלט מותאם", "qr_custom_input_button": "הקישו 10 פעמים כדי להכניס קלט מותאם",
"unlock": "פתיחה", "unlock": "פתיחה",
"port": "פתחה",
"ssl_port": "פתחת SSL",
"suggested": "מוצע" "suggested": "מוצע"
}, },
"azteco": { "azteco": {
@ -72,6 +72,7 @@
"open_direct_channel": "פתח ערוץ ישיר עם צומת זה:", "open_direct_channel": "פתח ערוץ ישיר עם צומת זה:",
"please_pay_between_and": "אנא שלמו בין {min} לבין {max}", "please_pay_between_and": "אנא שלמו בין {min} לבין {max}",
"please_pay": "אנא שלמו", "please_pay": "אנא שלמו",
"date_time": "תאריך ושעה",
"wasnt_paid_and_expired": "חשבונית זו לא שולמה ופגה" "wasnt_paid_and_expired": "חשבונית זו לא שולמה ופגה"
}, },
"plausibledeniability": { "plausibledeniability": {
@ -103,8 +104,7 @@
"maxSatsFull": "סכום מקסימלי הינו {max} sats או {currency}", "maxSatsFull": "סכום מקסימלי הינו {max} sats או {currency}",
"minSats": "סכום מינימלי הינו {min} sats", "minSats": "סכום מינימלי הינו {min} sats",
"minSatsFull": "סכום מינימלי הינו {min} sats או {currency}", "minSatsFull": "סכום מינימלי הינו {min} sats או {currency}",
"qrcode_for_the_address": "קוד QR לכתובת", "qrcode_for_the_address": "קוד QR לכתובת"
"bip47_explanation": "קודי תשלום הם כתובת אוניברסלית שמונעת חשיפה של כתובות הארנק שלך. לא כל השירותים יתמכו בהם."
}, },
"send": { "send": {
"provided_address_is_invoice": "נראה שכתובת זו היא חשבונית ברק. אנא עברו לארנק הברק שלכם כדי לבצע תשלום עבור חשבונית זו.", "provided_address_is_invoice": "נראה שכתובת זו היא חשבונית ברק. אנא עברו לארנק הברק שלכם כדי לבצע תשלום עבור חשבונית זו.",
@ -188,7 +188,6 @@
"outdated_rate": "תעריף עודכן לאחרונה: {date}", "outdated_rate": "תעריף עודכן לאחרונה: {date}",
"psbt_tx_open": "פתחו פעולה חתומה", "psbt_tx_open": "פתחו פעולה חתומה",
"psbt_tx_scan": "סרקו פעולה חתומה", "psbt_tx_scan": "סרקו פעולה חתומה",
"qr_error_no_qrcode": "לא הצלחנו למצוא קוד QR בתמונה הנבחרת. אנא ודאו כי התמונה מכילה רק קוד QR, ולא תוכן נוסף כמו טקסט, או כפתורים.",
"reset_amount": "איפוס סכום", "reset_amount": "איפוס סכום",
"reset_amount_confirm": "האם ברצונך לאפס את הסכום?", "reset_amount_confirm": "האם ברצונך לאפס את הסכום?",
"success_done": "בוצע", "success_done": "בוצע",
@ -246,15 +245,9 @@
"electrum_settings_server": "שרת אלקטרום", "electrum_settings_server": "שרת אלקטרום",
"electrum_status": "מצב", "electrum_status": "מצב",
"electrum_preferred_server": "שרת מועדף", "electrum_preferred_server": "שרת מועדף",
"electrum_clear_alert_title": "ניקוי היסטוריה?",
"electrum_clear_alert_message": "האם ברצונך לנקות היסטורית שרתי אלקטרום?",
"electrum_clear_alert_cancel": "ביטול",
"electrum_clear_alert_ok": "אישור",
"electrum_reset": "איפוס ברירת מחדל",
"electrum_unable_to_connect": "לא מסוגל להתחבר לשרת {server}.", "electrum_unable_to_connect": "לא מסוגל להתחבר לשרת {server}.",
"electrum_history": "היסטוריה", "electrum_history": "היסטוריה",
"electrum_reset_to_default": "האם אתם בטוחים שברצונכם לשחזר את הגדרות האקלטרום שלכם לברירת מחדל?", "electrum_reset": "איפוס ברירת מחדל",
"electrum_clear": "ניקוי היסטוריה",
"encrypt_decrypt": "פתיחת אחסון מוצפן", "encrypt_decrypt": "פתיחת אחסון מוצפן",
"encrypt_decrypt_q": "האם לפענח אחסון מוצפן? זה יאפשר לגשת לארנקים שלך ללא סיסמה.", "encrypt_decrypt_q": "האם לפענח אחסון מוצפן? זה יאפשר לגשת לארנקים שלך ללא סיסמה.",
"encrypt_enc_and_pass": "מוצפן ומוגן על ידי סיסמה", "encrypt_enc_and_pass": "מוצפן ומוגן על ידי סיסמה",
@ -266,6 +259,7 @@
"encrypt_title": "אבטחה", "encrypt_title": "אבטחה",
"encrypt_tstorage": "אחסון", "encrypt_tstorage": "אחסון",
"encrypt_use": "השתמש {type}", "encrypt_use": "השתמש {type}",
"set_as_preferred": "הגדרה כמועדף",
"encrypted_feature_disabled": "לא ניתן להשתמש בתכונה זאת עם הצפנת אחסון מופעלת.", "encrypted_feature_disabled": "לא ניתן להשתמש בתכונה זאת עם הצפנת אחסון מופעלת.",
"encrypt_use_expl": "{type} ישמש לאמת את זהותך לפני ביצוע פעולה, פתיחה, יצוא או מחיקה של ארנק. {type} אינו ישמש לפתיחת אחסון מוצפן.", "encrypt_use_expl": "{type} ישמש לאמת את זהותך לפני ביצוע פעולה, פתיחה, יצוא או מחיקה של ארנק. {type} אינו ישמש לפתיחת אחסון מוצפן.",
"biometrics_fail": "אם {type} לא מאופשר, או נכשל בפתיחה, תוכלו להשתמש בסיסמת המכשיר שלכם בתור חלופה.", "biometrics_fail": "אם {type} לא מאופשר, או נכשל בפתיחה, תוכלו להשתמש בסיסמת המכשיר שלכם בתור חלופה.",
@ -391,7 +385,6 @@
"add_wallet_name": "שם", "add_wallet_name": "שם",
"add_wallet_type": "סוג", "add_wallet_type": "סוג",
"add_wallet_seed_length": "אורך גרעין", "add_wallet_seed_length": "אורך גרעין",
"add_wallet_seed_length_message": "בחרו את אורך צירוף הגרעין שברצונכם להשתמש בשביל ארנק זה.",
"add_wallet_seed_length_12": "12 מילים", "add_wallet_seed_length_12": "12 מילים",
"add_wallet_seed_length_24": "24 מילים", "add_wallet_seed_length_24": "24 מילים",
"clipboard_bitcoin": "ישנה כתובת ביטקוין בלוח. האם תרצו להשתמש בה בשביל העברה?", "clipboard_bitcoin": "ישנה כתובת ביטקוין בלוח. האם תרצו להשתמש בה בשביל העברה?",
@ -411,7 +404,6 @@
"details_export_history": "יצוא היסטוריה ל- CSV", "details_export_history": "יצוא היסטוריה ל- CSV",
"details_master_fingerprint": "טביעת אצבע ראשית", "details_master_fingerprint": "טביעת אצבע ראשית",
"details_multisig_type": "רב-חתימות", "details_multisig_type": "רב-חתימות",
"details_no_cancel": "לא, בטל",
"details_show_xpub": "הצגת מפתח צפייה של הארנק", "details_show_xpub": "הצגת מפתח צפייה של הארנק",
"details_show_addresses": "הצגת כתובות", "details_show_addresses": "הצגת כתובות",
"details_title": "ארנק", "details_title": "ארנק",
@ -469,6 +461,10 @@
"select_wallet": "בחירת ארנק", "select_wallet": "בחירת ארנק",
"xpub_copiedToClipboard": "הועתק ללוח.", "xpub_copiedToClipboard": "הועתק ללוח.",
"pull_to_refresh": "משכו כדי לרענן", "pull_to_refresh": "משכו כדי לרענן",
"warning_do_not_disclose": "לעולם אל תשתפו את המידע למטה",
"write_down_header": "יצירת גיבוי ידני",
"wallet_type_this": "סוג ארנק זה הוא {type}.",
"share_number": "שיתוף {number}",
"add_ln_wallet_first": "עלייך להוסיף ארנק ברק קודם.", "add_ln_wallet_first": "עלייך להוסיף ארנק ברק קודם.",
"identity_pubkey": "מפתח זהות ציבורי", "identity_pubkey": "מפתח זהות ציבורי",
"xpub_title": "מפתח צפייה של הארנק", "xpub_title": "מפתח צפייה של הארנק",
@ -629,6 +625,7 @@
"bip47": { "bip47": {
"payment_code": "קוד תשלום", "payment_code": "קוד תשלום",
"contacts": "אנשי קשר", "contacts": "אנשי קשר",
"bip47_explain": "קוד רב פעמי ובר שיתוף",
"purpose": "קוד רב-פעמי ובר-שיתוף (BIP47)", "purpose": "קוד רב-פעמי ובר-שיתוף (BIP47)",
"pay_this_contact": "תשלום לאיש קשר זה", "pay_this_contact": "תשלום לאיש קשר זה",
"rename_contact": "שינוי שם איש קשר", "rename_contact": "שינוי שם איש קשר",

View file

@ -9,7 +9,6 @@
"storage_is_encrypted": "Vaš spremnik je kriptiran. Za dekripcoju je potrebna lozinka.", "storage_is_encrypted": "Vaš spremnik je kriptiran. Za dekripcoju je potrebna lozinka.",
"yes": "Da", "yes": "Da",
"no": "Ne", "no": "Ne",
"save": "Spremi",
"seed": "Izvor", "seed": "Izvor",
"success": "Uspjeh" "success": "Uspjeh"
}, },
@ -68,7 +67,6 @@
"settings": { "settings": {
"about": "Informacije", "about": "Informacije",
"currency": "Valuta", "currency": "Valuta",
"electrum_clear_alert_cancel": "Otkaži",
"header": "Postavke", "header": "Postavke",
"language": "Jezik", "language": "Jezik",
"lightning_settings": "Lightning postavke", "lightning_settings": "Lightning postavke",
@ -95,7 +93,6 @@
"details_are_you_sure": "Jesi li ziher?", "details_are_you_sure": "Jesi li ziher?",
"details_delete": "Obriši", "details_delete": "Obriši",
"details_export_backup": "Izvoz / bekap", "details_export_backup": "Izvoz / bekap",
"details_no_cancel": "Ne, otiaži",
"details_show_xpub": "Prikaži voletov XPUB", "details_show_xpub": "Prikaži voletov XPUB",
"details_title": "Volet", "details_title": "Volet",
"wallets": "Voleti", "wallets": "Voleti",

View file

@ -12,7 +12,6 @@
"storage_is_encrypted": "A Tárhely titkosítva. Jelszó szükséges a dekódoláshoz", "storage_is_encrypted": "A Tárhely titkosítva. Jelszó szükséges a dekódoláshoz",
"yes": "Igen", "yes": "Igen",
"no": "Nem", "no": "Nem",
"save": "Mentés",
"seed": "jelszó sorozat", "seed": "jelszó sorozat",
"success": "Sikeres", "success": "Sikeres",
"wallet_key": "Tárca kulcs", "wallet_key": "Tárca kulcs",
@ -162,7 +161,6 @@
"outdated_rate": "A ráta utoljára frissítve: {date}", "outdated_rate": "A ráta utoljára frissítve: {date}",
"psbt_tx_open": "Aláírt tranzakció megnyitása", "psbt_tx_open": "Aláírt tranzakció megnyitása",
"psbt_tx_scan": "Aláírt tranzakció szkennelése", "psbt_tx_scan": "Aláírt tranzakció szkennelése",
"qr_error_no_qrcode": "Nem találtunk QR kódot a kiválasztott képen. Győződjön meg arról, hogy a kép csak QR kódot tartalmaz, és nem tartalmaz további tartalmat, például szöveget vagy gombokat.",
"reset_amount": "Összeg Visszaállítása", "reset_amount": "Összeg Visszaállítása",
"reset_amount_confirm": "Valóban visszaállítja az összeget?", "reset_amount_confirm": "Valóban visszaállítja az összeget?",
"success_done": "Kész!", "success_done": "Kész!",
@ -206,13 +204,8 @@
"set_electrum_server_as_default": "{server} bállítása alapértelmezett Electrum szerverként?", "set_electrum_server_as_default": "{server} bállítása alapértelmezett Electrum szerverként?",
"electrum_settings_server": "Electrum Szerver", "electrum_settings_server": "Electrum Szerver",
"electrum_status": "Állapot", "electrum_status": "Állapot",
"electrum_clear_alert_title": "Előzmények törlése?",
"electrum_clear_alert_message": "Kiszeretné törölni az Electrum Szerver előzményeket?",
"electrum_clear_alert_cancel": "Mégse",
"electrum_clear_alert_ok": "OK",
"electrum_reset": "alapértelmezett beállítások visszaállítása",
"electrum_unable_to_connect": "Nincs kapcsolat {server} -hez.", "electrum_unable_to_connect": "Nincs kapcsolat {server} -hez.",
"electrum_reset_to_default": "Biztosan vissza akarja alapértelmezettre állitani az Electrum szerver beállításait?", "electrum_reset": "alapértelmezett beállítások visszaállítása",
"encrypt_decrypt": "Háttértár titkosításának feloldása", "encrypt_decrypt": "Háttértár titkosításának feloldása",
"encrypt_decrypt_q": "Biztosan feloldod a háttértár titkosítását? Ez lehetővé teszi a tárca jelszó nélküli használatát. ", "encrypt_decrypt_q": "Biztosan feloldod a háttértár titkosítását? Ez lehetővé teszi a tárca jelszó nélküli használatát. ",
"encrypt_enc_and_pass": "Titkosítva és jelszóval védve", "encrypt_enc_and_pass": "Titkosítva és jelszóval védve",
@ -333,7 +326,6 @@
"details_export_backup": "Exportálás / Biztonsági mentés", "details_export_backup": "Exportálás / Biztonsági mentés",
"details_master_fingerprint": "Mester ujjlenyomat ", "details_master_fingerprint": "Mester ujjlenyomat ",
"details_multisig_type": "multisig", "details_multisig_type": "multisig",
"details_no_cancel": "Nem, megszakít",
"details_show_xpub": "Mutasd a tárca XPUB kulcsát", "details_show_xpub": "Mutasd a tárca XPUB kulcsát",
"details_show_addresses": "Cím mutatása", "details_show_addresses": "Cím mutatása",
"details_title": "Tárca", "details_title": "Tárca",

View file

@ -12,7 +12,6 @@
"storage_is_encrypted": "Ruang penyimpanan terenkripsi. Masukkan kata sandi untuk decript:", "storage_is_encrypted": "Ruang penyimpanan terenkripsi. Masukkan kata sandi untuk decript:",
"yes": "Ya", "yes": "Ya",
"no": "Tidak", "no": "Tidak",
"save": "Simpan",
"seed": "Benih", "seed": "Benih",
"success": "Sukses", "success": "Sukses",
"wallet_key": "Kunci Dompet", "wallet_key": "Kunci Dompet",
@ -200,13 +199,8 @@
"use_ssl": "Gunakan SSL", "use_ssl": "Gunakan SSL",
"electrum_settings_server": "Server Electrum", "electrum_settings_server": "Server Electrum",
"electrum_status": "Status", "electrum_status": "Status",
"electrum_clear_alert_title": "Hapus riwayat?",
"electrum_clear_alert_message": "Apakah Anda ingin menghapus riwayat server Electrum?",
"electrum_clear_alert_cancel": "Batalkan",
"electrum_clear_alert_ok": "OK",
"electrum_reset": "Atur ulang ke bawaan",
"electrum_unable_to_connect": "Tidak bisa mengubungkan ke {server}.", "electrum_unable_to_connect": "Tidak bisa mengubungkan ke {server}.",
"electrum_reset_to_default": "Apakah Anda yakin ingin mengatur ulang Electrum Anda ke pengaturan bawaan?", "electrum_reset": "Atur ulang ke bawaan",
"encrypt_enc_and_pass": "Dienkripsi dan kata sandi dilindungi", "encrypt_enc_and_pass": "Dienkripsi dan kata sandi dilindungi",
"encrypt_title": "Keamanan", "encrypt_title": "Keamanan",
"encrypt_tstorage": "Penyimpanan", "encrypt_tstorage": "Penyimpanan",
@ -284,7 +278,6 @@
"details_export_backup": "Ekspor / backup", "details_export_backup": "Ekspor / backup",
"details_export_history": "Ekspor riwayat ke CSV", "details_export_history": "Ekspor riwayat ke CSV",
"details_master_fingerprint": "Sidik Jari Utama", "details_master_fingerprint": "Sidik Jari Utama",
"details_no_cancel": "Tidak, batalkan",
"details_show_xpub": "Tampilkan XPUB dompet", "details_show_xpub": "Tampilkan XPUB dompet",
"details_show_addresses": "Tunjukkan alamat", "details_show_addresses": "Tunjukkan alamat",
"details_title": "Dompet", "details_title": "Dompet",

View file

@ -12,7 +12,6 @@
"storage_is_encrypted": "Il tuo archivio è criptato. È necessaria una password per decriptarlo.", "storage_is_encrypted": "Il tuo archivio è criptato. È necessaria una password per decriptarlo.",
"yes": "Sì", "yes": "Sì",
"no": "No", "no": "No",
"save": "Salva",
"seed": "Seed", "seed": "Seed",
"success": "Operazione avvenuta con successo", "success": "Operazione avvenuta con successo",
"wallet_key": "Chiave del portafoglio", "wallet_key": "Chiave del portafoglio",
@ -207,13 +206,8 @@
"set_electrum_server_as_default": "Impostare {server} come server Electrum predefinito?", "set_electrum_server_as_default": "Impostare {server} come server Electrum predefinito?",
"electrum_settings_server": "Server Electrum", "electrum_settings_server": "Server Electrum",
"electrum_status": "Stato", "electrum_status": "Stato",
"electrum_clear_alert_title": "Cancella la cronologia?",
"electrum_clear_alert_message": "Desideri eliminare la cronologia dei server Electrum?",
"electrum_clear_alert_cancel": "Annulla",
"electrum_clear_alert_ok": "Ok",
"electrum_reset": "Ripristino delle impostazioni predefinite",
"electrum_unable_to_connect": "Impossibile collegarsi a {server}.", "electrum_unable_to_connect": "Impossibile collegarsi a {server}.",
"electrum_reset_to_default": "Desideri veramente ripristinare le impostazioni Electrum predefinite?", "electrum_reset": "Ripristino delle impostazioni predefinite",
"encrypt_decrypt": "Decripta l'archivio", "encrypt_decrypt": "Decripta l'archivio",
"encrypt_decrypt_q": "Desideri veramente decriptare il tuo archivio? Quest'azione avrà come conseguenza di permettere l'accesso ai portafogli senza una password.", "encrypt_decrypt_q": "Desideri veramente decriptare il tuo archivio? Quest'azione avrà come conseguenza di permettere l'accesso ai portafogli senza una password.",
"encrypt_enc_and_pass": "Criptato e protetto da password", "encrypt_enc_and_pass": "Criptato e protetto da password",
@ -333,7 +327,6 @@
"details_export_history": "Esporta la cronologia in un file CSV", "details_export_history": "Esporta la cronologia in un file CSV",
"details_master_fingerprint": "Master Fingerprint", "details_master_fingerprint": "Master Fingerprint",
"details_multisig_type": "multisig", "details_multisig_type": "multisig",
"details_no_cancel": "No, annulla",
"details_show_xpub": "Mostra XPUB del portafoglio", "details_show_xpub": "Mostra XPUB del portafoglio",
"details_show_addresses": "Mostra indirizzi", "details_show_addresses": "Mostra indirizzi",
"details_title": "Portafoglio", "details_title": "Portafoglio",

View file

@ -10,12 +10,11 @@
"never": "データなし", "never": "データなし",
"of": "{number} / {total}", "of": "{number} / {total}",
"ok": "OK", "ok": "OK",
"customize": "カスタマイズ",
"enter_url": "URLを入力", "enter_url": "URLを入力",
"storage_is_encrypted": "ウォレットは暗号化されています。復号にはパスワードが必要です。", "storage_is_encrypted": "ウォレットは暗号化されています。復号にはパスワードが必要です。",
"yes": "はい", "yes": "はい",
"no": "いいえ", "no": "いいえ",
"save": "保存", "save": "保存...",
"seed": "シード", "seed": "シード",
"success": "成功", "success": "成功",
"wallet_key": "ウォレットキー", "wallet_key": "ウォレットキー",
@ -28,6 +27,8 @@
"enter_amount": "額を入力", "enter_amount": "額を入力",
"qr_custom_input_button": "10回タップしてカスタム入力", "qr_custom_input_button": "10回タップしてカスタム入力",
"unlock": "ロック解除", "unlock": "ロック解除",
"port": "ポート",
"ssl_port": "SSLポート",
"suggested": "サジェスト" "suggested": "サジェスト"
}, },
"azteco": { "azteco": {
@ -74,6 +75,7 @@
"please_pay": "取引額", "please_pay": "取引額",
"preimage": "プリイメージ", "preimage": "プリイメージ",
"sats": "sats", "sats": "sats",
"date_time": "日時",
"wasnt_paid_and_expired": "この請求書は支払いが行われなかったため無効になりました" "wasnt_paid_and_expired": "この請求書は支払いが行われなかったため無効になりました"
}, },
"plausibledeniability": { "plausibledeniability": {
@ -134,6 +136,9 @@
"details_add_recc_rem_all_alert_description": "本当に宛先をすべて削除しますか?", "details_add_recc_rem_all_alert_description": "本当に宛先をすべて削除しますか?",
"details_add_rec_rem_all": "宛先をすべて削除", "details_add_rec_rem_all": "宛先をすべて削除",
"details_recipients_title": "宛先", "details_recipients_title": "宛先",
"details_recipient_title": "宛先#{number}/#{total}",
"please_complete_recipient_title": "宛先が不完全",
"please_complete_recipient_details": "新しい宛先を追加する前に、宛先#{number}の詳細を入力してください。",
"details_address": "アドレス", "details_address": "アドレス",
"details_address_field_is_not_valid": "アドレス欄が正しくありません", "details_address_field_is_not_valid": "アドレス欄が正しくありません",
"details_adv_fee_bump": "費用のバンプ(増加)を許可", "details_adv_fee_bump": "費用のバンプ(増加)を許可",
@ -180,6 +185,7 @@
"input_total": "合計:", "input_total": "合計:",
"permission_camera_message": "カメラを使用するのに許可が必要です", "permission_camera_message": "カメラを使用するのに許可が必要です",
"psbt_sign": "トランザクションに署名する", "psbt_sign": "トランザクションに署名する",
"invalid_psbt": "無効なPSBTが入力されました。",
"open_settings": "設定を開く", "open_settings": "設定を開く",
"permission_storage_denied_message": "BlueWalletはファイルを保存できませんでした。デバイスの設定を開いてストレージのパーミッションを有効にしてください。", "permission_storage_denied_message": "BlueWalletはファイルを保存できませんでした。デバイスの設定を開いてストレージのパーミッションを有効にしてください。",
"permission_storage_title": "ストレージアクセス許可", "permission_storage_title": "ストレージアクセス許可",
@ -190,7 +196,7 @@
"outdated_rate": "レートの最終更新:{date}", "outdated_rate": "レートの最終更新:{date}",
"psbt_tx_open": "署名トランザクションを開く", "psbt_tx_open": "署名トランザクションを開く",
"psbt_tx_scan": "署名トランザクションをスキャン", "psbt_tx_scan": "署名トランザクションをスキャン",
"qr_error_no_qrcode": "選択した画像からQRコードが見つかりませんでした。画像がQRコードのみを含み、テキストやボタンなど別の内容を含まないようにしてください。", "qr_error_no_qrcode": "選択した画像から有効なQRコードが見つかりませんでした。画像がQRコードのみを含み、テキストやボタンなど別の内容を含まないようにしてください。",
"reset_amount": "額をリセット", "reset_amount": "額をリセット",
"reset_amount_confirm": "額をリセットしますか?", "reset_amount_confirm": "額をリセットしますか?",
"success_done": "完了", "success_done": "完了",
@ -237,6 +243,7 @@
"electrum_connected": "接続済", "electrum_connected": "接続済",
"electrum_connected_not": "未接続", "electrum_connected_not": "未接続",
"electrum_error_connect": "指定されたElectrumサーバーに接続できません", "electrum_error_connect": "指定されたElectrumサーバーに接続できません",
"electrum_error_connect_tor": "入力されたElectrumサーバーに接続できません。Orbitアプリが接続されていることを確認して再度お試しください。",
"lndhub_uri": "例:{example}", "lndhub_uri": "例:{example}",
"electrum_host": "例:{example}", "electrum_host": "例:{example}",
"electrum_offline_mode": "オフラインモード", "electrum_offline_mode": "オフラインモード",
@ -250,15 +257,11 @@
"electrum_status": "ステータス", "electrum_status": "ステータス",
"electrum_preferred_server": "優先サーバー", "electrum_preferred_server": "優先サーバー",
"electrum_preferred_server_description": "あなたのウォレットのすべてのビットコイン取引に使うサーバーを入力します。設定すると、あなたのウォレットは残高の確認、トランザクションの送信、ネットワークデータの取得にこのサーバーだけを使います。信用するサーバーを設定するようにしてください。", "electrum_preferred_server_description": "あなたのウォレットのすべてのビットコイン取引に使うサーバーを入力します。設定すると、あなたのウォレットは残高の確認、トランザクションの送信、ネットワークデータの取得にこのサーバーだけを使います。信用するサーバーを設定するようにしてください。",
"electrum_clear_alert_title": "履歴を削除しますか?",
"electrum_clear_alert_message": "Electrumサーバーヒストリーをクリアしますか",
"electrum_clear_alert_cancel": "キャンセル",
"electrum_clear_alert_ok": "Ok",
"electrum_reset": "デフォルトの設定に戻す",
"electrum_unable_to_connect": "{server}に接続できません。", "electrum_unable_to_connect": "{server}に接続できません。",
"electrum_history": "履歴", "electrum_history": "履歴",
"electrum_reset_to_default": "Electrumの設定をデフォルトに戻してよろしいですか", "electrum_reset_to_default": "サーバーリストからBlueWalletがランダムにサーバーを選択するようになります。",
"electrum_clear": "履歴を削除", "electrum_reset": "デフォルトの設定に戻す",
"electrum_reset_to_default_and_clear_history": "デフォルトにリセットして履歴を削除",
"encrypt_decrypt": "ストレージ復号化", "encrypt_decrypt": "ストレージ復号化",
"encrypt_decrypt_q": "本当にストレージを復号化しますか?これによりウォレットがパスワードなしでアクセス可能になります。", "encrypt_decrypt_q": "本当にストレージを復号化しますか?これによりウォレットがパスワードなしでアクセス可能になります。",
"encrypt_enc_and_pass": "暗号化しパスワードで保護", "encrypt_enc_and_pass": "暗号化しパスワードで保護",
@ -272,6 +275,8 @@
"encrypt_title": "セキュリティ", "encrypt_title": "セキュリティ",
"encrypt_tstorage": "ストレージ", "encrypt_tstorage": "ストレージ",
"encrypt_use": "{type} を使う", "encrypt_use": "{type} を使う",
"set_as_preferred": "優先に設定",
"set_as_preferred_electrum": "優先サーバーとして {host}:{port} を設定することで、おすすめサーバーへのランダムな接続が無効になります。",
"encrypted_feature_disabled": "ストレージの暗号化が有効の場合この機能は使えません。", "encrypted_feature_disabled": "ストレージの暗号化が有効の場合この機能は使えません。",
"encrypt_use_expl": "{type} は、トランザクションの実行、ウォレットのロック解除、エクスポート、または削除を行う前の本人確認に使用されます。{type} は暗号化されたストレージのロック解除には使用されません。", "encrypt_use_expl": "{type} は、トランザクションの実行、ウォレットのロック解除、エクスポート、または削除を行う前の本人確認に使用されます。{type} は暗号化されたストレージのロック解除には使用されません。",
"biometrics_fail": "もし {type} が有効でない、または解除に失敗した場合、デバイスのパスコードを代わりに使うことができます。", "biometrics_fail": "もし {type} が有効でない、または解除に失敗した場合、デバイスのパスコードを代わりに使うことができます。",
@ -284,13 +289,15 @@
"last_updated": "最終更新", "last_updated": "最終更新",
"language_isRTL": "言語の向きを適用するにはBlueWalletの再起動が必要です。", "language_isRTL": "言語の向きを適用するにはBlueWalletの再起動が必要です。",
"license": "ライセンス", "license": "ライセンス",
"lightning_error_lndhub_uri": "無効なLndHub URIです", "lightning_error_lndhub_uri": "無効なLNDhub URIです",
"lightning_error_lndhub_uri_tor": "無効なLNDhub URIです。Orbotアプリが接続されていることを確認して再度お試しください。",
"lightning_saved": "変更は正常に保存されました", "lightning_saved": "変更は正常に保存されました",
"lightning_settings": "Lightning 設定", "lightning_settings": "Lightning 設定",
"lightning_settings_explain": "他の LND ノードへ接続するには LNDHub をインストール後、URL を入力してください。指定した LNDHub へ接続するのは変更保存後に作成されたウォレットのみですので注意してください。", "lightning_settings_explain": "他の LND ノードへ接続するには LNDHub をインストール後、URL を入力してください。指定した LNDHub へ接続するのは変更保存後に作成されたウォレットのみですので注意してください。",
"network": "ネットワーク", "network": "ネットワーク",
"network_broadcast": "ブロードキャストトランザクション", "network_broadcast": "ブロードキャストトランザクション",
"network_electrum": "Electrum サーバー", "network_electrum": "Electrum サーバー",
"electrum_suggested_description": "優先サーバーが設定されていない場合、ランダムに選ばれたおすすめのサーバーが使われます。",
"not_a_valid_uri": "無効なURI", "not_a_valid_uri": "無効なURI",
"notifications": "通知", "notifications": "通知",
"open_link_in_explorer": "エクスプローラで開く", "open_link_in_explorer": "エクスプローラで開く",
@ -402,7 +409,6 @@
"add_wallet_name": "ウォレット名", "add_wallet_name": "ウォレット名",
"add_wallet_type": "タイプ", "add_wallet_type": "タイプ",
"add_wallet_seed_length": "シード長", "add_wallet_seed_length": "シード長",
"add_wallet_seed_length_message": "このウォレットで使いたいシードフレーズの長さを選択してください。",
"add_wallet_seed_length_12": "12語", "add_wallet_seed_length_12": "12語",
"add_wallet_seed_length_24": "24語", "add_wallet_seed_length_24": "24語",
"clipboard_bitcoin": "クリップボードにビットコインのアドレスがあります。このアドレスを使って取引をしますか?", "clipboard_bitcoin": "クリップボードにビットコインのアドレスがあります。このアドレスを使って取引をしますか?",
@ -422,7 +428,6 @@
"details_export_history": "履歴をCSVにエクスポート", "details_export_history": "履歴をCSVにエクスポート",
"details_master_fingerprint": "マスタフィンガープリント", "details_master_fingerprint": "マスタフィンガープリント",
"details_multisig_type": "マルチシグ", "details_multisig_type": "マルチシグ",
"details_no_cancel": "いいえ、中止します",
"details_show_xpub": "ウォレット XPUB の表示", "details_show_xpub": "ウォレット XPUB の表示",
"details_show_addresses": "アドレスを表示", "details_show_addresses": "アドレスを表示",
"details_title": "ウォレット", "details_title": "ウォレット",
@ -493,7 +498,9 @@
"identity_pubkey": "識別用公開鍵", "identity_pubkey": "識別用公開鍵",
"xpub_title": "ウォレット XPUB", "xpub_title": "ウォレット XPUB",
"manage_wallets_search_placeholder": "ウォレット・メモを検索", "manage_wallets_search_placeholder": "ウォレット・メモを検索",
"more_info": "詳細情報" "more_info": "詳細情報",
"details_delete_wallet_error_message": "ウォレットが通知から削除されたかの確認に問題が生じました—ネットワークの問題か、接続が弱いためかもしれません。続行すると、ウォレットを削除した後でも、関連するトランザクションの通知を受け取る可能性があります。",
"details_delete_anyway": "とにかく削除"
}, },
"total_balance_view": { "total_balance_view": {
"display_in_bitcoin": "ビットコインで表示", "display_in_bitcoin": "ビットコインで表示",
@ -508,6 +515,10 @@
"default_label": "マルチシグ金庫", "default_label": "マルチシグ金庫",
"multisig_vault_explain": "大きな資産にベストなセキュリティ", "multisig_vault_explain": "大きな資産にベストなセキュリティ",
"provide_signature": "署名を提供", "provide_signature": "署名を提供",
"provide_signature_details": "このトランザクションに署名するためのキーを持っているデバイスとウォレットを使ってください",
"provide_signature_details_bluewallet": "BlueWalletで、送金スクリーンメニューに移動し",
"provide_signature_next_steps": "署名済みトランザクションをスキャンまたはインポート",
"provide_signature_next_steps_details": "ウォレットを使ったトランザクションの署名が終わったら、QRコードをスキャンまたはファイルをインポートして、トランザクションの詳細をすべて確認してから配信してください。",
"vault_key": "金庫キー {number}", "vault_key": "金庫キー {number}",
"required_keys_out_of_total": "全体のうち必要なキー", "required_keys_out_of_total": "全体のうち必要なキー",
"fee": "費用: {number}", "fee": "費用: {number}",
@ -654,6 +665,8 @@
"bip47": { "bip47": {
"payment_code": "支払コード", "payment_code": "支払コード",
"contacts": "連絡先", "contacts": "連絡先",
"bip47_explain": "再利用・共有可能なコード",
"bip47_explain_subtitle": "BIP47",
"purpose": "再利用・共有可能なコード (BIP47)", "purpose": "再利用・共有可能なコード (BIP47)",
"pay_this_contact": "この連絡先に支払う", "pay_this_contact": "この連絡先に支払う",
"rename_contact": "連絡先をリネーム", "rename_contact": "連絡先をリネーム",

View file

@ -9,7 +9,6 @@
"ok": "ОК", "ok": "ОК",
"yes": "Иә", "yes": "Иә",
"no": "Жоқ", "no": "Жоқ",
"save": "Сақтау",
"close": "Жабу" "close": "Жабу"
}, },
"entropy": { "entropy": {
@ -54,7 +53,6 @@
}, },
"settings": { "settings": {
"about_license": "MIT License", "about_license": "MIT License",
"electrum_clear_alert_cancel": "Бас тарту",
"header": "Баптаулар", "header": "Баптаулар",
"save": "Сақтау" "save": "Сақтау"
}, },
@ -65,7 +63,6 @@
"wallets": { "wallets": {
"add_create": "Жасау", "add_create": "Жасау",
"details_address": "Адрес", "details_address": "Адрес",
"details_no_cancel": "Жоқ, бас тарту",
"import_do_import": "Еңгізу", "import_do_import": "Еңгізу",
"import_search_accounts": "Шот іздеу", "import_search_accounts": "Шот іздеу",
"import_title": "Енгізу", "import_title": "Енгізу",

View file

@ -10,14 +10,12 @@
"storage_is_encrypted": "ನಿಮ್ಮ ಸಂಗ್ರಹಣೆಯನ್ನು ಎನ್‌ಕ್ರಿಪ್ಟ್ ಮಾಡಲಾಗಿದೆ. ಅದನ್ನು ಡೀಕ್ರಿಪ್ಟ್ ಮಾಡಲು ಪಾಸ್ವರ್ಡ್ ಅಗತ್ಯವಿದೆ.", "storage_is_encrypted": "ನಿಮ್ಮ ಸಂಗ್ರಹಣೆಯನ್ನು ಎನ್‌ಕ್ರಿಪ್ಟ್ ಮಾಡಲಾಗಿದೆ. ಅದನ್ನು ಡೀಕ್ರಿಪ್ಟ್ ಮಾಡಲು ಪಾಸ್ವರ್ಡ್ ಅಗತ್ಯವಿದೆ.",
"yes": "ಹೌದು", "yes": "ಹೌದು",
"no": "ಇಲ್ಲ", "no": "ಇಲ್ಲ",
"save": "ಉಳಿಸಿ",
"wallet_key": "ವ್ಯಾಲೆಟ್ ಕೀ" "wallet_key": "ವ್ಯಾಲೆಟ್ ಕೀ"
}, },
"entropy": { "entropy": {
"save": "ಉಳಿಸಿ" "save": "ಉಳಿಸಿ"
}, },
"settings": { "settings": {
"electrum_clear_alert_cancel": "ರದ್ದುಮಾಡಿ",
"save": "ಉಳಿಸಿ" "save": "ಉಳಿಸಿ"
} }
} }

Some files were not shown because too many files have changed in this diff Show more