mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-03-13 19:16:52 +01:00
Compare commits
396 commits
Author | SHA1 | Date | |
---|---|---|---|
|
c7909049dc | ||
|
b5270d0a07 | ||
|
4f3b828990 | ||
|
26720e8284 | ||
|
a80bacc0f4 | ||
|
5f18540ca7 | ||
|
c14cb3508c | ||
|
751c7d6f45 | ||
|
0b1c3dd9f7 | ||
|
ae89a59794 | ||
|
10b3432e0e | ||
|
c67eea8155 | ||
|
9421511f74 | ||
|
9ec0ef51e4 | ||
|
1cada11c50 | ||
|
d2cebde6ad | ||
|
1a940971bc | ||
|
28316b4d73 | ||
|
4670eea38a | ||
|
b2552bdc71 | ||
|
dbd4066f7e | ||
|
4cdd952f90 | ||
|
ddee4cdaaf | ||
|
0aa6b96e4b | ||
|
8d49aff279 | ||
|
18a187b120 | ||
|
1f77a852a8 | ||
|
9d899d672d | ||
|
e7b81e5517 | ||
|
f8af06e2ae | ||
|
8b81472fa4 | ||
|
4ad2b15070 | ||
|
dd118af993 | ||
|
d375bd9780 | ||
|
c967f6701a | ||
|
4753d984ca | ||
|
28e2e343b8 | ||
|
b81879e9bd | ||
|
040f91028a | ||
|
1c8aa08de8 | ||
|
d7743a740f | ||
|
a2ec407720 | ||
|
c90bf68a66 | ||
|
7ab73669dc | ||
|
72134fef84 | ||
|
c0eec1be8e | ||
|
77f9cf7e16 | ||
|
88be0332e4 | ||
|
a4f8e42d8c | ||
|
e519360d89 | ||
|
62a5efc82c | ||
|
ec2bc5e627 | ||
|
0bdfc6fa85 | ||
|
ef5887f28b | ||
|
3f82cb4449 | ||
|
9574554780 | ||
|
4b249edaa4 | ||
|
e3fcbbb713 | ||
|
63a3c61534 | ||
|
a6d66574cd | ||
|
2376ef8be9 | ||
|
155f021692 | ||
|
3a26d6dab2 | ||
|
1da481542a | ||
|
472307c271 | ||
|
09394ff4f9 | ||
|
16936fca27 | ||
|
54166c0592 | ||
|
bb6d443670 | ||
|
e4e16a8f40 | ||
|
35deca58e0 | ||
|
898443f3a5 | ||
|
15fc708a0a | ||
|
15c618b59a | ||
|
ccdb492ba0 | ||
|
0449965ef5 | ||
|
758c2acf3a | ||
|
b75aa7b269 | ||
|
aa695f2705 | ||
|
863ac46bc8 | ||
|
1946fa0dde | ||
|
a62a21b28b | ||
|
c1ae300254 | ||
|
5e4d58b207 | ||
|
3504d0dc30 | ||
|
fe795e648b | ||
|
af8d7d3477 | ||
|
c604ac4197 | ||
|
4c0fd89530 | ||
|
d14b4265f8 | ||
|
307e950d15 | ||
|
10f145d012 | ||
|
88b8274758 | ||
|
a65776933d | ||
|
05491387ff | ||
|
e23f233f25 | ||
|
7eb420c561 | ||
|
96e553f3d5 | ||
|
2f3cf1b4e9 | ||
|
6a4392de02 | ||
|
62bb33a9ff | ||
|
85cd7b4aed | ||
|
9507a48314 | ||
|
07b93d521d | ||
|
7c6bf01372 | ||
|
b01aa58e3b | ||
|
136dd20f9e | ||
|
0bfeda0d75 | ||
|
1a848328e3 | ||
|
93e6269611 | ||
|
5f8dbc52d1 | ||
|
e4d3ecba98 | ||
|
bbe4449dd9 | ||
|
bf9087eae6 | ||
|
b3ff1b7c3f | ||
|
00dcc25142 | ||
|
4614c51041 | ||
|
8d694ceb7b | ||
|
49f6068b21 | ||
|
be8437e107 | ||
|
cc71dfce8c | ||
|
2e1f20c080 | ||
|
11dceb19fa | ||
|
7bb3dd6aef | ||
|
7e7492d314 | ||
|
c4b1e67f9d | ||
|
79f624e906 | ||
|
fabfc5c156 | ||
|
a3d234bee1 | ||
|
850ac2c653 | ||
|
1c26cb420e | ||
|
0f23c4d0a7 | ||
|
9adef2b3c1 | ||
|
a9b003e762 | ||
|
37b03b12e7 | ||
|
4e7c5a28ae | ||
|
9766d2387a | ||
|
3939ef32f9 | ||
|
17a5a78fd8 | ||
|
f7d673d93b | ||
|
dc3e88c005 | ||
|
85cb6c1287 | ||
|
8488dfb9e7 | ||
|
3dde81f3a8 | ||
|
a0dc0a31e7 | ||
|
2dc26ac26a | ||
|
b881370f83 | ||
|
4f31aff503 | ||
|
2ee13dcf4f | ||
|
bf65d2d302 | ||
|
3599ef50ad | ||
|
80fb4a74a8 | ||
|
4f326452ab | ||
|
37a88fd60d | ||
|
3fcf3c0840 | ||
|
685332ce22 | ||
|
0892332d23 | ||
|
11cb7dbed5 | ||
|
7205f70c30 | ||
|
aa9e647c28 | ||
|
8439ff9893 | ||
|
f83476428e | ||
|
4be2668c81 | ||
|
867581003c | ||
|
3f1ea9432b | ||
|
2bcbe9903e | ||
|
7963083237 | ||
|
b7e03c1ed1 | ||
|
b74fb5f389 | ||
|
ddd141fc11 | ||
|
d9a70f5879 | ||
|
e645c911d7 | ||
|
06fbb8c945 | ||
|
c008a644cc | ||
|
ab7e7cf1d5 | ||
|
d38968086e | ||
|
787fcb797d | ||
|
599b5d3b60 | ||
|
c3f7d5b184 | ||
|
3423730a41 | ||
|
2bb7b0c53f | ||
|
b0a7053fc0 | ||
|
a8c0da4768 | ||
|
b357053e65 | ||
|
5b20ac352e | ||
|
8c2640e5a9 | ||
|
a6575b7b73 | ||
|
db58bcf70d | ||
|
b8d1d686f0 | ||
|
5d088a67c1 | ||
|
fa43d03a36 | ||
|
5a7fb86742 | ||
|
4ff759b538 | ||
|
0b33af59f0 | ||
|
f9d8594509 | ||
|
7f97c340f8 | ||
|
925dc17042 | ||
|
164f16657a | ||
|
34db010bde | ||
|
7ccf19212f | ||
|
133312e065 | ||
|
46a78e8dfb | ||
|
e5ab5f6565 | ||
|
ad71dccd72 | ||
|
159a8b2e16 | ||
|
d3fd8c050f | ||
|
52b3cb9b34 | ||
|
98b643a023 | ||
|
0eb3393f1f | ||
|
47a08448a2 | ||
|
254550d92e | ||
|
9ed7ceabdb | ||
|
6afc6624eb | ||
|
2317d0a4cf | ||
|
dcd2023815 | ||
|
4b37eaba98 | ||
|
8619e80dc0 | ||
|
e06d1ce57c | ||
|
157bd3529a | ||
|
e92eb7eae0 | ||
|
b9227cdbc6 | ||
|
a047c0219e | ||
|
2ca8eca810 | ||
|
93f901e94f | ||
|
670ad6a833 | ||
|
b232a13243 | ||
|
d610063809 | ||
|
1b328cd130 | ||
|
0b5e640630 | ||
|
527219f697 | ||
|
c400771d7a | ||
|
021ed454f1 | ||
|
47673a4ae0 | ||
|
7e66e42862 | ||
|
e25833f0d3 | ||
|
0e698069f4 | ||
|
3a8a7d6da8 | ||
|
1b561c8a91 | ||
|
4b93827b7f | ||
|
3de6976997 | ||
|
9c1be484c1 | ||
|
f04b50c58b | ||
|
f974658472 | ||
|
208157430f | ||
|
b73f04b4e6 | ||
|
0429721d66 | ||
|
5fb3991cb2 | ||
|
e4093a357d | ||
|
fda596211a | ||
|
fb1a30191d | ||
|
2f3ac6e972 | ||
|
8719ded414 | ||
|
680d9d4495 | ||
|
45f095badf | ||
|
66bb0b0e1c | ||
|
33acf30d68 | ||
|
2c4bb95475 | ||
|
9435fb769f | ||
|
6321627578 | ||
|
18cb2faef6 | ||
|
2c68583495 | ||
|
f8629e2555 | ||
|
6c11e2a5b8 | ||
|
827c2ad3db | ||
|
4be2bb03be | ||
|
12d8596180 | ||
|
2a4b14d63e | ||
|
062b8844d4 | ||
|
e4cea4f451 | ||
|
82f13fbded | ||
|
a1d5941a75 | ||
|
4d9a2f79f9 | ||
|
fef62f2fd8 | ||
|
c1adabb021 | ||
|
b42290ceee | ||
|
0aa2ed20f3 | ||
|
c092ea4523 | ||
|
d338f813cb | ||
|
238ee798ab | ||
|
bbf746b011 | ||
|
fdd2b66d8e | ||
|
44fc028159 | ||
|
632500b734 | ||
|
a4a513f703 | ||
|
39b141507c | ||
|
dde4520094 | ||
|
a7843e127f | ||
|
f4125cb1e9 | ||
|
115b0a2a4f | ||
|
7035bec229 | ||
|
80ef3252a1 | ||
|
6cd6079493 | ||
|
9a8158a384 | ||
|
9976734665 | ||
|
1c15ae0a0c | ||
|
64d8238872 | ||
|
336785e1a0 | ||
|
d68b806b60 | ||
|
1acf4c9af2 | ||
|
2d51238d6f | ||
|
3b90c49d79 | ||
|
226d499603 | ||
|
f155b6b577 | ||
|
4be7f78be8 | ||
|
5d81a4cf57 | ||
|
1b11200a0a | ||
|
f229beb5e0 | ||
|
e176783a3f | ||
|
5c7460d6b1 | ||
|
571b056854 | ||
|
54db4b366e | ||
|
02dd22b8d4 | ||
|
8b94a9db10 | ||
|
175a5f27aa | ||
|
907e54938c | ||
|
c43d36d84d | ||
|
f900f46deb | ||
|
472b6c97ff | ||
|
ae80cb9118 | ||
|
f389af09ba | ||
|
fc5eccfcd4 | ||
|
307306f5ec | ||
|
73081033ed | ||
|
8b531350af | ||
|
4c09a52e02 | ||
|
879f4f4081 | ||
|
ea870729d6 | ||
|
b8a8986a8e | ||
|
caf41400a0 | ||
|
30ed2a3d96 | ||
|
a8374ab25b | ||
|
d3fd15dcf8 | ||
|
e8c181359d | ||
|
307f6881c9 | ||
|
e38e70bb0b | ||
|
92fda5d969 | ||
|
e69c105ccf | ||
|
002efdc4e7 | ||
|
63ab4da34b | ||
|
c2ff24591e | ||
|
19dd1047d1 | ||
|
38d92a7a5c | ||
|
59f2835cb8 | ||
|
9863dfd47b | ||
|
70e32c9d69 | ||
|
2b393ba997 | ||
|
88a1ec4260 | ||
|
5eabded72b | ||
|
d957ee7197 | ||
|
a4df48a0c5 | ||
|
6d7e48eb1a | ||
|
0481c8d6a9 | ||
|
85e47ac83d | ||
|
0d64347813 | ||
|
4849042dc6 | ||
|
d2e186bbf7 | ||
|
588da24f0b | ||
|
a8858833ef | ||
|
06ec5feb4b | ||
|
e9c3e3143f | ||
|
83f545ed4b | ||
|
7b8b3a0be2 | ||
|
6dde0c4b4e | ||
|
e810baf9c8 | ||
|
facd7b7783 | ||
|
89e7b23c05 | ||
|
56c983e1dc | ||
|
016e9f4214 | ||
|
c932d0da5a | ||
|
cdd923db7c | ||
|
4611c46d1e | ||
|
59c9edeebd | ||
|
05eff5f2b4 | ||
|
8894bcf965 | ||
|
a6306c53d8 | ||
|
d05d51237a | ||
|
5c70faf17d | ||
|
18c5e38d6c | ||
|
ca1be7d443 | ||
|
ec027a12df | ||
|
6b013e5bb7 | ||
|
950848181e | ||
|
559468b221 | ||
|
3adb90abff | ||
|
6765dd7246 | ||
|
6698645f48 | ||
|
cdd76db18f | ||
|
715991b106 | ||
|
7882716c73 | ||
|
9f912c51ed | ||
|
8ae9ac6155 | ||
|
ea4acc2556 | ||
|
e1202c6854 | ||
|
021a1fd352 | ||
|
8f26859f76 | ||
|
f6a6d7c41e |
197 changed files with 6240 additions and 4408 deletions
|
@ -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"
|
||||||
|
|
124
.github/workflows/build-ios-release-pullrequest.yml
vendored
124
.github/workflows/build-ios-release-pullrequest.yml
vendored
|
@ -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
17
App.tsx
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
9
Gemfile
9
Gemfile
|
@ -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)
|
||||||
|
|
52
Gemfile.lock
52
Gemfile.lock
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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;
|
|
|
@ -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({
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
4
android/app/src/main/res/xml/file_paths.xml
Normal file
4
android/app/src/main/res/xml/file_paths.xml
Normal 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>
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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], []);
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -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}>
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -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}>
|
||||||
|
|
|
@ -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
73
components/TipBox.tsx
Normal 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;
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
1
gesture-handler.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
// Don't import react-native-gesture-handler on web
|
2
gesture-handler.native.js
Normal file
2
gesture-handler.native.js
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
// Only import react-native-gesture-handler on native platforms
|
||||||
|
import 'react-native-gesture-handler';
|
|
@ -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
23
helpers/screenProtect.ts
Normal 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
|
|
@ -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;
|
|
@ -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;
|
||||||
|
|
|
@ -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>();
|
|
@ -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();
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
8
index.js
8
index.js
|
@ -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(() => {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
32
ios/Components/EventEmitter.swift
Normal file
32
ios/Components/EventEmitter.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
ios/Components/SegmentedControl/CustomSegmentedControl.m
Normal file
9
ios/Components/SegmentedControl/CustomSegmentedControl.m
Normal 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
|
70
ios/Components/SegmentedControl/CustomSegmentedControl.swift
Normal file
70
ios/Components/SegmentedControl/CustomSegmentedControl.swift
Normal 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
33
ios/EventEmitter.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
||||||
|
|
134
ios/MenuElementsEmitter/MenuElementsEmitter.swift
Normal file
134
ios/MenuElementsEmitter/MenuElementsEmitter.swift
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
605
ios/Podfile.lock
605
ios/Podfile.lock
File diff suppressed because it is too large
Load diff
|
@ -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)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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": "المحفظة",
|
||||||
|
|
|
@ -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": {
|
||||||
|
|
|
@ -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": "Криптиран и защитен с парола",
|
||||||
|
|
|
@ -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": "جاگه زفت کردنی",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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": "Πορτοφόλι",
|
||||||
|
|
17
loc/en.json
17
loc/en.json
|
@ -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}",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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": {
|
||||||
|
|
|
@ -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": "کیف پول",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
25
loc/he.json
25
loc/he.json
|
@ -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": "שינוי שם איש קשר",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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": "連絡先をリネーム",
|
||||||
|
|
|
@ -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": "Енгізу",
|
||||||
|
|
|
@ -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
Loading…
Add table
Reference in a new issue