mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-02-24 15:36:59 +01:00
Merge branch 'master' into clip
This commit is contained in:
commit
9890d5a7cf
47 changed files with 1937 additions and 1813 deletions
137
.github/workflows/build-ios-release-pullrequest.yml
vendored
137
.github/workflows/build-ios-release-pullrequest.yml
vendored
|
@ -17,65 +17,88 @@ jobs:
|
|||
outputs:
|
||||
new_build_number: ${{ steps.generate_build_number.outputs.build_number }}
|
||||
project_version: ${{ steps.determine_marketing_version.outputs.project_version }}
|
||||
ipa_output_path: ${{ steps.build_app.outputs.ipa_output_path }}
|
||||
latest_commit_message: ${{ steps.get_latest_commit_message.outputs.commit_message }}
|
||||
env:
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
steps:
|
||||
- name: Checkout project
|
||||
- name: Checkout Project
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # Fetches all history
|
||||
|
||||
|
||||
- name: Specify node version
|
||||
|
||||
- name: Specify Node.js Version
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
|
||||
- uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: 16.0
|
||||
|
||||
- name: Set up Ruby
|
||||
|
||||
- name: Set Up Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 3.1.6
|
||||
bundler-cache: true
|
||||
|
||||
- name: Install dependencies with Bundler
|
||||
|
||||
- name: Install Dependencies with Bundler
|
||||
run: |
|
||||
bundle config path vendor/bundle
|
||||
bundle install --jobs 4 --retry 3 --quiet
|
||||
|
||||
- name: Install node_modules
|
||||
- name: Install Node Modules
|
||||
run: npm install --omit=dev --yes
|
||||
|
||||
- name: Install CocoaPods Dependencies
|
||||
run: |
|
||||
bundle exec fastlane ios install_pods
|
||||
|
||||
- name: Cache CocoaPods Pods
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ios/Pods
|
||||
key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
|
||||
- name: Display release-notes.txt
|
||||
run: cat release-notes.txt
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pods-
|
||||
|
||||
- name: Get Latest Commit Message
|
||||
id: get_latest_commit_message
|
||||
run: |
|
||||
LATEST_COMMIT_MESSAGE=$(git log -1 --pretty=format:"%s")
|
||||
echo "LATEST_COMMIT_MESSAGE=${LATEST_COMMIT_MESSAGE}" >> $GITHUB_ENV
|
||||
echo "::set-output name=commit_message::$LATEST_COMMIT_MESSAGE"
|
||||
- name: Set up Git Authentication
|
||||
echo "commit_message=$LATEST_COMMIT_MESSAGE" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Generate Build Number Based on Timestamp
|
||||
id: generate_build_number
|
||||
run: |
|
||||
NEW_BUILD_NUMBER=$(date +%s)
|
||||
echo "NEW_BUILD_NUMBER=$NEW_BUILD_NUMBER" >> $GITHUB_ENV
|
||||
echo "build_number=$NEW_BUILD_NUMBER" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Set Build Number
|
||||
run: bundle exec fastlane ios increment_build_number_lane
|
||||
|
||||
- name: Determine Marketing Version
|
||||
id: determine_marketing_version
|
||||
run: |
|
||||
MARKETING_VERSION=$(grep MARKETING_VERSION BlueWallet.xcodeproj/project.pbxproj | awk -F '= ' '{print $2}' | tr -d ' ;' | head -1)
|
||||
echo "PROJECT_VERSION=$MARKETING_VERSION" >> $GITHUB_ENV
|
||||
echo "project_version=$MARKETING_VERSION" >> $GITHUB_OUTPUT
|
||||
working-directory: ios
|
||||
|
||||
- name: Set Up Git Authentication
|
||||
env:
|
||||
ACCESS_TOKEN: ${{ secrets.GIT_ACCESS_TOKEN }}
|
||||
run: |
|
||||
git config --global credential.helper 'cache --timeout=3600'
|
||||
git config --global http.https://github.com/.extraheader "AUTHORIZATION: basic $(echo -n x-access-token:${ACCESS_TOKEN} | base64)"
|
||||
|
||||
- name: Create Temporary Keychain
|
||||
run: bundle exec fastlane ios create_temp_keychain
|
||||
env:
|
||||
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
|
||||
|
||||
- name: Setup Provisioning Profiles
|
||||
env:
|
||||
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
|
||||
|
@ -86,14 +109,16 @@ jobs:
|
|||
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
|
||||
run: |
|
||||
bundle exec fastlane ios setup_provisioning_profiles
|
||||
|
||||
- name: Cache Provisioning Profiles
|
||||
id: cache_provisioning_profiles
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/Library/MobileDevice/Provisioning Profiles
|
||||
path: ~/Library/MobileDevice/Provisioning\ Profiles
|
||||
key: ${{ runner.os }}-provisioning-profiles-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-provisioning-profiles-
|
||||
|
||||
- name: Check Cache Status for Provisioning Profiles
|
||||
run: |
|
||||
if [ -n "${{ steps.cache_provisioning_profiles.outputs.cache-hit }}" ]; then
|
||||
|
@ -101,41 +126,36 @@ jobs:
|
|||
else
|
||||
echo "No cache found for provisioning profiles. A new cache will be created."
|
||||
fi
|
||||
|
||||
- name: Verify Provisioning Profiles Exist
|
||||
run: |
|
||||
if [ -d "~/Library/MobileDevice/Provisioning Profiles" ]; then
|
||||
echo "Provisioning profiles are available in the cache."
|
||||
ls -la ~/Library/MobileDevice/Provisioning Profiles
|
||||
ls -la ~/Library/MobileDevice/Provisioning\ Profiles
|
||||
else
|
||||
echo "Provisioning profiles directory does not exist."
|
||||
fi
|
||||
- name: Generate Build Number based on timestamp
|
||||
id: generate_build_number
|
||||
run: |
|
||||
NEW_BUILD_NUMBER=$(date +%s)
|
||||
echo "NEW_BUILD_NUMBER=$NEW_BUILD_NUMBER" >> $GITHUB_ENV
|
||||
echo "::set-output name=build_number::$NEW_BUILD_NUMBER"
|
||||
- name: Set Build Number
|
||||
run: bundle exec fastlane ios increment_build_number_lane
|
||||
- name: Determine Marketing Version
|
||||
id: determine_marketing_version
|
||||
run: |
|
||||
MARKETING_VERSION=$(grep MARKETING_VERSION BlueWallet.xcodeproj/project.pbxproj | awk -F '= ' '{print $2}' | tr -d ' ;' | head -1)
|
||||
echo "PROJECT_VERSION=$MARKETING_VERSION" >> $GITHUB_ENV
|
||||
echo "::set-output name=project_version::$MARKETING_VERSION"
|
||||
working-directory: ios
|
||||
|
||||
- name: Expected IPA file name
|
||||
run: |
|
||||
echo "IPA file name: BlueWallet_${{env.PROJECT_VERSION}}(${{env.NEW_BUILD_NUMBER}}).ipa"
|
||||
|
||||
- name: Build App
|
||||
run: bundle exec fastlane ios build_app_lane
|
||||
id: build_app
|
||||
run: |
|
||||
bundle exec fastlane ios build_app_lane --verbose
|
||||
echo "ipa_output_path=$IPA_OUTPUT_PATH" >> $GITHUB_OUTPUT # Set the IPA output path for future jobs
|
||||
|
||||
- name: Upload Build Logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build_logs
|
||||
path: ./ios/build_logs/
|
||||
|
||||
- name: Upload IPA as Artifact
|
||||
if: success()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: BlueWallet_${{env.PROJECT_VERSION}}_${{env.NEW_BUILD_NUMBER}}.ipa
|
||||
path: ./build/BlueWallet_${{env.PROJECT_VERSION}}_${{env.NEW_BUILD_NUMBER}}.ipa
|
||||
|
||||
path: ${{ env.IPA_OUTPUT_PATH }} # Directly from Fastfile `IPA_OUTPUT_PATH`
|
||||
|
||||
testflight-upload:
|
||||
needs: build
|
||||
runs-on: macos-latest
|
||||
|
@ -146,13 +166,15 @@ jobs:
|
|||
PROJECT_VERSION: ${{ needs.build.outputs.project_version }}
|
||||
LATEST_COMMIT_MESSAGE: ${{ needs.build.outputs.latest_commit_message }}
|
||||
steps:
|
||||
- name: Checkout project
|
||||
- name: Checkout Project
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Ruby
|
||||
|
||||
- name: Set Up Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 3.1.6
|
||||
bundler-cache: true
|
||||
|
||||
- name: Cache Ruby Gems
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
|
@ -160,19 +182,43 @@ jobs:
|
|||
key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gems-
|
||||
- name: Install dependencies with Bundler
|
||||
|
||||
- name: Install Dependencies with Bundler
|
||||
run: |
|
||||
bundle config path vendor/bundle
|
||||
bundle install --jobs 4 --retry 3 --quiet
|
||||
|
||||
- name: Download IPA from Artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: BlueWallet_${{ needs.build.outputs.project_version }}_${{ needs.build.outputs.new_build_number }}.ipa
|
||||
path: ./
|
||||
path: ./ # Download the IPA file to the current working directory
|
||||
|
||||
- name: Create App Store Connect 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
|
||||
run: echo "IPA_OUTPUT_PATH=$(pwd)/BlueWallet_${{ needs.build.outputs.project_version }}_${{ needs.build.outputs.new_build_number }}.ipa" >> $GITHUB_ENV
|
||||
|
||||
- name: Verify IPA Path Before Upload
|
||||
run: |
|
||||
if [ ! -f "$IPA_OUTPUT_PATH" ]; then
|
||||
echo "IPA file not found at path: $IPA_OUTPUT_PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Upload to TestFlight
|
||||
env:
|
||||
run: |
|
||||
ls -la $IPA_OUTPUT_PATH
|
||||
bundle exec fastlane ios upload_to_testflight_lane
|
||||
env:
|
||||
APP_STORE_CONNECT_API_KEY_PATH: $(pwd)/appstore_api_key.p8
|
||||
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
|
||||
GIT_ACCESS_TOKEN: ${{ secrets.GIT_ACCESS_TOKEN }}
|
||||
|
@ -182,7 +228,8 @@ jobs:
|
|||
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 }}
|
||||
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
|
||||
run: bundle exec fastlane ios upload_to_testflight_lane
|
||||
IPA_OUTPUT_PATH: ${{ env.IPA_OUTPUT_PATH }}
|
||||
|
||||
- name: Post PR Comment
|
||||
if: success() && github.event_name == 'pull_request'
|
||||
uses: actions/github-script@v6
|
||||
|
@ -199,4 +246,4 @@ jobs:
|
|||
...repo,
|
||||
issue_number: prNumber,
|
||||
body: message,
|
||||
});
|
||||
});
|
39
Gemfile.lock
39
Gemfile.lock
|
@ -24,20 +24,20 @@ GEM
|
|||
artifactory (3.0.17)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.3.0)
|
||||
aws-partitions (1.973.0)
|
||||
aws-sdk-core (3.204.0)
|
||||
aws-partitions (1.989.0)
|
||||
aws-sdk-core (3.209.1)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.651.0)
|
||||
aws-sigv4 (~> 1.9)
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-kms (1.90.0)
|
||||
aws-sdk-core (~> 3, >= 3.203.0)
|
||||
aws-sdk-kms (1.94.0)
|
||||
aws-sdk-core (~> 3, >= 3.207.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-s3 (1.161.0)
|
||||
aws-sdk-core (~> 3, >= 3.203.0)
|
||||
aws-sdk-s3 (1.167.0)
|
||||
aws-sdk-core (~> 3, >= 3.207.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sigv4 (1.9.1)
|
||||
aws-sigv4 (1.10.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.4)
|
||||
base64 (0.2.0)
|
||||
|
@ -96,8 +96,8 @@ GEM
|
|||
escape (0.0.4)
|
||||
ethon (0.16.0)
|
||||
ffi (>= 1.15.0)
|
||||
excon (0.111.0)
|
||||
faraday (1.10.3)
|
||||
excon (0.112.0)
|
||||
faraday (1.10.4)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
|
@ -123,10 +123,10 @@ GEM
|
|||
faraday-patron (1.0.0)
|
||||
faraday-rack (1.0.0)
|
||||
faraday-retry (1.0.3)
|
||||
faraday_middleware (1.2.0)
|
||||
faraday_middleware (1.2.1)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.3.1)
|
||||
fastlane (2.222.0)
|
||||
fastlane (2.224.0)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
|
@ -214,16 +214,17 @@ GEM
|
|||
http-cookie (1.0.7)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
i18n (1.14.5)
|
||||
i18n (1.14.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jmespath (1.6.2)
|
||||
json (2.7.2)
|
||||
jwt (2.8.2)
|
||||
jwt (2.9.3)
|
||||
base64
|
||||
logger (1.6.1)
|
||||
mime-types (3.5.2)
|
||||
mime-types (3.6.0)
|
||||
logger
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2024.0903)
|
||||
mime-types-data (3.2024.1001)
|
||||
mini_magick (4.13.2)
|
||||
mini_mime (1.1.5)
|
||||
minitest (5.25.1)
|
||||
|
@ -250,7 +251,7 @@ GEM
|
|||
mime-types (>= 1.16, < 4.0)
|
||||
netrc (~> 0.8)
|
||||
retriable (3.1.2)
|
||||
rexml (3.3.7)
|
||||
rexml (3.3.8)
|
||||
rouge (2.0.7)
|
||||
ruby-macho (2.5.1)
|
||||
ruby2_keywords (0.0.5)
|
||||
|
@ -278,15 +279,15 @@ GEM
|
|||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
uber (0.1.0)
|
||||
unicode-display_width (2.5.0)
|
||||
unicode-display_width (2.6.0)
|
||||
word_wrap (1.0.0)
|
||||
xcodeproj (1.25.0)
|
||||
xcodeproj (1.25.1)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
nanaimo (~> 0.3.0)
|
||||
rexml (>= 3.3.2, < 4.0)
|
||||
rexml (>= 3.3.6, < 4.0)
|
||||
xcpretty (0.3.0)
|
||||
rouge (~> 2.0.7)
|
||||
xcpretty-travis-formatter (1.0.1)
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<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
|
||||
android:name=".MainApplication"
|
||||
android:label="@string/app_name"
|
||||
|
@ -52,12 +52,12 @@
|
|||
<meta-data
|
||||
android:name="com.dieam.reactnativepushnotification.notification_color"
|
||||
android:resource="@color/white" />
|
||||
<meta-data
|
||||
android:name="firebase_messaging_auto_init_enabled"
|
||||
android:value="false" />
|
||||
<meta-data
|
||||
android:name="firebase_analytics_collection_enabled"
|
||||
android:value="false" />
|
||||
<meta-data
|
||||
android:name="firebase_messaging_auto_init_enabled"
|
||||
android:value="false" />
|
||||
<meta-data
|
||||
android:name="firebase_analytics_collection_enabled"
|
||||
android:value="false" />
|
||||
|
||||
<receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationActions" />
|
||||
<receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationPublisher" />
|
||||
|
@ -95,11 +95,23 @@
|
|||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:exported="true">
|
||||
|
||||
<!-- Main launcher intent filter -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Intent filter for opening the app only when .psbt files are selected -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="file" android:mimeType="application/octet-stream" android:pathPattern=".*\\.psbt" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Intent filter for other custom schemes (bitcoin, bluewallet, etc.) -->
|
||||
<intent-filter tools:ignore="AppLinkUrlError">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
@ -111,7 +123,16 @@
|
|||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Intent filter for importing other file types (but not launching the app) -->
|
||||
<intent-filter tools:ignore="AppLinkUrlError">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:scheme="file" android:mimeType="application/octet-stream" android:pathPattern=".*\\.*" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Intent filter for handling PSBT files -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.intent.action.EDIT" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
@ -119,7 +140,7 @@
|
|||
android:mimeType="application/octet-stream"
|
||||
android:pathPattern=".*\\.psbt" />
|
||||
</intent-filter>
|
||||
<intent-filter tools:ignore="AppLinkUrlError">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.intent.action.EDIT" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
@ -142,4 +163,4 @@
|
|||
<data android:scheme="http" />
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
</manifest>
|
|
@ -299,6 +299,7 @@ const presentNetworkErrorAlert = async (usingPeer?: Peer) => {
|
|||
return;
|
||||
}
|
||||
presentAlert({
|
||||
allowRepeat: false,
|
||||
title: loc.errors.network,
|
||||
message: loc.formatString(
|
||||
usingPeer ? loc.settings.electrum_unable_to_connect : loc.settings.electrum_error_connect,
|
||||
|
|
|
@ -147,7 +147,7 @@ function Notifications(props) {
|
|||
ActionSheet.showActionSheetWithOptions(
|
||||
{
|
||||
title: loc.settings.notifications,
|
||||
message: loc.notifications.would_you_like_to_receive_notifications,
|
||||
message: `${loc.notifications.would_you_like_to_receive_notifications}\n${loc.settings.push_notifications_explanation}`,
|
||||
options,
|
||||
cancelButtonIndex: 0, // Assuming 'no and don't ask' is still treated as the cancel action
|
||||
anchor: anchor ? findNodeHandle(anchor.current) : undefined,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Alert as RNAlert, Platform, ToastAndroid } from 'react-native';
|
||||
import { Alert as RNAlert, Platform, ToastAndroid, AlertButton, AlertOptions } from 'react-native';
|
||||
import triggerHapticFeedback, { HapticFeedbackTypes } from '../blue_modules/hapticFeedback';
|
||||
import loc from '../loc';
|
||||
|
||||
|
@ -7,16 +7,6 @@ export enum AlertType {
|
|||
Toast,
|
||||
}
|
||||
|
||||
interface AlertButton {
|
||||
text: string;
|
||||
onPress?: () => void;
|
||||
style?: 'default' | 'cancel' | 'destructive';
|
||||
}
|
||||
|
||||
interface AlertOptions {
|
||||
cancelable?: boolean;
|
||||
}
|
||||
|
||||
const presentAlert = (() => {
|
||||
let lastAlertParams: {
|
||||
title?: string;
|
||||
|
@ -31,6 +21,14 @@ const presentAlert = (() => {
|
|||
lastAlertParams = null;
|
||||
};
|
||||
|
||||
const showAlert = (title: string | undefined, message: string, buttons: AlertButton[], options: AlertOptions) => {
|
||||
if (Platform.OS === 'ios') {
|
||||
RNAlert.alert(title ?? message, title && message ? message : undefined, buttons, options);
|
||||
} else {
|
||||
RNAlert.alert(title ?? '', message, buttons, options);
|
||||
}
|
||||
};
|
||||
|
||||
return ({
|
||||
title,
|
||||
message,
|
||||
|
@ -38,6 +36,7 @@ const presentAlert = (() => {
|
|||
hapticFeedback,
|
||||
buttons = [],
|
||||
options = { cancelable: false },
|
||||
allowRepeat = true,
|
||||
}: {
|
||||
title?: string;
|
||||
message: string;
|
||||
|
@ -45,45 +44,38 @@ const presentAlert = (() => {
|
|||
hapticFeedback?: HapticFeedbackTypes;
|
||||
buttons?: AlertButton[];
|
||||
options?: AlertOptions;
|
||||
allowRepeat?: boolean;
|
||||
}) => {
|
||||
if (
|
||||
lastAlertParams &&
|
||||
lastAlertParams.title === title &&
|
||||
lastAlertParams.message === message &&
|
||||
lastAlertParams.type === type &&
|
||||
lastAlertParams.hapticFeedback === hapticFeedback &&
|
||||
JSON.stringify(lastAlertParams.buttons) === JSON.stringify(buttons) &&
|
||||
JSON.stringify(lastAlertParams.options) === JSON.stringify(options)
|
||||
) {
|
||||
return; // Skip showing the alert if the content is the same as the last one
|
||||
const currentAlertParams = { title, message, type, hapticFeedback, buttons, options };
|
||||
|
||||
if (!allowRepeat && lastAlertParams && JSON.stringify(lastAlertParams) === JSON.stringify(currentAlertParams)) {
|
||||
return;
|
||||
}
|
||||
|
||||
lastAlertParams = { title, message, type, hapticFeedback, buttons, options };
|
||||
if (JSON.stringify(lastAlertParams) !== JSON.stringify(currentAlertParams)) {
|
||||
clearCache();
|
||||
}
|
||||
|
||||
lastAlertParams = currentAlertParams;
|
||||
|
||||
if (hapticFeedback) {
|
||||
triggerHapticFeedback(hapticFeedback);
|
||||
}
|
||||
|
||||
// Ensure that there's at least one button (required for both iOS and Android)
|
||||
const wrappedButtons =
|
||||
buttons.length > 0
|
||||
? buttons
|
||||
: [
|
||||
{
|
||||
text: loc._.ok,
|
||||
onPress: () => {},
|
||||
},
|
||||
];
|
||||
const wrappedButtons: AlertButton[] = buttons.length > 0 ? buttons : [{ text: loc._.ok, onPress: () => {}, style: 'default' }];
|
||||
|
||||
switch (type) {
|
||||
case AlertType.Toast:
|
||||
if (Platform.OS === 'android') {
|
||||
ToastAndroid.show(message, ToastAndroid.LONG);
|
||||
clearCache();
|
||||
} else {
|
||||
// For iOS, treat Toast as a normal alert
|
||||
showAlert(title, message, wrappedButtons, options);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
RNAlert.alert(title ?? message, title && message ? message : undefined, wrappedButtons, options);
|
||||
showAlert(title, message, wrappedButtons, options);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -253,7 +253,7 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
|
|||
const handleOnCopyTransactionID = useCallback(() => Clipboard.setString(item.hash), [item.hash]);
|
||||
const handleOnCopyNote = useCallback(() => Clipboard.setString(subtitle ?? ''), [subtitle]);
|
||||
const handleOnViewOnBlockExplorer = useCallback(() => {
|
||||
const url = `${selectedBlockExplorer}/tx/${item.hash}`;
|
||||
const url = `${selectedBlockExplorer.url}/tx/${item.hash}`;
|
||||
Linking.canOpenURL(url).then(supported => {
|
||||
if (supported) {
|
||||
Linking.openURL(url);
|
||||
|
@ -261,7 +261,7 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
|
|||
});
|
||||
}, [item.hash, selectedBlockExplorer]);
|
||||
const handleCopyOpenInBlockExplorerPress = useCallback(() => {
|
||||
Clipboard.setString(`${selectedBlockExplorer}/tx/${item.hash}`);
|
||||
Clipboard.setString(`${selectedBlockExplorer.url}/tx/${item.hash}`);
|
||||
}, [item.hash, selectedBlockExplorer]);
|
||||
|
||||
const onToolTipPress = useCallback(
|
||||
|
|
|
@ -1,75 +1,75 @@
|
|||
# Define app identifiers once for reuse across lanes
|
||||
def app_identifiers
|
||||
["io.bluewallet.bluewallet", "io.bluewallet.bluewallet.watch", "io.bluewallet.bluewallet.watch.extension", "io.bluewallet.bluewallet.Stickers", "io.bluewallet.bluewallet.MarketWidget"]
|
||||
[
|
||||
"io.bluewallet.bluewallet",
|
||||
"io.bluewallet.bluewallet.watch",
|
||||
"io.bluewallet.bluewallet.watch.extension",
|
||||
"io.bluewallet.bluewallet.Stickers",
|
||||
"io.bluewallet.bluewallet.MarketWidget"
|
||||
]
|
||||
end
|
||||
|
||||
default_platform(:android)
|
||||
project_root = File.expand_path("..", __dir__)
|
||||
|
||||
# ===========================
|
||||
# Android Lanes
|
||||
# ===========================
|
||||
|
||||
platform :android do
|
||||
|
||||
desc "Prepare the keystore file"
|
||||
lane :prepare_keystore do
|
||||
Dir.chdir(project_root) do
|
||||
keystore_file_hex = ENV['KEYSTORE_FILE_HEX']
|
||||
UI.user_error!("KEYSTORE_FILE_HEX environment variable is missing") if keystore_file_hex.nil?
|
||||
keystore_file_hex = ENV['KEYSTORE_FILE_HEX']
|
||||
UI.user_error!("KEYSTORE_FILE_HEX environment variable is missing") if keystore_file_hex.nil?
|
||||
|
||||
Dir.chdir("android") do
|
||||
UI.message("Creating keystore hex file...")
|
||||
Dir.chdir("android") do
|
||||
UI.message("Creating keystore from HEX...")
|
||||
File.write("bluewallet-release-key.keystore.hex", keystore_file_hex)
|
||||
|
||||
File.write("bluewallet-release-key.keystore.hex", keystore_file_hex)
|
||||
|
||||
sh("xxd -plain -revert bluewallet-release-key.keystore.hex > bluewallet-release-key.keystore") do |status|
|
||||
UI.user_error!("Error reverting hex to keystore") unless status.success?
|
||||
end
|
||||
UI.message("Keystore created successfully.")
|
||||
|
||||
File.delete("bluewallet-release-key.keystore.hex")
|
||||
sh("xxd -plain -revert bluewallet-release-key.keystore.hex > bluewallet-release-key.keystore") do |status|
|
||||
UI.user_error!("Error reverting hex to keystore") unless status.success?
|
||||
end
|
||||
UI.message("Keystore created successfully.")
|
||||
|
||||
File.delete("bluewallet-release-key.keystore.hex")
|
||||
end
|
||||
end
|
||||
|
||||
desc "Update version, build number, and sign APK"
|
||||
lane :update_version_build_and_sign_apk do
|
||||
Dir.chdir(project_root) do
|
||||
build_number = ENV['BUILD_NUMBER']
|
||||
UI.user_error!("BUILD_NUMBER environment variable is missing") if build_number.nil?
|
||||
|
||||
# Get the version name from build.gradle
|
||||
|
||||
# Extract versionName from build.gradle
|
||||
version_name = sh("grep versionName android/app/build.gradle | awk '{print $2}' | tr -d '\"'").strip
|
||||
|
||||
# Manually update the versionCode in build.gradle
|
||||
|
||||
# Update versionCode in build.gradle
|
||||
UI.message("Updating versionCode in build.gradle to #{build_number}...")
|
||||
build_gradle_path = "android/app/build.gradle"
|
||||
build_gradle_contents = File.read(build_gradle_path)
|
||||
new_build_gradle_contents = build_gradle_contents.gsub(/versionCode\s+\d+/, "versionCode #{build_number}")
|
||||
File.write(build_gradle_path, new_build_gradle_contents)
|
||||
|
||||
# Get the branch name and default to 'master' if empty
|
||||
|
||||
# Determine branch name
|
||||
branch_name = ENV['GITHUB_HEAD_REF'] || `git rev-parse --abbrev-ref HEAD`.strip.gsub(/[\/\\:?*"<>|]/, '_')
|
||||
if branch_name.nil? || branch_name.empty?
|
||||
branch_name = 'master'
|
||||
end
|
||||
|
||||
# Append branch name only if it's not 'master'
|
||||
if branch_name != 'master'
|
||||
signed_apk_name = "BlueWallet-#{version_name}-#{build_number}-#{branch_name}.apk"
|
||||
else
|
||||
signed_apk_name = "BlueWallet-#{version_name}-#{build_number}.apk"
|
||||
end
|
||||
|
||||
# Continue with the build process
|
||||
branch_name = 'master' if branch_name.nil? || branch_name.empty?
|
||||
|
||||
# Define APK name based on branch
|
||||
signed_apk_name = branch_name != 'master' ? "BlueWallet-#{version_name}-#{build_number}-#{branch_name}.apk" : "BlueWallet-#{version_name}-#{build_number}.apk"
|
||||
|
||||
# Build APK
|
||||
Dir.chdir("android") do
|
||||
UI.message("Building APK...")
|
||||
gradle(
|
||||
task: "assembleRelease",
|
||||
project_dir: "android"
|
||||
)
|
||||
gradle(task: "assembleRelease", project_dir: "android")
|
||||
UI.message("APK build completed.")
|
||||
|
||||
# Define the output paths
|
||||
|
||||
# Define paths
|
||||
unsigned_apk_path = "app/build/outputs/apk/release/app-release-unsigned.apk"
|
||||
signed_apk_path = "app/build/outputs/apk/release/#{signed_apk_name}"
|
||||
|
||||
# Rename the unsigned APK to include the version and build number
|
||||
|
||||
# Rename APK
|
||||
if File.exist?(unsigned_apk_path)
|
||||
UI.message("Renaming APK to #{signed_apk_name}...")
|
||||
FileUtils.mv(unsigned_apk_path, signed_apk_path)
|
||||
|
@ -78,8 +78,8 @@ platform :android do
|
|||
UI.error("Unsigned APK not found at path: #{unsigned_apk_path}")
|
||||
next
|
||||
end
|
||||
|
||||
# Sign the APK using apksigner
|
||||
|
||||
# Sign APK
|
||||
UI.message("Signing APK with apksigner...")
|
||||
apksigner_path = "#{ENV['ANDROID_HOME']}/build-tools/34.0.0/apksigner"
|
||||
sh("#{apksigner_path} sign --ks ./bluewallet-release-key.keystore --ks-pass=pass:#{ENV['KEYSTORE_PASSWORD']} #{signed_apk_path}")
|
||||
|
@ -91,33 +91,32 @@ platform :android do
|
|||
desc "Upload APK to BrowserStack and post result as PR comment"
|
||||
lane :upload_to_browserstack_and_comment do
|
||||
Dir.chdir(project_root) do
|
||||
# Fetch the APK path from environment variables
|
||||
# Determine APK path
|
||||
apk_path = ENV['APK_PATH']
|
||||
|
||||
# Attempt to find the APK if not provided
|
||||
if apk_path.nil? || apk_path.empty?
|
||||
UI.message("No APK path provided, attempting to find the artifact...")
|
||||
UI.message("No APK path provided, searching for APK...")
|
||||
apk_path = `find ./ -name "*.apk"`.strip
|
||||
UI.user_error!("No APK file found") if apk_path.nil? || apk_path.empty?
|
||||
end
|
||||
|
||||
|
||||
# Upload to BrowserStack
|
||||
UI.message("Uploading APK to BrowserStack: #{apk_path}...")
|
||||
upload_to_browserstack_app_live(
|
||||
file_path: apk_path,
|
||||
browserstack_username: ENV['BROWSERSTACK_USERNAME'],
|
||||
browserstack_access_key: ENV['BROWSERSTACK_ACCESS_KEY']
|
||||
)
|
||||
|
||||
# Extract the BrowserStack URL from the output
|
||||
|
||||
# Extract BrowserStack URL
|
||||
app_url = ENV['BROWSERSTACK_LIVE_APP_ID']
|
||||
UI.user_error!("BrowserStack upload failed, no app URL returned") if app_url.nil? || app_url.empty?
|
||||
|
||||
# Prepare necessary values for the PR comment
|
||||
|
||||
# Prepare PR comment
|
||||
apk_filename = File.basename(apk_path)
|
||||
apk_download_url = ENV['APK_OUTPUT_PATH'] # Assuming this path is accessible to the PR
|
||||
apk_download_url = ENV['APK_OUTPUT_PATH'] # Ensure this path is accessible
|
||||
browserstack_hashed_id = app_url.gsub('bs://', '')
|
||||
pr_number = ENV['GITHUB_PR_NUMBER']
|
||||
|
||||
|
||||
comment = <<~COMMENT
|
||||
### APK Successfully Uploaded to BrowserStack
|
||||
|
||||
|
@ -127,16 +126,17 @@ platform :android do
|
|||
- [Google Pixel 7 (Android 13.0)](https://app-live.browserstack.com/dashboard#os=android&os_version=13.0&device=Google+Pixel+7&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true)
|
||||
- [Google Pixel 8 (Android 14.0)](https://app-live.browserstack.com/dashboard#os=android&os_version=14.0&device=Google+Pixel+8&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true)
|
||||
- [Google Pixel 3a (Android 9.0)](https://app-live.browserstack.com/dashboard#os=android&os_version=9.0&device=Google+Pixel+3a&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true)
|
||||
|
||||
|
||||
- [Samsung Galaxy Z Fold 5 (Android 13.0)](https://app-live.browserstack.com/dashboard#os=android&os_version=13.0&device=Samsung+Galaxy+Z+Fold+5&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true)
|
||||
- [Samsung Galaxy Z Fold 6 (Android 14.0)](https://app-live.browserstack.com/dashboard#os=android&os_version=14.0&device=Samsung+Galaxy+Z+Fold+6&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true)
|
||||
- [Samsung Galaxy Tab S9 (Android 13.0)](https://app-live.browserstack.com/dashboard#os=android&os_version=13.0&device=Samsung+Galaxy+Tab+S9&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true)
|
||||
- [Samsung Galaxy Note 9 (Android 8.1)](https://app-live.browserstack.com/dashboard#os=android&os_version=8.1&device=Samsung+Galaxy+Note+9&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true)
|
||||
|
||||
|
||||
**Filename**: [#{apk_filename}](#{apk_download_url})
|
||||
**BrowserStack App URL**: #{app_url}
|
||||
COMMENT
|
||||
|
||||
|
||||
# Post PR comment if PR number is available
|
||||
if pr_number
|
||||
begin
|
||||
sh("GH_TOKEN=#{ENV['GH_TOKEN']} gh pr comment #{pr_number} --body '#{comment}'")
|
||||
|
@ -149,36 +149,34 @@ platform :android do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# ===========================
|
||||
# iOS Lanes
|
||||
# ===========================
|
||||
|
||||
platform :ios do
|
||||
|
||||
before_all do |lane, options|
|
||||
UI.message("Setting up for all lanes...")
|
||||
UI.message("Discarding all untracked changes before running any lane...")
|
||||
sh("git clean -fd")
|
||||
sh("git checkout -- .")
|
||||
end
|
||||
|
||||
desc "Register new devices from a file"
|
||||
lane :register_devices_from_txt do
|
||||
UI.message("Registering new devices from file...")
|
||||
|
||||
csv_path = "../../devices.txt" # Update this with the actual path to your file
|
||||
|
||||
# Registering devices using the devices_file parameter
|
||||
# Register devices using the devices_file parameter
|
||||
register_devices(
|
||||
devices_file: csv_path
|
||||
)
|
||||
|
||||
UI.message("Devices registered successfully.")
|
||||
|
||||
# Update provisioning profiles for all app identifiers
|
||||
app_identifiers.each do |app_identifier|
|
||||
match(
|
||||
type: "development",
|
||||
app_identifier: app_identifier,
|
||||
readonly: false, # This will regenerate the provisioning profile if needed
|
||||
readonly: false, # Regenerate provisioning profile if needed
|
||||
force_for_new_devices: true,
|
||||
clone_branch_directly: true
|
||||
)
|
||||
|
@ -190,7 +188,7 @@ platform :ios do
|
|||
desc "Create a temporary keychain"
|
||||
lane :create_temp_keychain do
|
||||
UI.message("Creating a temporary keychain...")
|
||||
|
||||
|
||||
create_keychain(
|
||||
name: "temp_keychain",
|
||||
password: ENV["KEYCHAIN_PASSWORD"],
|
||||
|
@ -199,32 +197,23 @@ platform :ios do
|
|||
timeout: 3600,
|
||||
lock_when_sleeps: true
|
||||
)
|
||||
|
||||
|
||||
UI.message("Temporary keychain created successfully.")
|
||||
end
|
||||
|
||||
desc "Synchronize certificates and provisioning profiles"
|
||||
lane :setup_provisioning_profiles do |options|
|
||||
lane :setup_provisioning_profiles do
|
||||
UI.message("Setting up provisioning profiles...")
|
||||
target_to_app_identifier = {
|
||||
'BlueWallet' => 'io.bluewallet.bluewallet',
|
||||
'BlueWalletWatch' => 'io.bluewallet.bluewallet.watch',
|
||||
'BlueWalletWatchExtension' => 'io.bluewallet.bluewallet.watch.extension',
|
||||
'Stickers' => 'io.bluewallet.bluewallet.Stickers',
|
||||
'MarketWidget' => 'io.bluewallet.bluewallet.MarketWidget'
|
||||
}
|
||||
|
||||
platform = options[:platform] || "ios" # Default to iOS if not specified
|
||||
|
||||
# Remove local master branch if it exists (Exit status: 128 - 'fatal: a branch named 'master' already exists')
|
||||
sh("git branch -D master || true")
|
||||
|
||||
target_to_app_identifier.each do |target, app_identifier|
|
||||
platform = "ios"
|
||||
|
||||
# Iterate over app identifiers to fetch provisioning profiles
|
||||
app_identifiers.each do |app_identifier|
|
||||
match(
|
||||
git_basic_authorization: ENV["GIT_ACCESS_TOKEN"],
|
||||
git_url: ENV["GIT_URL"],
|
||||
type: "appstore",
|
||||
clone_branch_directly: true, # Skip if the branch already exists (Exit 128 error)
|
||||
clone_branch_directly: true, # Skip if the branch already exists
|
||||
platform: platform,
|
||||
app_identifier: app_identifier,
|
||||
team_id: ENV["ITC_TEAM_ID"],
|
||||
|
@ -255,7 +244,6 @@ platform :ios do
|
|||
app_identifier: app_identifiers,
|
||||
readonly: true,
|
||||
clone_branch_directly: true
|
||||
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -306,94 +294,116 @@ platform :ios do
|
|||
cocoapods(podfile: "ios/Podfile")
|
||||
end
|
||||
|
||||
desc "Build the application"
|
||||
lane :build_app_lane do
|
||||
UI.message("Building the application...")
|
||||
build_app(
|
||||
scheme: "BlueWallet",
|
||||
workspace: "ios/BlueWallet.xcworkspace",
|
||||
export_method: "app-store",
|
||||
include_bitcode: false,
|
||||
configuration: "Release",
|
||||
skip_profile_detection: true,
|
||||
include_symbols: true,
|
||||
export_team_id: ENV["ITC_TEAM_ID"],
|
||||
export_options: {
|
||||
signingStyle: "manual",
|
||||
provisioningProfiles: {
|
||||
'io.bluewallet.bluewallet' => 'match AppStore io.bluewallet.bluewallet',
|
||||
'io.bluewallet.bluewallet.watch' => 'match AppStore io.bluewallet.bluewallet.watch',
|
||||
'io.bluewallet.bluewallet.watch.extension' => 'match AppStore io.bluewallet.bluewallet.watch.extension',
|
||||
'io.bluewallet.bluewallet.Stickers' => 'match AppStore io.bluewallet.bluewallet.Stickers',
|
||||
'io.bluewallet.bluewallet.MarketWidget' => 'match AppStore io.bluewallet.bluewallet.MarketWidget'
|
||||
}
|
||||
},
|
||||
xcargs: "GCC_PREPROCESSOR_DEFINITIONS='$(inherited) VERBOSE_LOGGING=1'",
|
||||
output_directory: "./build", # Directory where the IPA file will be stored
|
||||
desc "Upload IPA to TestFlight"
|
||||
lane :upload_to_testflight_lane do
|
||||
ipa_path = ENV['IPA_OUTPUT_PATH']
|
||||
changelog = ENV["LATEST_COMMIT_MESSAGE"]
|
||||
|
||||
output_name: "BlueWallet_#{ENV['PROJECT_VERSION']}_#{ENV['NEW_BUILD_NUMBER']}.ipa",
|
||||
buildlog_path: "./build_logs"
|
||||
)
|
||||
# Check if IPA exists before proceeding
|
||||
if ipa_path.nil? || ipa_path.empty? || !File.exist?(ipa_path)
|
||||
UI.user_error!("IPA file not found at path: #{ipa_path}")
|
||||
end
|
||||
|
||||
desc "Upload to TestFlight without Processing Wait"
|
||||
lane :upload_to_testflight_lane do
|
||||
attempts = 0
|
||||
max_attempts = 3
|
||||
UI.message("Uploading IPA to TestFlight from path: #{ipa_path}")
|
||||
|
||||
upload_to_testflight(
|
||||
api_key_path: "./appstore_api_key.json",
|
||||
ipa: ipa_path,
|
||||
skip_waiting_for_build_processing: true, # Do not wait for processing
|
||||
changelog: changelog
|
||||
)
|
||||
|
||||
UI.success("Successfully uploaded IPA to TestFlight!")
|
||||
end
|
||||
|
||||
desc "Build the iOS app"
|
||||
lane :build_app_lane do
|
||||
Dir.chdir(project_root) do
|
||||
UI.message("Building the application from: #{Dir.pwd}")
|
||||
|
||||
workspace_path = File.join(project_root, "ios", "BlueWallet.xcworkspace")
|
||||
export_options_path = File.join(project_root, "ios", "export_options.plist")
|
||||
|
||||
begin
|
||||
UI.message("Uploading to TestFlight without processing wait...")
|
||||
changelog = ENV["LATEST_COMMIT_MESSAGE"]
|
||||
ipa_path = "./BlueWallet_#{ENV['PROJECT_VERSION']}_#{ENV['NEW_BUILD_NUMBER']}.ipa"
|
||||
|
||||
upload_to_testflight(
|
||||
api_key_path: "./appstore_api_key.json",
|
||||
ipa: ipa_path,
|
||||
skip_waiting_for_build_processing: true, # Do not wait for processing
|
||||
changelog: changelog
|
||||
build_ios_app(
|
||||
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 => exception
|
||||
attempts += 1
|
||||
if attempts <= max_attempts
|
||||
wait_time = 180 # 3 minutes in seconds
|
||||
UI.message("Attempt ##{attempts} failed with error: #{exception.message}. Waiting #{wait_time} seconds before trying again...")
|
||||
sleep(wait_time)
|
||||
retry
|
||||
else
|
||||
UI.error("Failed after #{max_attempts} attempts. Error: #{exception.message}")
|
||||
raise exception
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
desc "Deploy to TestFlight"
|
||||
lane :deploy do |options|
|
||||
UI.message("Starting build process...")
|
||||
|
||||
# Update the WWDR certificate
|
||||
update_wwdr_certificate
|
||||
|
||||
setup_app_store_connect_api_key
|
||||
setup_provisioning_profiles
|
||||
clear_derived_data_lane
|
||||
increment_build_number_lane
|
||||
|
||||
unless File.directory?("Pods")
|
||||
install_pods
|
||||
rescue => e
|
||||
UI.user_error!("build_ios_app failed: #{e.message}")
|
||||
end
|
||||
|
||||
build_app_lane
|
||||
upload_to_testflight_lane
|
||||
# Use File.join to construct paths without extra slashes
|
||||
ipa_path = lane_context[SharedValues::IPA_OUTPUT_PATH]
|
||||
|
||||
# Clean up and delete the temporary keychain
|
||||
delete_keychain(name: "temp_keychain")
|
||||
if ipa_path && File.exist?(ipa_path)
|
||||
UI.message("IPA successfully found at: #{ipa_path}")
|
||||
ENV['IPA_OUTPUT_PATH'] = ipa_path
|
||||
sh("echo 'IPA_OUTPUT_PATH=#{ipa_path}' >> $GITHUB_ENV") # Export for GitHub Actions
|
||||
else
|
||||
UI.user_error!("IPA not found after build_ios_app.")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Mark deployment as completed for the current commit
|
||||
last_commit = last_git_commit
|
||||
already_built_flag = ".already_built_#{last_commit[:sha]}"
|
||||
File.write(already_built_flag, Time.now.to_s)
|
||||
# ===========================
|
||||
# Global Lanes
|
||||
# ===========================
|
||||
|
||||
|
||||
|
||||
desc "Deploy to TestFlight"
|
||||
lane :deploy do |options|
|
||||
UI.message("Starting deployment process...")
|
||||
|
||||
# Update WWDR Certificate
|
||||
update_wwdr_certificate
|
||||
|
||||
# Setup App Store Connect API Key
|
||||
setup_app_store_connect_api_key
|
||||
|
||||
# Setup Provisioning Profiles
|
||||
setup_provisioning_profiles
|
||||
|
||||
# Clear Derived Data
|
||||
clear_derived_data_lane
|
||||
|
||||
# Increment Build Number
|
||||
increment_build_number_lane
|
||||
|
||||
# Install CocoaPods if not already installed
|
||||
unless File.directory?("Pods")
|
||||
install_pods
|
||||
end
|
||||
|
||||
desc "Update 'What's New' section in App Store Connect for the 'Prepare for Submission' version"
|
||||
# Build the iOS App
|
||||
build_app_lane
|
||||
|
||||
# Upload IPA to TestFlight
|
||||
upload_to_testflight_lane
|
||||
|
||||
# Clean up and delete the temporary keychain
|
||||
delete_keychain(name: "temp_keychain")
|
||||
|
||||
# Mark deployment as completed for the current commit
|
||||
last_commit = last_git_commit
|
||||
already_built_flag = ".already_built_#{last_commit[:sha]}"
|
||||
File.write(already_built_flag, Time.now.to_s)
|
||||
end
|
||||
|
||||
desc "Update 'What's New' section in App Store Connect for the 'Prepare for Submission' version"
|
||||
lane :update_release_notes do |options|
|
||||
require 'spaceship'
|
||||
|
||||
|
@ -402,16 +412,13 @@ lane :update_release_notes do |options|
|
|||
|
||||
app = Spaceship::ConnectAPI::App.find(app_identifiers.first)
|
||||
|
||||
unless app
|
||||
UI.user_error!("Could not find the app with identifier: #{app_identifiers.first}")
|
||||
end
|
||||
UI.user_error!("Could not find the app with identifier: #{app_identifiers.first}") unless app
|
||||
|
||||
# Retry logic for fetching or creating the edit version
|
||||
retries = 5
|
||||
begin
|
||||
prepare_version = app.get_edit_app_store_version(platform: Spaceship::ConnectAPI::Platform::IOS)
|
||||
|
||||
# If no "Prepare for Submission" version is found, create a new one
|
||||
if prepare_version.nil?
|
||||
UI.message("No version in 'Prepare for Submission' found. Creating a new version...")
|
||||
latest_version = app.get_latest_version(platform: Spaceship::ConnectAPI::Platform::IOS)
|
||||
|
@ -436,12 +443,11 @@ lane :update_release_notes do |options|
|
|||
# Extract existing metadata
|
||||
localized_metadata = prepare_version.get_app_store_version_localizations
|
||||
|
||||
# Get all the enabled locales for the app version
|
||||
# Get enabled locales
|
||||
enabled_locales = localized_metadata.map(&:locale)
|
||||
|
||||
# Define valid language codes and filter them based on enabled locales
|
||||
# Define release notes
|
||||
release_notes_text = options[:release_notes]
|
||||
|
||||
if release_notes_text.nil? || release_notes_text.strip.empty?
|
||||
release_notes_path = "../../release-notes.txt"
|
||||
unless File.exist?(release_notes_path)
|
||||
|
@ -451,6 +457,7 @@ lane :update_release_notes do |options|
|
|||
release_notes_text = File.read(release_notes_path)
|
||||
end
|
||||
|
||||
# Define localized release notes
|
||||
localized_release_notes = {
|
||||
'en-US' => release_notes_text, # English (U.S.) - Primary
|
||||
'ar-SA' => release_notes_text, # Arabic
|
||||
|
@ -479,7 +486,7 @@ lane :update_release_notes do |options|
|
|||
'th' => release_notes_text, # Thai
|
||||
}.select { |locale, _| enabled_locales.include?(locale) } # Only include enabled locales
|
||||
|
||||
# Review what's going to be updated
|
||||
# Review release notes updates
|
||||
UI.message("Review the following release notes updates:")
|
||||
localized_release_notes.each do |locale, notes|
|
||||
UI.message("Locale: #{locale} - Notes: #{notes}")
|
||||
|
@ -487,21 +494,17 @@ lane :update_release_notes do |options|
|
|||
|
||||
unless options[:force_yes]
|
||||
confirm = UI.confirm("Do you want to proceed with these release notes updates?")
|
||||
unless confirm
|
||||
UI.user_error!("User aborted the lane.")
|
||||
end
|
||||
UI.user_error!("User aborted the lane.") unless confirm
|
||||
end
|
||||
|
||||
# Update release notes in App Store Connect and skip all other metadata
|
||||
# Update release notes in App Store Connect
|
||||
localized_release_notes.each do |locale, notes|
|
||||
app_store_version_localization = localized_metadata.find { |loc| loc.locale == locale }
|
||||
|
||||
if app_store_version_localization
|
||||
app_store_version_localization.update(attributes: { "whats_new" => notes })
|
||||
else
|
||||
UI.error("No localization found for locale #{locale}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
import { useEffect, useCallback } from 'react';
|
||||
// @ts-ignore: react-native-obscure is not in the type definition
|
||||
import Obscure from 'react-native-obscure';
|
||||
import { useSettings } from './context/useSettings';
|
||||
|
||||
export const usePrivacy = () => {
|
||||
const { isPrivacyBlurEnabled } = useSettings();
|
||||
|
||||
const enableBlur = useCallback(() => {
|
||||
if (!isPrivacyBlurEnabled) return;
|
||||
Obscure.activateObscure();
|
||||
}, [isPrivacyBlurEnabled]);
|
||||
|
||||
const disableBlur = useCallback(() => {
|
||||
Obscure.deactivateObscure();
|
||||
}, []); // This doesn't depend on the isPrivacyBlurEnabled value
|
||||
|
||||
useEffect(() => {
|
||||
// Automatically activate or deactivate on mount and when isPrivacyBlurEnabled changes
|
||||
if (isPrivacyBlurEnabled) {
|
||||
enableBlur();
|
||||
} else {
|
||||
disableBlur();
|
||||
}
|
||||
|
||||
// Cleanup function to deactivate obscure when the component unmounts
|
||||
return () => {
|
||||
disableBlur();
|
||||
};
|
||||
}, [isPrivacyBlurEnabled, enableBlur, disableBlur]);
|
||||
|
||||
return { enableBlur, disableBlur };
|
||||
};
|
||||
|
||||
export default usePrivacy;
|
|
@ -1,35 +0,0 @@
|
|||
import { useEffect, useCallback } from 'react';
|
||||
// @ts-ignore: react-native-obscure is not in the type definition
|
||||
import { enabled } from 'react-native-privacy-snapshot';
|
||||
import { useSettings } from './context/useSettings';
|
||||
|
||||
export const usePrivacy = () => {
|
||||
const { isPrivacyBlurEnabled } = useSettings();
|
||||
|
||||
const enableBlur = useCallback(() => {
|
||||
if (!isPrivacyBlurEnabled) return;
|
||||
enabled(true);
|
||||
}, [isPrivacyBlurEnabled]);
|
||||
|
||||
const disableBlur = useCallback(() => {
|
||||
enabled(false);
|
||||
}, []); // This doesn't depend on the isPrivacyBlurEnabled value
|
||||
|
||||
useEffect(() => {
|
||||
// Automatically activate or deactivate on mount and when isPrivacyBlurEnabled changes
|
||||
if (isPrivacyBlurEnabled) {
|
||||
enableBlur();
|
||||
} else {
|
||||
disableBlur();
|
||||
}
|
||||
|
||||
// Cleanup function to deactivate obscure when the component unmounts
|
||||
return () => {
|
||||
disableBlur();
|
||||
};
|
||||
}, [isPrivacyBlurEnabled, enableBlur, disableBlur]);
|
||||
|
||||
return { enableBlur, disableBlur };
|
||||
};
|
||||
|
||||
export default usePrivacy;
|
|
@ -1,9 +0,0 @@
|
|||
export const usePrivacy = () => {
|
||||
const enableBlur = () => {};
|
||||
|
||||
const disableBlur = () => {};
|
||||
|
||||
return { enableBlur, disableBlur };
|
||||
};
|
||||
|
||||
export default usePrivacy;
|
|
@ -2,369 +2,322 @@
|
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>11</string>
|
||||
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||
<array>
|
||||
<string>io.bluewallet.bluewallet.fetchTxsForWallet</string>
|
||||
</array>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>BlueWallet</string>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>PSBT</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>io.bluewallet.psbt</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>TXN</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>io.bluewallet.psbt.txn</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>ELECTRUMBACKUP</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>io.bluewallet.backup</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>BW COSIGNER</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>io.bluewallet.bwcosigner</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>bitcoin</string>
|
||||
<string>lightning</string>
|
||||
<string>bluewallet</string>
|
||||
<string>lapp</string>
|
||||
<string>blue</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.finance</string>
|
||||
<key>LSApplicationQueriesSchemes</key>
|
||||
<array>
|
||||
<string>https</string>
|
||||
<string>http</string>
|
||||
</array>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<false/>
|
||||
<key>NSAllowsLocalNetworking</key>
|
||||
<true/>
|
||||
<key>NSExceptionDomains</key>
|
||||
<dict>
|
||||
<key>localhost</key>
|
||||
<dict>
|
||||
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>onion</key>
|
||||
<dict>
|
||||
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
||||
<true/>
|
||||
<key>NSIncludesSubdomains</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>tailscale.net</key>
|
||||
<dict>
|
||||
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
||||
<true/>
|
||||
<key>NSIncludesSubdomains</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>ts.net</key>
|
||||
<dict>
|
||||
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
||||
<true/>
|
||||
<key>NSIncludesSubdomains</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>In order to quickly scan the recipient's address, we need your permission to use the camera to scan their QR Code.</string>
|
||||
<key>NSFaceIDUsageDescription</key>
|
||||
<string>In order to use FaceID please confirm your permission.</string>
|
||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||
<string>Your authorization is required to save this image.</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>In order to import an image for scanning, we need your permission to access your photo library.</string>
|
||||
<key>NSUserActivityTypes</key>
|
||||
<array>
|
||||
<string>io.bluewallet.bluewallet.receiveonchain</string>
|
||||
<string>io.bluewallet.bluewallet.xpub</string>
|
||||
</array>
|
||||
<key>UIAppFonts</key>
|
||||
<array>
|
||||
<string>Entypo.ttf</string>
|
||||
<string>FontAwesome.ttf</string>
|
||||
<string>FontAwesome5_Brands.ttf</string>
|
||||
<string>FontAwesome5_Regular.ttf</string>
|
||||
<string>FontAwesome5_Solid.ttf</string>
|
||||
<string>Ionicons.ttf</string>
|
||||
<string>MaterialIcons.ttf</string>
|
||||
<string>Octicons.ttf</string>
|
||||
</array>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>fetch</string>
|
||||
<string>processing</string>
|
||||
<string>remote-notification</string>
|
||||
</array>
|
||||
<key>UIFileSharingEnabled</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<true/>
|
||||
<key>UTExportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>Partially Signed Bitcoin Transaction</string>
|
||||
<key>UTTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>io.bluewallet.psbt</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>psbt</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>BW COSIGNER</string>
|
||||
<key>UTTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>io.bluewallet.bwcosigner</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>bwcosigner</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>Bitcoin Transaction</string>
|
||||
<key>UTTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>io.bluewallet.psbt.txn</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>txn</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
</array>
|
||||
<key>UTImportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.text</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>JSON File</string>
|
||||
<key>UTTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>json</string>
|
||||
</array>
|
||||
<key>public.mime-type</key>
|
||||
<array>
|
||||
<string>application/json</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>Partially Signed Bitcoin Transaction</string>
|
||||
<key>UTTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>io.bluewallet.psbt</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>psbt</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>Bitcoin Transaction</string>
|
||||
<key>UTTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>io.bluewallet.psbt.txn</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>txn</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>Electrum Backup</string>
|
||||
<key>UTTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>io.bluewallet.backup</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>backup</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>BW COSIGNER</string>
|
||||
<key>UTTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>io.bluewallet.bwcosigner</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>bwcosigner</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
</array>
|
||||
<key>bugsnag</key>
|
||||
<dict>
|
||||
<key>apiKey</key>
|
||||
<string>17ba9059f676f1cc4f45d98182388b01</string>
|
||||
</dict>
|
||||
<key>FIREBASE_ANALYTICS_COLLECTION_ENABLED</key>
|
||||
<false/>
|
||||
<key>FIREBASE_MESSAGING_AUTO_INIT_ENABLED</key>
|
||||
<false/>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>11</string>
|
||||
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||
<array>
|
||||
<string>io.bluewallet.bluewallet.fetchTxsForWallet</string>
|
||||
</array>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>BlueWallet</string>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<!-- PSBT file type -->
|
||||
<dict>
|
||||
<key>CFBundleTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>PSBT</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>io.bluewallet.psbt</string>
|
||||
</array>
|
||||
</dict>
|
||||
<!-- TXN file type -->
|
||||
<dict>
|
||||
<key>CFBundleTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>TXN</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>io.bluewallet.psbt.txn</string>
|
||||
</array>
|
||||
</dict>
|
||||
<!-- Electrum Backup file type -->
|
||||
<dict>
|
||||
<key>CFBundleTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>ELECTRUMBACKUP</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>io.bluewallet.backup</string>
|
||||
</array>
|
||||
</dict>
|
||||
<!-- BW COSIGNER file type -->
|
||||
<dict>
|
||||
<key>CFBundleTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>BW COSIGNER</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>io.bluewallet.bwcosigner</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>bitcoin</string>
|
||||
<string>lightning</string>
|
||||
<string>bluewallet</string>
|
||||
<string>lapp</string>
|
||||
<string>blue</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.finance</string>
|
||||
<key>LSApplicationQueriesSchemes</key>
|
||||
<array>
|
||||
<string>https</string>
|
||||
<string>http</string>
|
||||
</array>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<false/>
|
||||
<key>NSAllowsLocalNetworking</key>
|
||||
<true/>
|
||||
<key>NSExceptionDomains</key>
|
||||
<dict>
|
||||
<key>localhost</key>
|
||||
<dict>
|
||||
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>onion</key>
|
||||
<dict>
|
||||
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
||||
<true/>
|
||||
<key>NSIncludesSubdomains</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>tailscale.net</key>
|
||||
<dict>
|
||||
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
||||
<true/>
|
||||
<key>NSIncludesSubdomains</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>ts.net</key>
|
||||
<dict>
|
||||
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
||||
<true/>
|
||||
<key>NSIncludesSubdomains</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>In order to quickly scan the recipient's address, we need your permission to use the camera to scan their QR Code.</string>
|
||||
<key>NSFaceIDUsageDescription</key>
|
||||
<string>In order to use FaceID please confirm your permission.</string>
|
||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||
<string>Your authorization is required to save this image.</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>In order to import an image for scanning, we need your permission to access your photo library.</string>
|
||||
<key>NSUserActivityTypes</key>
|
||||
<array>
|
||||
<string>io.bluewallet.bluewallet.receiveonchain</string>
|
||||
<string>io.bluewallet.bluewallet.xpub</string>
|
||||
</array>
|
||||
<key>UIAppFonts</key>
|
||||
<array>
|
||||
<string>Entypo.ttf</string>
|
||||
<string>FontAwesome.ttf</string>
|
||||
<string>FontAwesome5_Brands.ttf</string>
|
||||
<string>FontAwesome5_Regular.ttf</string>
|
||||
<string>FontAwesome5_Solid.ttf</string>
|
||||
<string>Ionicons.ttf</string>
|
||||
<string>MaterialIcons.ttf</string>
|
||||
<string>Octicons.ttf</string>
|
||||
</array>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>fetch</string>
|
||||
<string>processing</string>
|
||||
<string>remote-notification</string>
|
||||
</array>
|
||||
<key>UIFileSharingEnabled</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<true/>
|
||||
|
||||
<!-- Define exported types (UTIs) for file types -->
|
||||
<key>UTExportedTypeDeclarations</key>
|
||||
<array>
|
||||
<!-- PSBT -->
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>Partially Signed Bitcoin Transaction</string>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>io.bluewallet.psbt</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>psbt</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<!-- BW Cosigner -->
|
||||
<dict>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>BW COSIGNER</string>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>io.bluewallet.bwcosigner</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>bwcosigner</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<!-- TXN -->
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>Bitcoin Transaction</string>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>io.bluewallet.psbt.txn</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>txn</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<!-- Electrum Backup -->
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>Electrum Backup</string>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>io.bluewallet.backup</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>backup</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
</array>
|
||||
|
||||
<!-- Define imported types for other files -->
|
||||
<key>UTImportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.text</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>JSON File</string>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>public.json</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>json</string>
|
||||
</array>
|
||||
<key>public.mime-type</key>
|
||||
<array>
|
||||
<string>application/json</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
</array>
|
||||
|
||||
<key>bugsnag</key>
|
||||
<dict>
|
||||
<key>apiKey</key>
|
||||
<string>17ba9059f676f1cc4f45d98182388b01</string>
|
||||
</dict>
|
||||
|
||||
<key>FIREBASE_ANALYTICS_COLLECTION_ENABLED</key>
|
||||
<false/>
|
||||
<key>FIREBASE_MESSAGING_AUTO_INIT_ENABLED</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
@ -1292,8 +1292,6 @@ PODS:
|
|||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- react-native-idle-timer (2.2.2):
|
||||
- React-Core
|
||||
- react-native-image-picker (7.1.2):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
|
@ -1317,7 +1315,7 @@ PODS:
|
|||
- Yoga
|
||||
- react-native-ios-context-menu (1.15.3):
|
||||
- React-Core
|
||||
- react-native-menu (1.1.2):
|
||||
- react-native-menu (1.1.3):
|
||||
- React
|
||||
- react-native-qrcode-local-image (1.0.4):
|
||||
- React
|
||||
|
@ -1325,6 +1323,8 @@ PODS:
|
|||
- React-Core
|
||||
- react-native-safe-area-context (4.11.0):
|
||||
- React-Core
|
||||
- react-native-screen-capture (0.2.3):
|
||||
- React
|
||||
- react-native-secure-key-store (2.0.10):
|
||||
- React-Core
|
||||
- react-native-tcp-socket (6.2.0):
|
||||
|
@ -1635,8 +1635,6 @@ PODS:
|
|||
- React-Core
|
||||
- RNPermissions (4.1.5):
|
||||
- React-Core
|
||||
- RNPrivacySnapshot (1.0.0):
|
||||
- React
|
||||
- RNQuickAction (0.3.13):
|
||||
- React
|
||||
- RNRate (1.2.12):
|
||||
|
@ -1662,7 +1660,7 @@ PODS:
|
|||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- RNReanimated (3.15.4):
|
||||
- RNReanimated (3.15.5):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- hermes-engine
|
||||
|
@ -1682,10 +1680,10 @@ PODS:
|
|||
- ReactCodegen
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- RNReanimated/reanimated (= 3.15.4)
|
||||
- RNReanimated/worklets (= 3.15.4)
|
||||
- RNReanimated/reanimated (= 3.15.5)
|
||||
- RNReanimated/worklets (= 3.15.5)
|
||||
- Yoga
|
||||
- RNReanimated/reanimated (3.15.4):
|
||||
- RNReanimated/reanimated (3.15.5):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- hermes-engine
|
||||
|
@ -1706,7 +1704,7 @@ PODS:
|
|||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- RNReanimated/worklets (3.15.4):
|
||||
- RNReanimated/worklets (3.15.5):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- hermes-engine
|
||||
|
@ -1845,13 +1843,13 @@ DEPENDENCIES:
|
|||
- react-native-blue-crypto (from `../node_modules/react-native-blue-crypto`)
|
||||
- react-native-bw-file-access (from `../blue_modules/react-native-bw-file-access`)
|
||||
- react-native-document-picker (from `../node_modules/react-native-document-picker`)
|
||||
- react-native-idle-timer (from `../node_modules/react-native-idle-timer`)
|
||||
- react-native-image-picker (from `../node_modules/react-native-image-picker`)
|
||||
- react-native-ios-context-menu (from `../node_modules/react-native-ios-context-menu`)
|
||||
- "react-native-menu (from `../node_modules/@react-native-menu/menu`)"
|
||||
- "react-native-qrcode-local-image (from `../node_modules/@remobile/react-native-qrcode-local-image`)"
|
||||
- react-native-randombytes (from `../node_modules/react-native-randombytes`)
|
||||
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
|
||||
- react-native-screen-capture (from `../node_modules/react-native-screen-capture`)
|
||||
- react-native-secure-key-store (from `../node_modules/react-native-secure-key-store`)
|
||||
- react-native-tcp-socket (from `../node_modules/react-native-tcp-socket`)
|
||||
- React-nativeconfig (from `../node_modules/react-native/ReactCommon`)
|
||||
|
@ -1893,7 +1891,6 @@ DEPENDENCIES:
|
|||
- RNKeychain (from `../node_modules/react-native-keychain`)
|
||||
- RNLocalize (from `../node_modules/react-native-localize`)
|
||||
- RNPermissions (from `../node_modules/react-native-permissions`)
|
||||
- RNPrivacySnapshot (from `../node_modules/react-native-privacy-snapshot`)
|
||||
- RNQuickAction (from `../node_modules/react-native-quick-actions`)
|
||||
- RNRate (from `../node_modules/react-native-rate`)
|
||||
- RNReactNativeHapticFeedback (from `../node_modules/react-native-haptic-feedback`)
|
||||
|
@ -1998,8 +1995,6 @@ EXTERNAL SOURCES:
|
|||
:path: "../blue_modules/react-native-bw-file-access"
|
||||
react-native-document-picker:
|
||||
:path: "../node_modules/react-native-document-picker"
|
||||
react-native-idle-timer:
|
||||
:path: "../node_modules/react-native-idle-timer"
|
||||
react-native-image-picker:
|
||||
:path: "../node_modules/react-native-image-picker"
|
||||
react-native-ios-context-menu:
|
||||
|
@ -2012,6 +2007,8 @@ EXTERNAL SOURCES:
|
|||
:path: "../node_modules/react-native-randombytes"
|
||||
react-native-safe-area-context:
|
||||
:path: "../node_modules/react-native-safe-area-context"
|
||||
react-native-screen-capture:
|
||||
:path: "../node_modules/react-native-screen-capture"
|
||||
react-native-secure-key-store:
|
||||
:path: "../node_modules/react-native-secure-key-store"
|
||||
react-native-tcp-socket:
|
||||
|
@ -2094,8 +2091,6 @@ EXTERNAL SOURCES:
|
|||
:path: "../node_modules/react-native-localize"
|
||||
RNPermissions:
|
||||
:path: "../node_modules/react-native-permissions"
|
||||
RNPrivacySnapshot:
|
||||
:path: "../node_modules/react-native-privacy-snapshot"
|
||||
RNQuickAction:
|
||||
:path: "../node_modules/react-native-quick-actions"
|
||||
RNRate:
|
||||
|
@ -2164,13 +2159,13 @@ SPEC CHECKSUMS:
|
|||
react-native-blue-crypto: 23f1558ad3d38d7a2edb7e2f6ed1bc520ed93e56
|
||||
react-native-bw-file-access: b232fd1d902521ca046f3fc5990ab1465e1878d7
|
||||
react-native-document-picker: 7343222102ece8aec51390717f47ad7119c7921f
|
||||
react-native-idle-timer: ee2053f2cd458f6fef1db7bebe5098ca281cce07
|
||||
react-native-image-picker: 2fbbafdae7a7c6db9d25df2f2b1db4442d2ca2ad
|
||||
react-native-ios-context-menu: e529171ba760a1af7f2ef0729f5a7f4d226171c5
|
||||
react-native-menu: d32728a357dfb360cf01cd5979cf7713c5acbb95
|
||||
react-native-menu: c30eb7a85d7b04d51945f61ea8a8986ed366ac5c
|
||||
react-native-qrcode-local-image: 35ccb306e4265bc5545f813e54cc830b5d75bcfc
|
||||
react-native-randombytes: 421f1c7d48c0af8dbcd471b0324393ebf8fe7846
|
||||
react-native-safe-area-context: 851c62c48dce80ccaa5637b6aa5991a1bc36eca9
|
||||
react-native-screen-capture: 75db9b051c41fea47fa68665506e9257d4b1dadc
|
||||
react-native-secure-key-store: 910e6df6bc33cb790aba6ee24bc7818df1fe5898
|
||||
react-native-tcp-socket: 8c3e8bef909ab06c557eeb95363fe029391ff09d
|
||||
React-nativeconfig: 8c83d992b9cc7d75b5abe262069eaeea4349f794
|
||||
|
@ -2212,11 +2207,10 @@ SPEC CHECKSUMS:
|
|||
RNKeychain: bfe3d12bf4620fe488771c414530bf16e88f3678
|
||||
RNLocalize: 4f22418187ecd5ca693231093ff1d912d1b3c9bc
|
||||
RNPermissions: 9fa74223844f437bc309e112994859dc47194829
|
||||
RNPrivacySnapshot: 71919dde3c6a29dd332115409c2aec564afee8f4
|
||||
RNQuickAction: 6d404a869dc872cde841ad3147416a670d13fa93
|
||||
RNRate: ef3bcff84f39bb1d1e41c5593d3eea4aab2bd73a
|
||||
RNReactNativeHapticFeedback: 0d591ea1e150f36cb96d868d4e8d77272243d78a
|
||||
RNReanimated: 6e79f3e3b37a88cddfb38525e9652aabd7c4c750
|
||||
RNReanimated: 625f9e7f53cba61d7b3436e8e6e209d1dd4e6e9b
|
||||
RNScreens: 19719a9c326e925498ac3b2d35c4e50fe87afc06
|
||||
RNShare: 0fad69ae2d71de9d1f7b9a43acf876886a6cb99c
|
||||
RNSVG: 4590aa95758149fa27c5c83e54a6a466349a1688
|
||||
|
|
33
ios/export_options.plist
Normal file
33
ios/export_options.plist
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>method</key>
|
||||
<string>app-store</string>
|
||||
<key>signingStyle</key>
|
||||
<string>manual</string>
|
||||
<key>teamID</key>
|
||||
<string>A7W54YZ4WU</string>
|
||||
<key>uploadSymbols</key>
|
||||
<true/>
|
||||
<key>compileBitcode</key>
|
||||
<false/>
|
||||
<key>thinning</key>
|
||||
<string>none</string>
|
||||
<key>destination</key>
|
||||
<string>export</string>
|
||||
<key>provisioningProfiles</key>
|
||||
<dict>
|
||||
<key>io.bluewallet.bluewallet</key>
|
||||
<string>match AppStore io.bluewallet.bluewallet</string>
|
||||
<key>io.bluewallet.bluewallet.watch</key>
|
||||
<string>match AppStore io.bluewallet.bluewallet.watch</string>
|
||||
<key>io.bluewallet.bluewallet.watch.extension</key>
|
||||
<string>match AppStore io.bluewallet.bluewallet.watch.extension</string>
|
||||
<key>io.bluewallet.bluewallet.Stickers</key>
|
||||
<string>match AppStore io.bluewallet.bluewallet.Stickers</string>
|
||||
<key>io.bluewallet.bluewallet.MarketWidget</key>
|
||||
<string>match AppStore io.bluewallet.bluewallet.MarketWidget</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
|
@ -10,6 +10,7 @@
|
|||
"never": "nie",
|
||||
"of": "{number} von {total}",
|
||||
"ok": "OK",
|
||||
"enter_url": "URL eingeben",
|
||||
"storage_is_encrypted": "Zum Entschlüsseln des Speichers das Passwort eingeben.",
|
||||
"yes": "Ja",
|
||||
"no": "Nein",
|
||||
|
@ -25,7 +26,8 @@
|
|||
"pick_file": "Datei auswählen",
|
||||
"enter_amount": "Betrag eingeben",
|
||||
"qr_custom_input_button": "10x antippen für individuelle Eingabe",
|
||||
"unlock": "Entsperren"
|
||||
"unlock": "Entsperren",
|
||||
"suggested": "Vorgeschlagen"
|
||||
},
|
||||
"azteco": {
|
||||
"codeIs": "Dein Gutscheincode lautet",
|
||||
|
@ -206,8 +208,10 @@
|
|||
"performance_score": "Leistungskennzahl: {num}",
|
||||
"run_performance_test": "Leistung testen",
|
||||
"about_selftest": "Selbsttest ausführen",
|
||||
"block_explorer_invalid_custom_url": "Ungültige URL. Geben Sie eine gültige URL ein, die mit http:// oder https:// beginnt.",
|
||||
"about_selftest_electrum_disabled": "Deaktiviere den Electrum Offline-Modus, um den Selbsttest durchführen zu können.",
|
||||
"about_selftest_ok": "Alle internen Tests verliefen erfolgreich. Das Wallet funktioniert gut.",
|
||||
|
||||
"about_sm_github": "GitHub",
|
||||
"about_sm_discord": "Discord Server",
|
||||
"about_sm_telegram": "Telegram-Channel",
|
||||
|
@ -259,6 +263,9 @@
|
|||
"encrypt_storage_explanation_description_line1": "Die Aktivierung der Speicherverschlüsselung fügt Ihrer App ein zusätzlicher Schutz hinzu. Die Art und Weise, wie die Daten auf Ihrem Gerät gespeichert werden, macht es anderen damit schwieriger, ohne Erlaubnis darauf zuzugreifen.",
|
||||
"encrypt_storage_explanation_description_line2": "Diese Verschlüsselung betrifft den Zugriff auf die im Gerät gespeicherten Schlüssel, schützt also die Wallets. Die Wallet selbst werden dabei nicht mit einem Passwort oder einem anderen zusätzlichen Schutz versehen.",
|
||||
"i_understand": "Ich habe verstanden",
|
||||
"block_explorer": "Block-Explorer",
|
||||
"block_explorer_preferred": "Bevorzugten Block-Explorer verwenden",
|
||||
"block_explorer_error_saving_custom": "Fehler beim Speichern des bevorzugten Block-Explorers.",
|
||||
"encrypt_title": "Sicherheit",
|
||||
"encrypt_tstorage": "Speicher",
|
||||
"encrypt_use": "Benutze {type}",
|
||||
|
|
|
@ -130,6 +130,9 @@
|
|||
"details_insert_contact": "Insert Contact",
|
||||
"details_add_rec_add": "Add Recipient",
|
||||
"details_add_rec_rem": "Remove Recipient",
|
||||
"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_recipients_title": "Recipients",
|
||||
"details_address": "Address",
|
||||
"details_address_field_is_not_valid": "The address is not valid.",
|
||||
"details_adv_fee_bump": "Allow Fee Bump",
|
||||
|
@ -302,8 +305,8 @@
|
|||
"privacy_clipboard_explanation": "Provide shortcuts if an address or invoice is found in your clipboard.",
|
||||
"privacy_do_not_track": "Disable Analytics",
|
||||
"privacy_do_not_track_explanation": "Performance and reliability information will not be submitted for analysis.",
|
||||
"push_notifications": "Push Notifications",
|
||||
"rate": "Rate",
|
||||
"push_notifications_explanation": "By enabling notifications, your device token will be sent to the server, along with wallet addresses and transaction IDs for all wallets and transactions made after enabling notifications. The device token is used to send notifications, and the wallet information allows us to notify you about incoming Bitcoin or transaction confirmations.\n\nOnly information from after you enable notifications is transmitted—nothing from before is collected.\n\nDisabling notifications will remove all of this information from the server. Additionally, deleting a wallet from the app will also remove its associated information from the server.",
|
||||
"selfTest": "Self-Test",
|
||||
"save": "Save",
|
||||
"saved": "Saved",
|
||||
|
@ -315,6 +318,7 @@
|
|||
},
|
||||
"notifications": {
|
||||
"would_you_like_to_receive_notifications": "Would you like to receive notifications when you get incoming payments?",
|
||||
"notifications_subtitle": "Incoming payments and transaction confirmations",
|
||||
"no_and_dont_ask": "No, and do not ask me again.",
|
||||
"ask_me_later": "Ask me later."
|
||||
},
|
||||
|
@ -341,7 +345,6 @@
|
|||
"details_outputs": "Outputs",
|
||||
"date": "Date",
|
||||
"details_received": "Received",
|
||||
"transaction_saved": "Saved",
|
||||
"details_show_in_block_explorer": "View in Block Explorer",
|
||||
"details_title": "Transaction",
|
||||
"incoming_transaction": "Incoming Transaction",
|
||||
|
@ -417,7 +420,6 @@
|
|||
"details_master_fingerprint": "Master Fingerprint",
|
||||
"details_multisig_type": "multisig",
|
||||
"details_no_cancel": "No, cancel",
|
||||
"details_save": "Save",
|
||||
"details_show_xpub": "Show Wallet XPUB",
|
||||
"details_show_addresses": "Show addresses",
|
||||
"details_title": "Wallet",
|
||||
|
|
|
@ -130,6 +130,9 @@
|
|||
"details_insert_contact": "Insertar contacto",
|
||||
"details_add_rec_add": "Agregar destinatario",
|
||||
"details_add_rec_rem": "Eliminar destinatario",
|
||||
"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_recipients_title": "Destinatarios",
|
||||
"details_address": "Dirección",
|
||||
"details_address_field_is_not_valid": "La dirección no es válida.",
|
||||
"details_adv_fee_bump": "Permitir aumento de tarifas",
|
||||
|
@ -302,8 +305,8 @@
|
|||
"privacy_clipboard_explanation": "Proporciona atajos si encuentras una dirección o factura en tu portapapeles.",
|
||||
"privacy_do_not_track": "Desactivar análisis",
|
||||
"privacy_do_not_track_explanation": "La información de rendimiento y confiabilidad no se enviará para su análisis.",
|
||||
"push_notifications": "Notificaciones Push",
|
||||
"rate": "Tasa",
|
||||
"push_notifications_explanation": "Al habilitar las notificaciones, el token de tu dispositivo se enviará al servidor, junto con las direcciones de la billetera y los identificadores de transacciones de todas las billeteras y transacciones realizadas después de habilitar las notificaciones. El token del dispositivo se utiliza para enviar notificaciones, y la información de la billetera nos permite notificarte sobre la llegada de Bitcoin o las confirmaciones de transacciones.\n\nSolo se transmite la información que se recibe después de habilitar las notificaciones; no se recopila nada anterior.\n\nSi deshabilitas las notificaciones, se eliminará toda esta información del servidor. Además, si eliminas una billetera de la aplicación, también se eliminará la información asociada a ella del servidor.",
|
||||
"selfTest": "Auto-Test",
|
||||
"save": "Guardar",
|
||||
"saved": "Guardado",
|
||||
|
@ -315,6 +318,7 @@
|
|||
},
|
||||
"notifications": {
|
||||
"would_you_like_to_receive_notifications": "¿Te gustaría recibir notificaciones cuando recibas pagos entrantes?",
|
||||
"notifications_subtitle": "Pagos entrantes y confirmaciones de transacciones",
|
||||
"no_and_dont_ask": "No, y no me vuelvas a preguntar.",
|
||||
"ask_me_later": "Pregúntame Luego."
|
||||
},
|
||||
|
@ -341,7 +345,6 @@
|
|||
"details_outputs": "Salidas",
|
||||
"date": "Fecha",
|
||||
"details_received": "Recibido",
|
||||
"transaction_saved": "Guardado",
|
||||
"details_show_in_block_explorer": "Ver en el Explorador de Bloques",
|
||||
"details_title": "Transacción",
|
||||
"incoming_transaction": "Transacción entrante",
|
||||
|
@ -416,7 +419,6 @@
|
|||
"details_master_fingerprint": "Huella Digital Maestra",
|
||||
"details_multisig_type": "multifirma",
|
||||
"details_no_cancel": "No, cancelar",
|
||||
"details_save": "Guardar",
|
||||
"details_show_xpub": "Mostrar el XPUB de la Billetera",
|
||||
"details_show_addresses": "Mostrar direcciones",
|
||||
"details_title": "Billetera",
|
||||
|
|
|
@ -14,7 +14,7 @@ import LnurlAuth from '../screen/lnd/lnurlAuth';
|
|||
import LnurlPay from '../screen/lnd/lnurlPay';
|
||||
import LnurlPaySuccess from '../screen/lnd/lnurlPaySuccess';
|
||||
import Broadcast from '../screen/send/Broadcast';
|
||||
import IsItMyAddress from '../screen/send/isItMyAddress';
|
||||
import IsItMyAddress from '../screen/settings/IsItMyAddress';
|
||||
import Success from '../screen/send/success';
|
||||
import CPFP from '../screen/transactions/CPFP';
|
||||
import TransactionDetails from '../screen/transactions/TransactionDetails';
|
||||
|
@ -133,7 +133,6 @@ const DetailViewStackScreensStack = () => {
|
|||
backgroundColor: theme.colors.customHeader,
|
||||
},
|
||||
headerTitle: loc.transactions.details_title,
|
||||
headerRight: () => DetailButton,
|
||||
})(theme)}
|
||||
/>
|
||||
<DetailViewStack.Screen
|
||||
|
@ -200,6 +199,7 @@ const DetailViewStackScreensStack = () => {
|
|||
<DetailViewStack.Screen
|
||||
name="IsItMyAddress"
|
||||
component={IsItMyAddress}
|
||||
initialParams={{ address: undefined }}
|
||||
options={navigationStyle({ title: loc.is_it_my_address.title })(theme)}
|
||||
/>
|
||||
<DetailViewStack.Screen
|
||||
|
|
|
@ -16,7 +16,7 @@ export type DetailViewStackParamList = {
|
|||
LNDViewAdditionalInvoiceInformation: { invoiceId: string };
|
||||
LNDViewAdditionalInvoicePreImage: { invoiceId: string };
|
||||
Broadcast: { scannedData?: string };
|
||||
IsItMyAddress: undefined;
|
||||
IsItMyAddress: { address?: string };
|
||||
GenerateWord: undefined;
|
||||
LnurlPay: undefined;
|
||||
LnurlPaySuccess: {
|
||||
|
@ -77,7 +77,7 @@ export type DetailViewStackParamList = {
|
|||
ReceiveDetailsRoot: {
|
||||
screen: 'ReceiveDetails';
|
||||
params: {
|
||||
walletID: string;
|
||||
walletID?: string;
|
||||
address: string;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -14,7 +14,7 @@ const DefaultView = lazy(() => import('../screen/settings/DefaultView'));
|
|||
const ElectrumSettings = lazy(() => import('../screen/settings/electrumSettings'));
|
||||
const EncryptStorage = lazy(() => import('../screen/settings/EncryptStorage'));
|
||||
const LightningSettings = lazy(() => import('../screen/settings/LightningSettings'));
|
||||
const NotificationSettings = lazy(() => import('../screen/settings/notificationSettings'));
|
||||
const NotificationSettings = lazy(() => import('../screen/settings/NotificationSettings'));
|
||||
const SelfTest = lazy(() => import('../screen/settings/SelfTest'));
|
||||
const ReleaseNotes = lazy(() => import('../screen/settings/ReleaseNotes'));
|
||||
const Tools = lazy(() => import('../screen/settings/tools'));
|
||||
|
|
1106
package-lock.json
generated
1106
package-lock.json
generated
File diff suppressed because it is too large
Load diff
14
package.json
14
package.json
|
@ -74,7 +74,7 @@
|
|||
"unit": "jest -b -w tests/unit/*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/preset-env": "7.25.3",
|
||||
"@babel/preset-env": "7.25.8",
|
||||
"@bugsnag/react-native": "8.0.0",
|
||||
"@bugsnag/source-maps": "2.3.3",
|
||||
"@keystonehq/bc-ur-registry": "0.7.0",
|
||||
|
@ -84,7 +84,7 @@
|
|||
"@react-native-async-storage/async-storage": "1.24.0",
|
||||
"@react-native-clipboard/clipboard": "1.14.2",
|
||||
"@react-native-community/push-notification-ios": "1.11.0",
|
||||
"@react-native-menu/menu": "https://github.com/BlueWallet/menu.git#8c6004b",
|
||||
"@react-native-menu/menu": "https://github.com/BlueWallet/menu.git#a33379d",
|
||||
"@react-native/gradle-plugin": "^0.75.4",
|
||||
"@react-native/metro-config": "0.75.4",
|
||||
"@react-navigation/drawer": "6.7.2",
|
||||
|
@ -138,27 +138,25 @@
|
|||
"react-native-gesture-handler": "2.20.0",
|
||||
"react-native-handoff": "github:BlueWallet/react-native-handoff#v0.0.4",
|
||||
"react-native-haptic-feedback": "2.3.3",
|
||||
"react-native-idle-timer": "github:BlueWallet/react-native-idle-timer#v2.2.3",
|
||||
"react-native-image-picker": "7.1.2",
|
||||
"react-native-ios-context-menu": "github:BlueWallet/react-native-ios-context-menu#e5c1217cd220bfab6e6d9a7c65838545082e3f8e",
|
||||
"react-native-keychain": "8.2.0",
|
||||
"react-native-linear-gradient": "2.8.3",
|
||||
"react-native-localize": "3.2.1",
|
||||
"react-native-obscure": "github:BlueWallet/react-native-obscure#f4b83b4a261e39b1f5ed4a45ac5bcabc8a59eadb",
|
||||
"react-native-permissions": "4.1.5",
|
||||
"react-native-privacy-snapshot": "github:BlueWallet/react-native-privacy-snapshot#529e4627d93f67752a27e82a040ff7b64dca0783",
|
||||
"react-native-prompt-android": "github:BlueWallet/react-native-prompt-android#ed168d66fed556bc2ed07cf498770f058b78a376",
|
||||
"react-native-push-notification": "8.1.1",
|
||||
"react-native-qrcode-svg": "6.3.2",
|
||||
"react-native-quick-actions": "0.3.13",
|
||||
"react-native-randombytes": "3.6.1",
|
||||
"react-native-rate": "1.2.12",
|
||||
"react-native-reanimated": "3.15.4",
|
||||
"react-native-safe-area-context": "4.11.0",
|
||||
"react-native-reanimated": "3.16.0",
|
||||
"react-native-safe-area-context": "4.11.1",
|
||||
"react-native-screen-capture": "github:BlueWallet/react-native-screen-capture#18cb79f",
|
||||
"react-native-screens": "3.34.0",
|
||||
"react-native-secure-key-store": "github:BlueWallet/react-native-secure-key-store#2076b4849e88aa0a78e08bfbb4ce3923e0925cbc",
|
||||
"react-native-share": "10.2.1",
|
||||
"react-native-svg": "15.7.1",
|
||||
"react-native-svg": "15.8.0",
|
||||
"react-native-tcp-socket": "6.2.0",
|
||||
"react-native-vector-icons": "10.2.0",
|
||||
"react-native-watch-connectivity": "1.1.0",
|
||||
|
|
|
@ -57,6 +57,7 @@ import { useKeyboard } from '../../hooks/useKeyboard';
|
|||
import { DismissKeyboardInputAccessory, DismissKeyboardInputAccessoryViewID } from '../../components/DismissKeyboardInputAccessory';
|
||||
import ActionSheet from '../ActionSheet';
|
||||
import HeaderMenuButton from '../../components/HeaderMenuButton';
|
||||
import { CommonToolTipActions } from '../../typings/CommonToolTipActions';
|
||||
|
||||
interface IPaymentDestinations {
|
||||
address: string; // btc address or payment code
|
||||
|
@ -852,6 +853,24 @@ const SendDetails = () => {
|
|||
}, 0);
|
||||
};
|
||||
|
||||
const onRemoveAllRecipientsConfirmed = useCallback(() => {
|
||||
setAddresses([{ address: '', key: String(Math.random()) } as IPaymentDestinations]);
|
||||
}, []);
|
||||
|
||||
const handleRemoveAllRecipients = useCallback(() => {
|
||||
Alert.alert(loc.send.details_recipients_title, loc.send.details_add_recc_rem_all_alert_description, [
|
||||
{
|
||||
text: loc._.cancel,
|
||||
onPress: () => {},
|
||||
style: 'cancel',
|
||||
},
|
||||
{
|
||||
text: loc._.ok,
|
||||
onPress: onRemoveAllRecipientsConfirmed,
|
||||
},
|
||||
]);
|
||||
}, [onRemoveAllRecipientsConfirmed]);
|
||||
|
||||
const handleRemoveRecipient = () => {
|
||||
if (addresses.length > 1) {
|
||||
const newAddresses = [...addresses];
|
||||
|
@ -933,9 +952,9 @@ const SendDetails = () => {
|
|||
// Header Right Button
|
||||
|
||||
const headerRightOnPress = (id: string) => {
|
||||
if (id === SendDetails.actionKeys.AddRecipient) {
|
||||
if (id === CommonToolTipActions.AddRecipient.id) {
|
||||
handleAddRecipient();
|
||||
} else if (id === SendDetails.actionKeys.RemoveRecipient) {
|
||||
} else if (id === CommonToolTipActions.RemoveRecipient.id) {
|
||||
handleRemoveRecipient();
|
||||
} else if (id === SendDetails.actionKeys.SignPSBT) {
|
||||
handlePsbtSign();
|
||||
|
@ -955,6 +974,8 @@ const SendDetails = () => {
|
|||
handleCoinControl();
|
||||
} else if (id === SendDetails.actionKeys.InsertContact) {
|
||||
handleInsertContact();
|
||||
} else if (id === CommonToolTipActions.RemoveAllRecipients.id) {
|
||||
handleRemoveAllRecipients();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1007,19 +1028,13 @@ const SendDetails = () => {
|
|||
if ((wallet as MultisigHDWallet)?.allowCosignPsbt()) {
|
||||
transactionActions.push({ id: SendDetails.actionKeys.SignPSBT, text: loc.send.psbt_sign, icon: SendDetails.actionIcons.SignPSBT });
|
||||
}
|
||||
actions.push(transactionActions, [
|
||||
{
|
||||
id: SendDetails.actionKeys.AddRecipient,
|
||||
text: loc.send.details_add_rec_add,
|
||||
icon: SendDetails.actionIcons.AddRecipient,
|
||||
},
|
||||
{
|
||||
id: SendDetails.actionKeys.RemoveRecipient,
|
||||
text: loc.send.details_add_rec_rem,
|
||||
disabled: addresses.length < 2,
|
||||
icon: SendDetails.actionIcons.RemoveRecipient,
|
||||
},
|
||||
]);
|
||||
actions.push(transactionActions);
|
||||
|
||||
const recipientActions: Action[] = [CommonToolTipActions.AddRecipient, CommonToolTipActions.RemoveRecipient];
|
||||
if (addresses.length > 1) {
|
||||
recipientActions.push(CommonToolTipActions.RemoveAllRecipients);
|
||||
}
|
||||
actions.push(recipientActions);
|
||||
}
|
||||
|
||||
actions.push({ id: SendDetails.actionKeys.CoinControl, text: loc.cc.header, icon: SendDetails.actionIcons.CoinControl });
|
||||
|
@ -1355,8 +1370,6 @@ SendDetails.actionKeys = {
|
|||
InsertContact: 'InsertContact',
|
||||
SignPSBT: 'SignPSBT',
|
||||
SendMax: 'SendMax',
|
||||
AddRecipient: 'AddRecipient',
|
||||
RemoveRecipient: 'RemoveRecipient',
|
||||
AllowRBF: 'AllowRBF',
|
||||
ImportTransaction: 'ImportTransaction',
|
||||
ImportTransactionMultsig: 'ImportTransactionMultisig',
|
||||
|
@ -1369,8 +1382,6 @@ SendDetails.actionIcons = {
|
|||
InsertContact: { iconValue: 'at.badge.plus' },
|
||||
SignPSBT: { iconValue: 'signature' },
|
||||
SendMax: 'SendMax',
|
||||
AddRecipient: { iconValue: 'person.badge.plus' },
|
||||
RemoveRecipient: { iconValue: 'person.badge.minus' },
|
||||
AllowRBF: 'AllowRBF',
|
||||
ImportTransaction: { iconValue: 'square.and.arrow.down' },
|
||||
ImportTransactionMultsig: { iconValue: 'square.and.arrow.down.on.square' },
|
||||
|
|
|
@ -16,7 +16,7 @@ import { BlueText } from '../../BlueComponents';
|
|||
import presentAlert from '../../components/Alert';
|
||||
import { DynamicQRCode } from '../../components/DynamicQRCode';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import usePrivacy from '../../hooks/usePrivacy';
|
||||
import { disallowScreenshot } from 'react-native-screen-capture';
|
||||
import loc from '../../loc';
|
||||
import { BitcoinUnit } from '../../models/bitcoinUnits';
|
||||
|
||||
|
@ -26,7 +26,6 @@ const SendCreate = () => {
|
|||
const size = transaction.virtualSize();
|
||||
const { colors } = useTheme();
|
||||
const { setOptions } = useNavigation();
|
||||
const { enableBlur, disableBlur } = usePrivacy();
|
||||
|
||||
const styleHooks = StyleSheet.create({
|
||||
transactionDetailsTitle: {
|
||||
|
@ -48,11 +47,11 @@ const SendCreate = () => {
|
|||
|
||||
useEffect(() => {
|
||||
console.log('send/create - useEffect');
|
||||
enableBlur();
|
||||
disallowScreenshot(true);
|
||||
return () => {
|
||||
disableBlur();
|
||||
disallowScreenshot(false);
|
||||
};
|
||||
}, [disableBlur, enableBlur]);
|
||||
}, []);
|
||||
|
||||
const exportTXN = useCallback(async () => {
|
||||
const fileName = `${Date.now()}.txn`;
|
||||
|
|
|
@ -1,151 +0,0 @@
|
|||
import React, { useRef, useState } from 'react';
|
||||
import { useRoute } from '@react-navigation/native';
|
||||
import { Keyboard, StyleSheet, TextInput, View } from 'react-native';
|
||||
import { BlueButtonLink, BlueCard, BlueSpacing10, BlueSpacing20, BlueText } from '../../BlueComponents';
|
||||
import Button from '../../components/Button';
|
||||
import SafeArea from '../../components/SafeArea';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import { scanQrHelper } from '../../helpers/scan-qr';
|
||||
import loc from '../../loc';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
||||
|
||||
const IsItMyAddress = () => {
|
||||
/** @type {AbstractWallet[]} */
|
||||
const { wallets } = useStorage();
|
||||
const { navigate } = useExtendedNavigation();
|
||||
const { name } = useRoute();
|
||||
const { colors } = useTheme();
|
||||
const scanButtonRef = useRef();
|
||||
|
||||
const [address, setAddress] = useState('');
|
||||
const [result, setResult] = useState('');
|
||||
const [resultCleanAddress, setResultCleanAddress] = useState();
|
||||
|
||||
const stylesHooks = StyleSheet.create({
|
||||
input: {
|
||||
borderColor: colors.formBorder,
|
||||
borderBottomColor: colors.formBorder,
|
||||
backgroundColor: colors.inputBackgroundColor,
|
||||
},
|
||||
});
|
||||
|
||||
const handleUpdateAddress = nextValue => setAddress(nextValue.trim());
|
||||
|
||||
const checkAddress = () => {
|
||||
Keyboard.dismiss();
|
||||
const cleanAddress = address.replace('bitcoin:', '').replace('BITCOIN:', '').replace('bitcoin=', '').split('?')[0];
|
||||
const _result = [];
|
||||
for (const w of wallets) {
|
||||
if (w.weOwnAddress(cleanAddress)) {
|
||||
setResultCleanAddress(cleanAddress);
|
||||
_result.push(loc.formatString(loc.is_it_my_address.owns, { label: w.getLabel(), address: cleanAddress }));
|
||||
}
|
||||
}
|
||||
|
||||
if (_result.length === 0) {
|
||||
setResult(_result.push(loc.is_it_my_address.no_wallet_owns_address));
|
||||
setResultCleanAddress();
|
||||
}
|
||||
|
||||
setResult(_result.join('\n\n'));
|
||||
};
|
||||
|
||||
const onBarScanned = value => {
|
||||
setAddress(value);
|
||||
setResultCleanAddress(value);
|
||||
};
|
||||
|
||||
const importScan = () => {
|
||||
scanQrHelper(name, true, onBarScanned);
|
||||
};
|
||||
|
||||
const clearAddressInput = () => {
|
||||
setAddress('');
|
||||
setResult();
|
||||
setResultCleanAddress();
|
||||
};
|
||||
|
||||
const viewQRCode = () => {
|
||||
navigate('ReceiveDetailsRoot', {
|
||||
screen: 'ReceiveDetails',
|
||||
params: {
|
||||
address: resultCleanAddress,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<SafeArea style={styles.blueArea}>
|
||||
<View style={styles.wrapper}>
|
||||
<BlueCard style={styles.mainCard}>
|
||||
<View style={[styles.input, stylesHooks.input]}>
|
||||
<TextInput
|
||||
style={styles.text}
|
||||
maxHeight={100}
|
||||
minHeight={100}
|
||||
maxWidth="100%"
|
||||
minWidth="100%"
|
||||
multiline
|
||||
editable
|
||||
placeholder={loc.is_it_my_address.enter_address}
|
||||
placeholderTextColor="#81868e"
|
||||
value={address}
|
||||
onChangeText={handleUpdateAddress}
|
||||
testID="AddressInput"
|
||||
/>
|
||||
</View>
|
||||
|
||||
<BlueSpacing10 />
|
||||
<BlueButtonLink ref={scanButtonRef} title={loc.wallets.import_scan_qr} onPress={importScan} />
|
||||
<BlueSpacing10 />
|
||||
<Button title={loc.send.input_clear} onPress={clearAddressInput} />
|
||||
<BlueSpacing20 />
|
||||
{resultCleanAddress && (
|
||||
<>
|
||||
<Button title={loc.is_it_my_address.view_qrcode} onPress={viewQRCode} />
|
||||
<BlueSpacing20 />
|
||||
</>
|
||||
)}
|
||||
<Button
|
||||
disabled={address.trim().length === 0}
|
||||
title={loc.is_it_my_address.check_address}
|
||||
onPress={checkAddress}
|
||||
testID="CheckAddress"
|
||||
/>
|
||||
<BlueSpacing20 />
|
||||
<BlueText testID="Result">{result}</BlueText>
|
||||
</BlueCard>
|
||||
</View>
|
||||
</SafeArea>
|
||||
);
|
||||
};
|
||||
|
||||
export default IsItMyAddress;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
wrapper: {
|
||||
marginTop: 16,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
},
|
||||
mainCard: {
|
||||
padding: 0,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
},
|
||||
input: {
|
||||
flexDirection: 'row',
|
||||
borderWidth: 1,
|
||||
borderBottomWidth: 0.5,
|
||||
alignItems: 'center',
|
||||
borderRadius: 4,
|
||||
},
|
||||
text: {
|
||||
padding: 8,
|
||||
minHeight: 33,
|
||||
color: '#81868e',
|
||||
},
|
||||
});
|
|
@ -54,7 +54,7 @@ const Success = () => {
|
|||
<HandOffComponent
|
||||
title={loc.transactions.details_title}
|
||||
type={HandOffActivityType.ViewInBlockExplorer}
|
||||
url={`${selectedBlockExplorer}/tx/${txid}`}
|
||||
url={`${selectedBlockExplorer.url}/tx/${txid}`}
|
||||
/>
|
||||
)}
|
||||
</SafeArea>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import dayjs from 'dayjs';
|
||||
import calendar from 'dayjs/plugin/calendar';
|
||||
import React, { useEffect, useLayoutEffect, useMemo, useState } from 'react';
|
||||
import { FlatList, NativeSyntheticEvent, StyleSheet, View, LayoutAnimation, UIManager, Platform } from 'react-native';
|
||||
import { FlatList, NativeSyntheticEvent, StyleSheet, View, LayoutAnimation, UIManager, Platform, Keyboard } from 'react-native';
|
||||
|
||||
import {
|
||||
CurrencyRate,
|
||||
|
@ -87,6 +87,7 @@ const Currency: React.FC = () => {
|
|||
isLoading={isSavingNewPreferredCurrency && selectedCurrency.endPointKey === item.endPointKey}
|
||||
subtitle={item.country}
|
||||
onPress={async () => {
|
||||
Keyboard.dismiss();
|
||||
setIsSavingNewPreferredCurrency(item);
|
||||
try {
|
||||
await getFiatRate(item.endPointKey);
|
||||
|
|
282
screen/settings/IsItMyAddress.tsx
Normal file
282
screen/settings/IsItMyAddress.tsx
Normal file
|
@ -0,0 +1,282 @@
|
|||
import React, { useRef, useState, useEffect } from 'react';
|
||||
import { useRoute, useNavigation, RouteProp } from '@react-navigation/native';
|
||||
import { Keyboard, StyleSheet, TextInput, View, ScrollView, TouchableOpacity, Text } from 'react-native';
|
||||
import { BlueButtonLink, BlueCard, BlueSpacing10, BlueSpacing20, BlueSpacing40, BlueText } from '../../BlueComponents';
|
||||
import Button from '../../components/Button';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import { scanQrHelper } from '../../helpers/scan-qr';
|
||||
import loc from '../../loc';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
import { TWallet } from '../../class/wallets/types';
|
||||
import { WalletCarouselItem } from '../../components/WalletsCarousel';
|
||||
import { DetailViewStackParamList } from '../../navigation/DetailViewStackParamList';
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||
import { Divider } from '@rneui/themed';
|
||||
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
|
||||
import presentAlert from '../../components/Alert';
|
||||
|
||||
type RouteProps = RouteProp<DetailViewStackParamList, 'IsItMyAddress'>;
|
||||
type NavigationProp = NativeStackNavigationProp<DetailViewStackParamList, 'IsItMyAddress'>;
|
||||
|
||||
const IsItMyAddress: React.FC = () => {
|
||||
const { wallets } = useStorage();
|
||||
const navigation = useNavigation<NavigationProp>();
|
||||
const route = useRoute<RouteProps>();
|
||||
const { colors } = useTheme();
|
||||
const scanButtonRef = useRef<any>();
|
||||
const scrollViewRef = useRef<ScrollView>(null);
|
||||
const firstWalletRef = useRef<View>(null);
|
||||
|
||||
const [address, setAddress] = useState<string>('');
|
||||
const [matchingWallets, setMatchingWallets] = useState<TWallet[] | undefined>();
|
||||
const [resultCleanAddress, setResultCleanAddress] = useState<string | undefined>();
|
||||
|
||||
const stylesHooks = StyleSheet.create({
|
||||
input: {
|
||||
borderColor: colors.formBorder,
|
||||
borderBottomColor: colors.formBorder,
|
||||
backgroundColor: colors.inputBackgroundColor,
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (route.params?.address && route.params.address !== address) {
|
||||
setAddress(route.params.address);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [route.params?.address]);
|
||||
|
||||
useEffect(() => {
|
||||
const currentAddress = route.params?.address;
|
||||
if (currentAddress !== address) {
|
||||
navigation.setParams({ address });
|
||||
}
|
||||
}, [address, navigation, route.params?.address]);
|
||||
|
||||
const handleUpdateAddress = (nextValue: string) => setAddress(nextValue);
|
||||
|
||||
const clearAddressInput = () => {
|
||||
setAddress('');
|
||||
setResultCleanAddress(undefined);
|
||||
setMatchingWallets(undefined);
|
||||
};
|
||||
|
||||
const checkAddress = () => {
|
||||
Keyboard.dismiss();
|
||||
const cleanAddress = address.replace('bitcoin:', '').replace('BITCOIN:', '').replace('bitcoin=', '').split('?')[0];
|
||||
const matching: TWallet[] = [];
|
||||
|
||||
for (const w of wallets) {
|
||||
if (w.weOwnAddress(cleanAddress)) {
|
||||
matching.push(w);
|
||||
}
|
||||
}
|
||||
|
||||
if (matching.length > 0) {
|
||||
setMatchingWallets(matching);
|
||||
setResultCleanAddress(cleanAddress);
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
|
||||
} else {
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
|
||||
presentAlert({
|
||||
message: loc.is_it_my_address.no_wallet_owns_address,
|
||||
buttons: [
|
||||
{
|
||||
text: loc.receive.reset,
|
||||
onPress: () => {
|
||||
clearAddressInput();
|
||||
},
|
||||
style: 'destructive',
|
||||
},
|
||||
{
|
||||
text: loc._.ok,
|
||||
onPress: () => {},
|
||||
style: 'cancel',
|
||||
},
|
||||
],
|
||||
options: { cancelable: true },
|
||||
});
|
||||
setMatchingWallets([]);
|
||||
setResultCleanAddress(undefined);
|
||||
}
|
||||
};
|
||||
|
||||
const onBarScanned = (value: string) => {
|
||||
const cleanAddress = value.replace(/^bitcoin(:|=)/i, '').split('?')[0];
|
||||
setAddress(value);
|
||||
setResultCleanAddress(cleanAddress);
|
||||
};
|
||||
|
||||
const importScan = async () => {
|
||||
const data = await scanQrHelper(route.name, true, undefined, true);
|
||||
if (data) {
|
||||
onBarScanned(data);
|
||||
}
|
||||
};
|
||||
|
||||
const viewQRCode = () => {
|
||||
if (!resultCleanAddress) return;
|
||||
navigation.navigate('ReceiveDetailsRoot', {
|
||||
screen: 'ReceiveDetails',
|
||||
params: {
|
||||
address: resultCleanAddress,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const isCheckAddressDisabled = address.trim().length === 0;
|
||||
|
||||
useEffect(() => {
|
||||
if (matchingWallets && matchingWallets.length > 0 && scrollViewRef.current && firstWalletRef.current) {
|
||||
firstWalletRef.current.measureLayout(scrollViewRef.current.getInnerViewNode(), (x, y) => {
|
||||
scrollViewRef.current?.scrollTo({ x: 0, y: y - 20, animated: true });
|
||||
});
|
||||
}
|
||||
}, [matchingWallets]);
|
||||
|
||||
const renderFormattedText = (text: string, values: { [key: string]: string }) => {
|
||||
const regex = /\{(\w+)\}/g;
|
||||
const parts = [];
|
||||
let lastIndex = 0;
|
||||
let match;
|
||||
let index = 0;
|
||||
|
||||
while ((match = regex.exec(text)) !== null) {
|
||||
if (match.index > lastIndex) {
|
||||
parts.push(<Text key={`text-${index++}`}>{text.substring(lastIndex, match.index)}</Text>);
|
||||
}
|
||||
const value = values[match[1]];
|
||||
if (value) {
|
||||
parts.push(
|
||||
<Text key={`bold-${index++}`} style={styles.boldText} selectable>
|
||||
{value}
|
||||
</Text>,
|
||||
);
|
||||
}
|
||||
lastIndex = regex.lastIndex;
|
||||
}
|
||||
if (lastIndex < text.length) {
|
||||
parts.push(<Text key={`text-${index++}`}>{text.substring(lastIndex)}</Text>);
|
||||
}
|
||||
return parts;
|
||||
};
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
ref={scrollViewRef}
|
||||
contentContainerStyle={styles.wrapper}
|
||||
automaticallyAdjustContentInsets
|
||||
automaticallyAdjustKeyboardInsets
|
||||
contentInsetAdjustmentBehavior="automatic"
|
||||
>
|
||||
<BlueCard style={styles.mainCard}>
|
||||
<View style={[styles.input, stylesHooks.input]}>
|
||||
<TextInput
|
||||
style={styles.textInput}
|
||||
multiline
|
||||
editable
|
||||
placeholder={loc.is_it_my_address.enter_address}
|
||||
placeholderTextColor={colors.placeholderTextColor}
|
||||
value={address}
|
||||
onChangeText={handleUpdateAddress}
|
||||
testID="AddressInput"
|
||||
/>
|
||||
{address.length > 0 && (
|
||||
<TouchableOpacity onPress={clearAddressInput} style={styles.clearButton}>
|
||||
<Icon name="close" size={20} color="#81868e" />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<BlueSpacing10 />
|
||||
<BlueButtonLink ref={scanButtonRef} title={loc.wallets.import_scan_qr} onPress={importScan} />
|
||||
<BlueSpacing20 />
|
||||
{resultCleanAddress && (
|
||||
<>
|
||||
<Button title={loc.is_it_my_address.view_qrcode} onPress={viewQRCode} />
|
||||
<BlueSpacing20 />
|
||||
</>
|
||||
)}
|
||||
<Button disabled={isCheckAddressDisabled} title={loc.is_it_my_address.check_address} onPress={checkAddress} testID="CheckAddress" />
|
||||
<BlueSpacing40 />
|
||||
|
||||
{matchingWallets !== undefined && matchingWallets.length > 0 && (
|
||||
<>
|
||||
<Divider />
|
||||
<BlueSpacing40 />
|
||||
</>
|
||||
)}
|
||||
{matchingWallets !== undefined &&
|
||||
matchingWallets.length > 0 &&
|
||||
matchingWallets.map((wallet, index) => (
|
||||
<View key={wallet.getID()} ref={index === 0 ? firstWalletRef : undefined} style={styles.walletContainer}>
|
||||
<BlueText selectable style={styles.resultText}>
|
||||
{resultCleanAddress &&
|
||||
renderFormattedText(loc.is_it_my_address.owns, {
|
||||
label: wallet.getLabel(),
|
||||
address: resultCleanAddress,
|
||||
})}
|
||||
</BlueText>
|
||||
<BlueSpacing10 />
|
||||
<WalletCarouselItem
|
||||
item={wallet}
|
||||
onPress={item => {
|
||||
navigation.navigate('WalletTransactions', {
|
||||
walletID: item.getID(),
|
||||
walletType: item.type,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<BlueSpacing20 />
|
||||
</View>
|
||||
))}
|
||||
</BlueCard>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
export default IsItMyAddress;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
wrapper: {
|
||||
alignItems: 'center',
|
||||
},
|
||||
mainCard: {
|
||||
padding: 0,
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
width: '100%',
|
||||
},
|
||||
input: {
|
||||
flexDirection: 'row',
|
||||
borderWidth: 1,
|
||||
borderBottomWidth: 0.5,
|
||||
alignItems: 'center',
|
||||
borderRadius: 4,
|
||||
width: '100%',
|
||||
},
|
||||
textInput: {
|
||||
flex: 1,
|
||||
padding: 8,
|
||||
minHeight: 100,
|
||||
color: '#81868e',
|
||||
},
|
||||
clearButton: {
|
||||
padding: 8,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
boldText: {
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
resultText: {
|
||||
marginVertical: 10,
|
||||
textAlign: 'center',
|
||||
},
|
||||
walletContainer: {
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
},
|
||||
});
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useEffect, useLayoutEffect, useState } from 'react';
|
||||
import { FlatList, NativeSyntheticEvent, StyleSheet } from 'react-native';
|
||||
import { FlatList, Keyboard, NativeSyntheticEvent, StyleSheet } from 'react-native';
|
||||
import presentAlert from '../../components/Alert';
|
||||
import ListItem from '../../components/ListItem';
|
||||
import { useTheme } from '../../components/themes';
|
||||
|
@ -33,6 +33,7 @@ const Language = () => {
|
|||
}, [language]);
|
||||
|
||||
const onLanguageSelect = (item: TLanguage) => {
|
||||
Keyboard.dismiss();
|
||||
const currentLanguage = AvailableLanguages.find(l => l.value === language);
|
||||
setLanguageStorage(item.value).then(() => {
|
||||
if (currentLanguage?.isRTL !== item.isRTL) {
|
||||
|
|
231
screen/settings/NotificationSettings.tsx
Normal file
231
screen/settings/NotificationSettings.tsx
Normal file
|
@ -0,0 +1,231 @@
|
|||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { I18nManager, Linking, ScrollView, StyleSheet, TextInput, View, Pressable } from 'react-native';
|
||||
import { Button as ButtonRNElements } from '@rneui/themed';
|
||||
// @ts-ignore: no declaration file
|
||||
import Notifications from '../../blue_modules/notifications';
|
||||
import { BlueCard, BlueSpacing20, BlueText } from '../../BlueComponents';
|
||||
import presentAlert from '../../components/Alert';
|
||||
import { Button } from '../../components/Button';
|
||||
import CopyToClipboardButton from '../../components/CopyToClipboardButton';
|
||||
import ListItem, { PressableWrapper } from '../../components/ListItem';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import loc from '../../loc';
|
||||
import { Divider } from '@rneui/base';
|
||||
|
||||
const NotificationSettings: React.FC = () => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isNotificationsEnabled, setNotificationsEnabled] = useState(false);
|
||||
const [tokenInfo, setTokenInfo] = useState('<empty>');
|
||||
const [URI, setURI] = useState<string | undefined>();
|
||||
const [tapCount, setTapCount] = useState(0);
|
||||
const { colors } = useTheme();
|
||||
const stylesWithThemeHook = {
|
||||
root: {
|
||||
backgroundColor: colors.background,
|
||||
},
|
||||
scroll: {
|
||||
backgroundColor: colors.background,
|
||||
},
|
||||
scrollBody: {
|
||||
backgroundColor: colors.background,
|
||||
},
|
||||
uri: {
|
||||
borderColor: colors.formBorder,
|
||||
borderBottomColor: colors.formBorder,
|
||||
backgroundColor: colors.inputBackgroundColor,
|
||||
},
|
||||
};
|
||||
|
||||
const handleTap = () => {
|
||||
setTapCount(prevCount => prevCount + 1);
|
||||
};
|
||||
|
||||
const onNotificationsSwitch = async (value: boolean) => {
|
||||
try {
|
||||
setNotificationsEnabled(value);
|
||||
if (value) {
|
||||
// User is enabling notifications
|
||||
// @ts-ignore: refactor later
|
||||
await Notifications.cleanUserOptOutFlag();
|
||||
// @ts-ignore: refactor later
|
||||
if (await Notifications.getPushToken()) {
|
||||
// we already have a token, so we just need to reenable ALL level on groundcontrol:
|
||||
// @ts-ignore: refactor later
|
||||
await Notifications.setLevels(true);
|
||||
} else {
|
||||
// ok, we dont have a token. we need to try to obtain permissions, configure callbacks and save token locally:
|
||||
// @ts-ignore: refactor later
|
||||
await Notifications.tryToObtainPermissions();
|
||||
}
|
||||
} else {
|
||||
// User is disabling notifications
|
||||
// @ts-ignore: refactor later
|
||||
await Notifications.setLevels(false);
|
||||
}
|
||||
|
||||
// @ts-ignore: refactor later
|
||||
setNotificationsEnabled(await Notifications.isNotificationsEnabled());
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
presentAlert({ message: (error as Error).message });
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
// @ts-ignore: refactor later
|
||||
setNotificationsEnabled(await Notifications.isNotificationsEnabled());
|
||||
// @ts-ignore: refactor later
|
||||
setURI(await Notifications.getSavedUri());
|
||||
// @ts-ignore: refactor later
|
||||
setTokenInfo(
|
||||
'token: ' +
|
||||
// @ts-ignore: refactor later
|
||||
JSON.stringify(await Notifications.getPushToken()) +
|
||||
' permissions: ' +
|
||||
// @ts-ignore: refactor later
|
||||
JSON.stringify(await Notifications.checkPermissions()) +
|
||||
' stored notifications: ' +
|
||||
// @ts-ignore: refactor later
|
||||
JSON.stringify(await Notifications.getStoredNotifications()),
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
presentAlert({ message: (e as Error).message });
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
})();
|
||||
}, []);
|
||||
|
||||
const save = useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
if (URI) {
|
||||
// validating only if its not empty. empty means use default
|
||||
// @ts-ignore: refactor later
|
||||
if (await Notifications.isGroundControlUriValid(URI)) {
|
||||
// @ts-ignore: refactor later
|
||||
await Notifications.saveUri(URI);
|
||||
presentAlert({ message: loc.settings.saved });
|
||||
} else {
|
||||
presentAlert({ message: loc.settings.not_a_valid_uri });
|
||||
}
|
||||
} else {
|
||||
// @ts-ignore: refactor later
|
||||
await Notifications.saveUri('');
|
||||
presentAlert({ message: loc.settings.saved });
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(error);
|
||||
}
|
||||
setIsLoading(false);
|
||||
}, [URI]);
|
||||
|
||||
return (
|
||||
<ScrollView style={stylesWithThemeHook.scroll} automaticallyAdjustContentInsets contentInsetAdjustmentBehavior="automatic">
|
||||
<ListItem
|
||||
Component={PressableWrapper}
|
||||
title={loc.settings.notifications}
|
||||
subtitle={loc.notifications.notifications_subtitle}
|
||||
disabled={isLoading}
|
||||
switch={{ onValueChange: onNotificationsSwitch, value: isNotificationsEnabled, testID: 'NotificationsSwitch' }}
|
||||
/>
|
||||
|
||||
<Pressable onPress={handleTap}>
|
||||
<BlueCard>
|
||||
<BlueText style={styles.multilineText}>{loc.settings.push_notifications_explanation}</BlueText>
|
||||
</BlueCard>
|
||||
</Pressable>
|
||||
|
||||
{tapCount >= 10 && (
|
||||
<>
|
||||
<Divider />
|
||||
<BlueCard>
|
||||
<BlueText>{loc.settings.groundcontrol_explanation}</BlueText>
|
||||
</BlueCard>
|
||||
|
||||
<ButtonRNElements
|
||||
icon={{
|
||||
name: 'github',
|
||||
type: 'font-awesome',
|
||||
color: colors.foregroundColor,
|
||||
}}
|
||||
onPress={() => Linking.openURL('https://github.com/BlueWallet/GroundControl')}
|
||||
titleStyle={{ color: colors.buttonAlternativeTextColor }}
|
||||
title="github.com/BlueWallet/GroundControl"
|
||||
color={colors.buttonTextColor}
|
||||
buttonStyle={styles.buttonStyle}
|
||||
/>
|
||||
|
||||
<BlueCard>
|
||||
<View style={[styles.uri, stylesWithThemeHook.uri]}>
|
||||
<TextInput
|
||||
// @ts-ignore: refactor later
|
||||
placeholder={Notifications.getDefaultUri()}
|
||||
value={URI}
|
||||
onChangeText={setURI}
|
||||
numberOfLines={1}
|
||||
style={styles.uriText}
|
||||
placeholderTextColor="#81868e"
|
||||
editable={!isLoading}
|
||||
textContentType="URL"
|
||||
autoCapitalize="none"
|
||||
underlineColorAndroid="transparent"
|
||||
/>
|
||||
</View>
|
||||
|
||||
<BlueSpacing20 />
|
||||
<BlueText style={styles.centered} onPress={() => setTapCount(tapCount + 1)}>
|
||||
♪ Ground Control to Major Tom ♪
|
||||
</BlueText>
|
||||
<BlueText style={styles.centered} onPress={() => setTapCount(tapCount + 1)}>
|
||||
♪ Commencing countdown, engines on ♪
|
||||
</BlueText>
|
||||
|
||||
<View>
|
||||
<CopyToClipboardButton stringToCopy={tokenInfo} displayText={tokenInfo} />
|
||||
</View>
|
||||
|
||||
<BlueSpacing20 />
|
||||
<Button onPress={save} title={loc.settings.save} />
|
||||
</BlueCard>
|
||||
</>
|
||||
)}
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
uri: {
|
||||
flexDirection: 'row',
|
||||
borderWidth: 1,
|
||||
borderBottomWidth: 0.5,
|
||||
minHeight: 44,
|
||||
height: 44,
|
||||
alignItems: 'center',
|
||||
borderRadius: 4,
|
||||
},
|
||||
centered: {
|
||||
textAlign: 'center',
|
||||
},
|
||||
uriText: {
|
||||
flex: 1,
|
||||
color: '#81868e',
|
||||
marginHorizontal: 8,
|
||||
minHeight: 36,
|
||||
height: 36,
|
||||
},
|
||||
buttonStyle: {
|
||||
backgroundColor: 'transparent',
|
||||
flexDirection: I18nManager.isRTL ? 'row-reverse' : 'row',
|
||||
},
|
||||
multilineText: {
|
||||
textAlign: 'left',
|
||||
lineHeight: 20,
|
||||
paddingBottom: 10,
|
||||
},
|
||||
});
|
||||
|
||||
export default NotificationSettings;
|
|
@ -1,195 +0,0 @@
|
|||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { I18nManager, Linking, ScrollView, StyleSheet, TextInput, TouchableWithoutFeedback, View } from 'react-native';
|
||||
import { Button as ButtonRNElements } from '@rneui/themed';
|
||||
|
||||
import Notifications from '../../blue_modules/notifications';
|
||||
import { BlueCard, BlueLoading, BlueSpacing20, BlueText } from '../../BlueComponents';
|
||||
import presentAlert from '../../components/Alert';
|
||||
import { Button } from '../../components/Button';
|
||||
import CopyToClipboardButton from '../../components/CopyToClipboardButton';
|
||||
import ListItem from '../../components/ListItem';
|
||||
import { BlueCurrentTheme, useTheme } from '../../components/themes';
|
||||
import loc from '../../loc';
|
||||
|
||||
const NotificationSettings = () => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isNotificationsEnabled, setNotificationsEnabled] = useState(false);
|
||||
const [isShowTokenInfo, setShowTokenInfo] = useState(0);
|
||||
const [tokenInfo, setTokenInfo] = useState('<empty>');
|
||||
const [URI, setURI] = useState();
|
||||
|
||||
const { colors } = useTheme();
|
||||
|
||||
const onNotificationsSwitch = async value => {
|
||||
setNotificationsEnabled(value); // so the slider is not 'jumpy'
|
||||
if (value) {
|
||||
// user is ENABLING notifications
|
||||
await Notifications.cleanUserOptOutFlag();
|
||||
if (await Notifications.getPushToken()) {
|
||||
// we already have a token, so we just need to reenable ALL level on groundcontrol:
|
||||
await Notifications.setLevels(true);
|
||||
} else {
|
||||
// ok, we dont have a token. we need to try to obtain permissions, configure callbacks and save token locally:
|
||||
await Notifications.tryToObtainPermissions();
|
||||
}
|
||||
} else {
|
||||
// user is DISABLING notifications
|
||||
await Notifications.setLevels(false);
|
||||
}
|
||||
|
||||
setNotificationsEnabled(await Notifications.isNotificationsEnabled());
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
setNotificationsEnabled(await Notifications.isNotificationsEnabled());
|
||||
setURI(await Notifications.getSavedUri());
|
||||
setTokenInfo(
|
||||
'token: ' +
|
||||
JSON.stringify(await Notifications.getPushToken()) +
|
||||
' permissions: ' +
|
||||
JSON.stringify(await Notifications.checkPermissions()) +
|
||||
' stored notifications: ' +
|
||||
JSON.stringify(await Notifications.getStoredNotifications()),
|
||||
);
|
||||
} catch (e) {
|
||||
console.debug(e);
|
||||
presentAlert({ message: e.message });
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
})();
|
||||
}, []);
|
||||
|
||||
const stylesWithThemeHook = {
|
||||
root: {
|
||||
...styles.root,
|
||||
backgroundColor: colors.background,
|
||||
},
|
||||
scroll: {
|
||||
...styles.scroll,
|
||||
backgroundColor: colors.background,
|
||||
},
|
||||
scrollBody: {
|
||||
...styles.scrollBody,
|
||||
backgroundColor: colors.background,
|
||||
},
|
||||
};
|
||||
|
||||
const save = useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
if (URI) {
|
||||
// validating only if its not empty. empty means use default
|
||||
if (await Notifications.isGroundControlUriValid(URI)) {
|
||||
await Notifications.saveUri(URI);
|
||||
presentAlert({ message: loc.settings.saved });
|
||||
} else {
|
||||
presentAlert({ message: loc.settings.not_a_valid_uri });
|
||||
}
|
||||
} else {
|
||||
await Notifications.saveUri('');
|
||||
presentAlert({ message: loc.settings.saved });
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(error);
|
||||
}
|
||||
setIsLoading(false);
|
||||
}, [URI]);
|
||||
|
||||
return isLoading ? (
|
||||
<BlueLoading />
|
||||
) : (
|
||||
<ScrollView style={stylesWithThemeHook.scroll} automaticallyAdjustContentInsets contentInsetAdjustmentBehavior="automatic">
|
||||
<ListItem
|
||||
Component={TouchableWithoutFeedback}
|
||||
title={loc.settings.push_notifications}
|
||||
subtitle={loc.settings.groundcontrol_explanation}
|
||||
switch={{ onValueChange: onNotificationsSwitch, value: isNotificationsEnabled, testID: 'NotificationsSwitch' }}
|
||||
/>
|
||||
<BlueSpacing20 />
|
||||
|
||||
<ButtonRNElements
|
||||
icon={{
|
||||
name: 'github',
|
||||
type: 'font-awesome',
|
||||
color: colors.foregroundColor,
|
||||
}}
|
||||
onPress={() => Linking.openURL('https://github.com/BlueWallet/GroundControl')}
|
||||
titleStyle={{ color: colors.buttonAlternativeTextColor }}
|
||||
title="github.com/BlueWallet/GroundControl"
|
||||
color={colors.buttonTextColor}
|
||||
buttonStyle={styles.buttonStyle}
|
||||
/>
|
||||
|
||||
<BlueCard>
|
||||
<View style={styles.uri}>
|
||||
<TextInput
|
||||
placeholder={Notifications.getDefaultUri()}
|
||||
value={URI}
|
||||
onChangeText={setURI}
|
||||
numberOfLines={1}
|
||||
style={styles.uriText}
|
||||
placeholderTextColor="#81868e"
|
||||
editable={!isLoading}
|
||||
textContentType="URL"
|
||||
autoCapitalize="none"
|
||||
underlineColorAndroid="transparent"
|
||||
/>
|
||||
</View>
|
||||
|
||||
<BlueSpacing20 />
|
||||
<BlueText style={styles.centered} onPress={() => setShowTokenInfo(isShowTokenInfo + 1)}>
|
||||
♪ Ground Control to Major Tom ♪
|
||||
</BlueText>
|
||||
<BlueText style={styles.centered} onPress={() => setShowTokenInfo(isShowTokenInfo + 1)}>
|
||||
♪ Commencing countdown, engines on ♪
|
||||
</BlueText>
|
||||
|
||||
{isShowTokenInfo >= 9 && (
|
||||
<View>
|
||||
<CopyToClipboardButton stringToCopy={tokenInfo} displayText={tokenInfo} />
|
||||
</View>
|
||||
)}
|
||||
|
||||
<BlueSpacing20 />
|
||||
<Button onPress={save} title={loc.settings.save} />
|
||||
</BlueCard>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
flex: 1,
|
||||
},
|
||||
uri: {
|
||||
flexDirection: 'row',
|
||||
borderColor: BlueCurrentTheme.colors.formBorder,
|
||||
borderBottomColor: BlueCurrentTheme.colors.formBorder,
|
||||
borderWidth: 1,
|
||||
borderBottomWidth: 0.5,
|
||||
backgroundColor: BlueCurrentTheme.colors.inputBackgroundColor,
|
||||
minHeight: 44,
|
||||
height: 44,
|
||||
alignItems: 'center',
|
||||
borderRadius: 4,
|
||||
},
|
||||
centered: {
|
||||
textAlign: 'center',
|
||||
},
|
||||
uriText: {
|
||||
flex: 1,
|
||||
color: '#81868e',
|
||||
marginHorizontal: 8,
|
||||
minHeight: 36,
|
||||
height: 36,
|
||||
},
|
||||
buttonStyle: {
|
||||
backgroundColor: 'transparent',
|
||||
flexDirection: I18nManager.isRTL ? 'row-reverse' : 'row',
|
||||
},
|
||||
});
|
||||
|
||||
export default NotificationSettings;
|
|
@ -1,17 +1,16 @@
|
|||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import { RouteProp, useFocusEffect, useRoute } from '@react-navigation/native';
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import assert from 'assert';
|
||||
import dayjs from 'dayjs';
|
||||
import { InteractionManager, Keyboard, Linking, ScrollView, StyleSheet, Text, TextInput, View } from 'react-native';
|
||||
import { InteractionManager, Linking, ScrollView, StyleSheet, Text, TextInput, View } from 'react-native';
|
||||
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
|
||||
import { BlueCard, BlueLoading, BlueSpacing20, BlueText } from '../../BlueComponents';
|
||||
import { Transaction, TWallet } from '../../class/wallets/types';
|
||||
import presentAlert from '../../components/Alert';
|
||||
import CopyToClipboardButton from '../../components/CopyToClipboardButton';
|
||||
import HandOffComponent from '../../components/HandOffComponent';
|
||||
import HeaderRightButton from '../../components/HeaderRightButton';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import ToolTipMenu from '../../components/TooltipMenu';
|
||||
import loc from '../../loc';
|
||||
|
@ -61,7 +60,7 @@ type NavigationProps = NativeStackNavigationProp<DetailViewStackParamList, 'Tran
|
|||
type RouteProps = RouteProp<DetailViewStackParamList, 'TransactionDetails'>;
|
||||
|
||||
const TransactionDetails = () => {
|
||||
const { setOptions, navigate } = useExtendedNavigation<NavigationProps>();
|
||||
const { addListener, navigate } = useExtendedNavigation<NavigationProps>();
|
||||
const { hash, walletID } = useRoute<RouteProps>().params;
|
||||
const { saveToDisk, txMetadata, counterpartyMetadata, wallets, getTransactions } = useStorage();
|
||||
const { selectedBlockExplorer } = useSettings();
|
||||
|
@ -88,29 +87,23 @@ const TransactionDetails = () => {
|
|||
},
|
||||
});
|
||||
|
||||
const handleOnSaveButtonTapped = useCallback(() => {
|
||||
Keyboard.dismiss();
|
||||
if (!tx) return;
|
||||
txMetadata[tx.hash] = { memo };
|
||||
if (counterpartyLabel && paymentCode) {
|
||||
counterpartyMetadata[paymentCode] = { label: counterpartyLabel };
|
||||
const saveTransactionDetails = useCallback(() => {
|
||||
if (tx) {
|
||||
txMetadata[tx.hash] = { memo };
|
||||
if (counterpartyLabel && paymentCode) {
|
||||
counterpartyMetadata[paymentCode] = { label: counterpartyLabel };
|
||||
}
|
||||
saveToDisk();
|
||||
}
|
||||
saveToDisk().then(_success => {
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
|
||||
presentAlert({ message: loc.transactions.transaction_saved });
|
||||
});
|
||||
}, [tx, txMetadata, memo, counterpartyLabel, paymentCode, saveToDisk, counterpartyMetadata]);
|
||||
|
||||
const HeaderRight = useMemo(
|
||||
() => <HeaderRightButton onPress={handleOnSaveButtonTapped} testID="SaveButton" disabled={false} title={loc.wallets.details_save} />,
|
||||
|
||||
[handleOnSaveButtonTapped],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// This effect only handles changes in `colors`
|
||||
setOptions({ headerRight: () => HeaderRight });
|
||||
}, [colors, HeaderRight, setOptions]);
|
||||
const unsubscribe = addListener('beforeRemove', () => {
|
||||
saveTransactionDetails();
|
||||
});
|
||||
|
||||
return unsubscribe;
|
||||
}, [addListener, saveTransactionDetails]);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
|
@ -160,8 +153,12 @@ const TransactionDetails = () => {
|
|||
}, [hash, wallets]),
|
||||
);
|
||||
|
||||
const handleMemoBlur = useCallback(() => {
|
||||
saveTransactionDetails();
|
||||
}, [saveTransactionDetails]);
|
||||
|
||||
const handleOnOpenTransactionOnBlockExplorerTapped = () => {
|
||||
const url = `${selectedBlockExplorer}/tx/${tx?.hash}`;
|
||||
const url = `${selectedBlockExplorer.url}/tx/${tx?.hash}`;
|
||||
Linking.canOpenURL(url)
|
||||
.then(supported => {
|
||||
if (supported) {
|
||||
|
@ -186,7 +183,7 @@ const TransactionDetails = () => {
|
|||
};
|
||||
|
||||
const handleCopyPress = (stringToCopy: string) => {
|
||||
Clipboard.setString(stringToCopy !== actionKeys.CopyToClipboard ? stringToCopy : `${selectedBlockExplorer}/tx/${tx?.hash}`);
|
||||
Clipboard.setString(stringToCopy !== actionKeys.CopyToClipboard ? stringToCopy : `${selectedBlockExplorer.url}/tx/${tx?.hash}`);
|
||||
};
|
||||
|
||||
if (isLoading || !tx) {
|
||||
|
@ -257,7 +254,7 @@ const TransactionDetails = () => {
|
|||
<HandOffComponent
|
||||
title={loc.transactions.details_title}
|
||||
type={HandOffActivityType.ViewInBlockExplorer}
|
||||
url={`${selectedBlockExplorer}/tx/${tx.hash}`}
|
||||
url={`${selectedBlockExplorer.url}/tx/${tx.hash}`}
|
||||
/>
|
||||
<BlueCard>
|
||||
<View>
|
||||
|
@ -268,6 +265,7 @@ const TransactionDetails = () => {
|
|||
clearButtonMode="while-editing"
|
||||
style={[styles.memoTextInput, stylesHooks.memoTextInput]}
|
||||
onChangeText={setMemo}
|
||||
onBlur={handleMemoBlur}
|
||||
testID="TransactionDetailsMemoInput"
|
||||
/>
|
||||
{isCounterpartyLabelVisible ? (
|
||||
|
@ -276,6 +274,7 @@ const TransactionDetails = () => {
|
|||
<TextInput
|
||||
placeholder={loc.send.counterparty_label_placeholder}
|
||||
value={counterpartyLabel}
|
||||
onBlur={handleMemoBlur}
|
||||
placeholderTextColor="#81868e"
|
||||
style={[styles.memoTextInput, stylesHooks.memoTextInput]}
|
||||
onChangeText={setCounterpartyLabel}
|
||||
|
|
|
@ -483,7 +483,7 @@ const TransactionStatus = () => {
|
|||
<HandOffComponent
|
||||
title={loc.transactions.details_title}
|
||||
type={HandOffActivityType.ViewInBlockExplorer}
|
||||
url={`${selectedBlockExplorer}/tx/${tx.hash}`}
|
||||
url={`${selectedBlockExplorer.url}/tx/${tx.hash}`}
|
||||
/>
|
||||
|
||||
<View style={styles.container}>
|
||||
|
|
|
@ -7,7 +7,7 @@ import { DynamicQRCode } from '../../components/DynamicQRCode';
|
|||
import SaveFileButton from '../../components/SaveFileButton';
|
||||
import { SquareButton } from '../../components/SquareButton';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import usePrivacy from '../../hooks/usePrivacy';
|
||||
import { disallowScreenshot } from 'react-native-screen-capture';
|
||||
import loc from '../../loc';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
import { ExportMultisigCoordinationSetupStackRootParamList } from '../../navigation/ExportMultisigCoordinationSetupStack';
|
||||
|
@ -75,7 +75,7 @@ const ExportMultisigCoordinationSetup: React.FC = () => {
|
|||
const wallet: TWallet | undefined = wallets.find(w => w.getID() === walletID);
|
||||
const dynamicQRCode = useRef<any>();
|
||||
const { colors } = useTheme();
|
||||
const { enableBlur, disableBlur } = usePrivacy();
|
||||
|
||||
const navigation = useNavigation();
|
||||
const stylesHook = StyleSheet.create({
|
||||
scrollViewContent: {
|
||||
|
@ -99,7 +99,7 @@ const ExportMultisigCoordinationSetup: React.FC = () => {
|
|||
dispatch({ type: ActionType.SET_LOADING, isLoading: true });
|
||||
|
||||
const task = InteractionManager.runAfterInteractions(() => {
|
||||
enableBlur();
|
||||
disallowScreenshot(true);
|
||||
if (wallet) {
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
|
@ -125,7 +125,7 @@ const ExportMultisigCoordinationSetup: React.FC = () => {
|
|||
|
||||
return () => {
|
||||
task.cancel();
|
||||
disableBlur();
|
||||
disallowScreenshot(false);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [walletID]),
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { RouteProp, useRoute } from '@react-navigation/native';
|
||||
import { ActivityIndicator, FlatList, LayoutAnimation, StyleSheet, View } from 'react-native';
|
||||
import IdleTimerManager from 'react-native-idle-timer';
|
||||
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
|
||||
import { BlueButtonLink, BlueFormLabel, BlueSpacing10, BlueSpacing20, BlueText } from '../../BlueComponents';
|
||||
import { HDSegwitBech32Wallet, WatchOnlyWallet } from '../../class';
|
||||
|
@ -19,6 +18,7 @@ import { AddWalletStackParamList } from '../../navigation/AddWalletStack';
|
|||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import { THDWalletForWatchOnly, TWallet } from '../../class/wallets/types';
|
||||
import { navigate } from '../../NavigationService';
|
||||
import { keepAwake, disallowScreenshot } from 'react-native-screen-capture';
|
||||
|
||||
type RouteProps = RouteProp<AddWalletStackParamList, 'ImportWalletDiscovery'>;
|
||||
type NavigationProp = NativeStackNavigationProp<AddWalletStackParamList, 'ImportWalletDiscovery'>;
|
||||
|
@ -115,8 +115,7 @@ const ImportWalletDiscovery: React.FC = () => {
|
|||
}
|
||||
};
|
||||
|
||||
IdleTimerManager.setIdleTimerDisabled(true);
|
||||
|
||||
keepAwake(true);
|
||||
task.current = startImport(importText, askPassphrase, searchAccounts, onProgress, onWallet, onPassword);
|
||||
|
||||
task.current.promise
|
||||
|
@ -134,7 +133,7 @@ const ImportWalletDiscovery: React.FC = () => {
|
|||
.finally(() => {
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||
setLoading(false);
|
||||
IdleTimerManager.setIdleTimerDisabled(false);
|
||||
keepAwake(false);
|
||||
});
|
||||
|
||||
return () => {
|
||||
|
@ -145,7 +144,8 @@ const ImportWalletDiscovery: React.FC = () => {
|
|||
|
||||
const handleCustomDerivation = () => {
|
||||
task.current?.stop();
|
||||
|
||||
keepAwake(false);
|
||||
disallowScreenshot(false);
|
||||
navigation.navigate('ImportCustomDerivationPath', { importText, password });
|
||||
};
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import { useNavigation, useRoute } from '@react-navigation/native';
|
|||
import { BackHandler, I18nManager, ScrollView, StyleSheet, Text, View } from 'react-native';
|
||||
import Button from '../../components/Button';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import usePrivacy from '../../hooks/usePrivacy';
|
||||
import { disallowScreenshot } from 'react-native-screen-capture';
|
||||
import loc from '../../loc';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
|
||||
|
@ -13,7 +13,6 @@ const PleaseBackup: React.FC = () => {
|
|||
const wallet = wallets.find(w => w.getID() === walletID);
|
||||
const navigation = useNavigation();
|
||||
const { colors } = useTheme();
|
||||
const { enableBlur, disableBlur } = usePrivacy();
|
||||
|
||||
const stylesHook = StyleSheet.create({
|
||||
flex: {
|
||||
|
@ -38,10 +37,10 @@ const PleaseBackup: React.FC = () => {
|
|||
|
||||
useEffect(() => {
|
||||
BackHandler.addEventListener('hardwareBackPress', handleBackButton);
|
||||
enableBlur();
|
||||
disallowScreenshot(true);
|
||||
return () => {
|
||||
BackHandler.removeEventListener('hardwareBackPress', handleBackButton);
|
||||
disableBlur();
|
||||
disallowScreenshot(false);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
|
|
@ -41,7 +41,7 @@ import prompt from '../../helpers/prompt';
|
|||
import { scanQrHelper } from '../../helpers/scan-qr';
|
||||
import { unlockWithBiometrics, useBiometrics } from '../../hooks/useBiometrics';
|
||||
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
||||
import usePrivacy from '../../hooks/usePrivacy';
|
||||
import { disallowScreenshot } from 'react-native-screen-capture';
|
||||
import loc from '../../loc';
|
||||
import ActionSheet from '../ActionSheet';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
|
@ -78,7 +78,6 @@ const ViewEditMultisigCosigners: React.FC = () => {
|
|||
Why the container view ? It was the easiest to get the ref for. No other reason.
|
||||
*/
|
||||
const discardChangesRef = useRef<View>(null);
|
||||
const { enableBlur, disableBlur } = usePrivacy();
|
||||
|
||||
const stylesHook = StyleSheet.create({
|
||||
root: {
|
||||
|
@ -190,7 +189,7 @@ const ViewEditMultisigCosigners: React.FC = () => {
|
|||
if (hasLoaded.current) return;
|
||||
setIsLoading(true);
|
||||
|
||||
enableBlur();
|
||||
disallowScreenshot(true);
|
||||
|
||||
const task = InteractionManager.runAfterInteractions(async () => {
|
||||
if (!w.current) {
|
||||
|
@ -206,7 +205,7 @@ const ViewEditMultisigCosigners: React.FC = () => {
|
|||
setIsLoading(false);
|
||||
});
|
||||
return () => {
|
||||
disableBlur();
|
||||
disallowScreenshot(false);
|
||||
task.cancel();
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
|
|
@ -4,7 +4,7 @@ import { ActivityIndicator, FlatList, StyleSheet, View, Platform, UIManager } fr
|
|||
import { WatchOnlyWallet } from '../../class';
|
||||
import { AddressItem } from '../../components/addresses/AddressItem';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import usePrivacy from '../../hooks/usePrivacy';
|
||||
import { disallowScreenshot } from 'react-native-screen-capture';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import { DetailViewStackParamList } from '../../navigation/DetailViewStackParamList';
|
||||
|
@ -131,7 +131,6 @@ const WalletAddresses: React.FC = () => {
|
|||
|
||||
const { colors } = useTheme();
|
||||
const { setOptions } = useExtendedNavigation<NavigationProps>();
|
||||
const { enableBlur, disableBlur } = usePrivacy();
|
||||
|
||||
const stylesHook = StyleSheet.create({
|
||||
root: {
|
||||
|
@ -177,12 +176,12 @@ const WalletAddresses: React.FC = () => {
|
|||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
enableBlur();
|
||||
disallowScreenshot(true);
|
||||
getAddresses();
|
||||
return () => {
|
||||
disableBlur();
|
||||
disallowScreenshot(false);
|
||||
};
|
||||
}, [enableBlur, disableBlur, getAddresses]),
|
||||
}, [getAddresses]),
|
||||
);
|
||||
|
||||
const data =
|
||||
|
|
|
@ -8,7 +8,7 @@ import HandOffComponent from '../../components/HandOffComponent';
|
|||
import QRCodeComponent from '../../components/QRCodeComponent';
|
||||
import SafeArea from '../../components/SafeArea';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import usePrivacy from '../../hooks/usePrivacy';
|
||||
import { disallowScreenshot } from 'react-native-screen-capture';
|
||||
import loc from '../../loc';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
import { HandOffActivityType } from '../../components/types';
|
||||
|
@ -25,7 +25,6 @@ const WalletExport: React.FC = () => {
|
|||
const wallet = wallets.find(w => w.getID() === walletID);
|
||||
const [qrCodeSize, setQRCodeSize] = useState(90);
|
||||
const appState = useRef(AppState.currentState);
|
||||
const { enableBlur, disableBlur } = usePrivacy();
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = AppState.addEventListener('change', nextAppState => {
|
||||
|
@ -55,7 +54,7 @@ const WalletExport: React.FC = () => {
|
|||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
enableBlur();
|
||||
disallowScreenshot(true);
|
||||
const task = InteractionManager.runAfterInteractions(async () => {
|
||||
if (wallet) {
|
||||
if (!wallet.getUserHasSavedExport()) {
|
||||
|
@ -67,7 +66,7 @@ const WalletExport: React.FC = () => {
|
|||
});
|
||||
return () => {
|
||||
task.cancel();
|
||||
disableBlur();
|
||||
disallowScreenshot(false);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [wallet]),
|
||||
|
|
|
@ -29,7 +29,7 @@ import QRCodeComponent from '../../components/QRCodeComponent';
|
|||
import { useTheme } from '../../components/themes';
|
||||
import confirm from '../../helpers/confirm';
|
||||
import prompt from '../../helpers/prompt';
|
||||
import usePrivacy from '../../hooks/usePrivacy';
|
||||
import { disallowScreenshot } from 'react-native-screen-capture';
|
||||
import loc from '../../loc';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
import { scanQrHelper } from '../../helpers/scan-qr';
|
||||
|
@ -60,15 +60,14 @@ const WalletsAddMultisigStep2 = () => {
|
|||
const [askPassphrase, setAskPassphrase] = useState(false);
|
||||
const openScannerButton = useRef();
|
||||
const data = useRef(new Array(n));
|
||||
const { enableBlur, disableBlur } = usePrivacy();
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
enableBlur();
|
||||
disallowScreenshot(true);
|
||||
return () => {
|
||||
disableBlur();
|
||||
disallowScreenshot(false);
|
||||
};
|
||||
}, [disableBlur, enableBlur]),
|
||||
}, []),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -5,7 +5,7 @@ import { BlueButtonLink, BlueFormLabel, BlueFormMultiInput, BlueSpacing20 } from
|
|||
import Button from '../../components/Button';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import { scanQrHelper } from '../../helpers/scan-qr';
|
||||
import usePrivacy from '../../hooks/usePrivacy';
|
||||
import { disallowScreenshot } from 'react-native-screen-capture';
|
||||
import loc from '../../loc';
|
||||
import {
|
||||
DoneAndDismissKeyboardInputAccessory,
|
||||
|
@ -30,7 +30,6 @@ const WalletsImport = () => {
|
|||
const [searchAccountsMenuState, setSearchAccountsMenuState] = useState(false);
|
||||
const [askPassphraseMenuState, setAskPassphraseMenuState] = useState(false);
|
||||
const [clearClipboardMenuState, setClearClipboardMenuState] = useState(true);
|
||||
const { enableBlur, disableBlur } = usePrivacy();
|
||||
|
||||
// Styles
|
||||
const styles = StyleSheet.create({
|
||||
|
@ -62,11 +61,11 @@ const WalletsImport = () => {
|
|||
});
|
||||
|
||||
useEffect(() => {
|
||||
enableBlur();
|
||||
disallowScreenshot(true);
|
||||
return () => {
|
||||
disableBlur();
|
||||
disallowScreenshot(false);
|
||||
};
|
||||
}, [disableBlur, enableBlur]);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (triggerImport) importButtonPressed();
|
||||
|
|
|
@ -7,7 +7,7 @@ import CopyTextToClipboard from '../../components/CopyTextToClipboard';
|
|||
import QRCodeComponent from '../../components/QRCodeComponent';
|
||||
import SafeArea from '../../components/SafeArea';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import usePrivacy from '../../hooks/usePrivacy';
|
||||
import { disallowScreenshot } from 'react-native-screen-capture';
|
||||
import loc from '../../loc';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
|
||||
|
@ -18,7 +18,6 @@ const PleaseBackupLNDHub = () => {
|
|||
const navigation = useNavigation();
|
||||
const { colors } = useTheme();
|
||||
const [qrCodeSize, setQRCodeSize] = useState(90);
|
||||
const { enableBlur, disableBlur } = usePrivacy();
|
||||
|
||||
const handleBackButton = useCallback(() => {
|
||||
navigation.getParent().pop();
|
||||
|
@ -39,13 +38,13 @@ const PleaseBackupLNDHub = () => {
|
|||
});
|
||||
|
||||
useEffect(() => {
|
||||
enableBlur();
|
||||
disallowScreenshot(true);
|
||||
BackHandler.addEventListener('hardwareBackPress', handleBackButton);
|
||||
return () => {
|
||||
disableBlur();
|
||||
disallowScreenshot(false);
|
||||
BackHandler.removeEventListener('hardwareBackPress', handleBackButton);
|
||||
};
|
||||
}, [disableBlur, enableBlur, handleBackButton]);
|
||||
}, [handleBackButton]);
|
||||
|
||||
const pop = () => navigation.getParent().pop();
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import CopyTextToClipboard from '../../components/CopyTextToClipboard';
|
|||
import HandOffComponent from '../../components/HandOffComponent';
|
||||
import QRCodeComponent from '../../components/QRCodeComponent';
|
||||
import SafeArea from '../../components/SafeArea';
|
||||
import usePrivacy from '../../hooks/usePrivacy';
|
||||
import { disallowScreenshot } from 'react-native-screen-capture';
|
||||
import loc from '../../loc';
|
||||
import { styles, useDynamicStyles } from './xpub.styles';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
|
@ -33,15 +33,14 @@ const WalletXpub: React.FC = () => {
|
|||
const stylesHook = useDynamicStyles(); // This now includes the theme implicitly
|
||||
const [qrCodeSize, setQRCodeSize] = useState<number>(90);
|
||||
const lastWalletIdRef = useRef<string | undefined>();
|
||||
const { enableBlur, disableBlur } = usePrivacy();
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
disallowScreenshot(true);
|
||||
// Skip execution if walletID hasn't changed
|
||||
if (lastWalletIdRef.current === walletID) {
|
||||
return;
|
||||
}
|
||||
enableBlur();
|
||||
const task = InteractionManager.runAfterInteractions(async () => {
|
||||
if (wallet) {
|
||||
const walletXpub = wallet.getXpub();
|
||||
|
@ -57,9 +56,9 @@ const WalletXpub: React.FC = () => {
|
|||
lastWalletIdRef.current = walletID;
|
||||
return () => {
|
||||
task.cancel();
|
||||
disableBlur();
|
||||
disallowScreenshot(false);
|
||||
};
|
||||
}, [walletID, enableBlur, wallet, xpub, navigation, disableBlur]),
|
||||
}, [walletID, wallet, xpub, navigation]),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -153,7 +153,8 @@ describe('BlueWallet UI Tests - no wallets', () => {
|
|||
await element(by.id('IsItMyAddress')).tap();
|
||||
await element(by.id('AddressInput')).replaceText('bc1q063ctu6jhe5k4v8ka99qac8rcm2tzjjnuktyrl');
|
||||
await element(by.id('CheckAddress')).tap();
|
||||
await expect(element(by.id('Result'))).toHaveText('None of the available wallets own the provided address.');
|
||||
await expect(element(by.text('None of the available wallets own the provided address.'))).toBeVisible();
|
||||
await element(by.text('OK')).tap();
|
||||
await device.pressBack();
|
||||
await device.pressBack();
|
||||
|
||||
|
|
|
@ -187,52 +187,70 @@ describe('BlueWallet UI Tests - import BIP84 wallet', () => {
|
|||
|
||||
await device.launchApp({ newInstance: true });
|
||||
|
||||
// go inside the wallet
|
||||
// Go inside the wallet
|
||||
await element(by.text('Imported HD SegWit (BIP84 Bech32 Native)')).tap();
|
||||
await yo('SendButton');
|
||||
await element(by.id('SendButton')).tap();
|
||||
|
||||
// lets create real transaction:
|
||||
// Add a few recipients initially
|
||||
await element(by.id('AddressInput')).replaceText('bc1qnapskphjnwzw2w3dk4anpxntunc77v6qrua0f7');
|
||||
await element(by.id('BitcoinAmountInput')).replaceText('0.0001\n');
|
||||
|
||||
// setting fee rate:
|
||||
await element(by.id('HeaderMenuButton')).tap();
|
||||
await element(by.text('Add Recipient')).tap();
|
||||
await yo('Transaction1');
|
||||
await element(by.id('AddressInput').withAncestor(by.id('Transaction1'))).replaceText('bc1q063ctu6jhe5k4v8ka99qac8rcm2tzjjnuktyrl');
|
||||
await element(by.id('BitcoinAmountInput').withAncestor(by.id('Transaction1'))).replaceText('0.0002\n');
|
||||
|
||||
// Now remove all recipients before proceeding
|
||||
await element(by.id('HeaderMenuButton')).tap();
|
||||
await element(by.text('Remove All Recipients')).tap();
|
||||
await element(by.text('OK')).tap();
|
||||
|
||||
// Now, let's proceed with the batch send process again
|
||||
// Let's create a real transaction again:
|
||||
await element(by.id('AddressInput')).replaceText('bc1qnapskphjnwzw2w3dk4anpxntunc77v6qrua0f7');
|
||||
await element(by.id('BitcoinAmountInput')).replaceText('0.0001\n');
|
||||
|
||||
// Setting fee rate:
|
||||
const feeRate = 2;
|
||||
await element(by.id('chooseFee')).tap();
|
||||
await element(by.id('feeCustom')).tap();
|
||||
await element(by.type('android.widget.EditText')).typeText(feeRate + '\n');
|
||||
await element(by.text('OK')).tap();
|
||||
|
||||
// lest add another two outputs
|
||||
// Let's add another two outputs
|
||||
await element(by.id('HeaderMenuButton')).tap();
|
||||
await element(by.text('Add Recipient')).tap();
|
||||
await yo('Transaction1'); // adding a recipient autoscrolls it to the last one
|
||||
await yo('Transaction1'); // Adding a recipient autoscrolls it to the last one
|
||||
await element(by.id('AddressInput').withAncestor(by.id('Transaction1'))).replaceText('bc1q063ctu6jhe5k4v8ka99qac8rcm2tzjjnuktyrl');
|
||||
await element(by.id('BitcoinAmountInput').withAncestor(by.id('Transaction1'))).replaceText('0.0002\n');
|
||||
|
||||
await element(by.id('HeaderMenuButton')).tap();
|
||||
await element(by.text('Add Recipient')).tap();
|
||||
await yo('Transaction2'); // adding a recipient autoscrolls it to the last one
|
||||
await yo('Transaction2'); // Adding a recipient autoscrolls it to the last one
|
||||
await element(by.id('AddressInput').withAncestor(by.id('Transaction2'))).replaceText('bc1qh6tf004ty7z7un2v5ntu4mkf630545gvhs45u7');
|
||||
await element(by.id('BitcoinAmountInput').withAncestor(by.id('Transaction2'))).replaceText('0.0003\n');
|
||||
|
||||
// remove last output, check if second output is shown
|
||||
// Remove last output, check if second output is shown
|
||||
await element(by.id('HeaderMenuButton')).tap();
|
||||
await element(by.text('Remove Recipient')).tap();
|
||||
await yo('Transaction1');
|
||||
|
||||
// adding it again
|
||||
// Add it again
|
||||
await element(by.id('HeaderMenuButton')).tap();
|
||||
await element(by.text('Add Recipient')).tap();
|
||||
await yo('Transaction2'); // adding a recipient autoscrolls it to the last one
|
||||
await yo('Transaction2'); // Adding a recipient autoscrolls it to the last one
|
||||
await element(by.id('AddressInput').withAncestor(by.id('Transaction2'))).replaceText('bc1qh6tf004ty7z7un2v5ntu4mkf630545gvhs45u7');
|
||||
await element(by.id('BitcoinAmountInput').withAncestor(by.id('Transaction2'))).replaceText('0.0003\n');
|
||||
|
||||
// remove second output
|
||||
// Remove second output
|
||||
await element(by.id('Transaction2')).swipe('right', 'fast', NaN, 0.2);
|
||||
await sleep(5000);
|
||||
await element(by.id('HeaderMenuButton')).tap();
|
||||
await element(by.text('Remove Recipient')).tap();
|
||||
|
||||
// creating and verifying. tx should have 3 outputs
|
||||
// Creating and verifying. tx should have 3 outputs
|
||||
if (process.env.TRAVIS) await sleep(5000);
|
||||
try {
|
||||
await element(by.id('CreateTransactionButton')).tap();
|
||||
|
@ -599,8 +617,7 @@ describe('BlueWallet UI Tests - import BIP84 wallet', () => {
|
|||
await element(by.text('Details')).tap();
|
||||
await expect(element(by.text('8b0ab2c7196312e021e0d3dc73f801693826428782970763df6134457bd2ec20'))).toBeVisible();
|
||||
await element(by.type('android.widget.EditText')).typeText('test1');
|
||||
await element(by.text('Save')).tap();
|
||||
await element(by.text('OK')).tap();
|
||||
await element(by.type('android.widget.EditText')).tapReturnKey();
|
||||
|
||||
// Terminate and reopen the app to confirm the note is persisted
|
||||
await device.launchApp({ newInstance: true });
|
||||
|
|
|
@ -159,12 +159,6 @@ jest.mock('realm', () => {
|
|||
};
|
||||
});
|
||||
|
||||
jest.mock('react-native-idle-timer', () => {
|
||||
return {
|
||||
setIdleTimerDisabled: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('react-native-ios-context-menu', () => {
|
||||
return {};
|
||||
});
|
||||
|
|
|
@ -20,6 +20,9 @@ const keys = {
|
|||
SaveChanges: 'saveChanges',
|
||||
ClearClipboard: 'clearClipboard',
|
||||
PaymentsCode: 'paymentsCode',
|
||||
RemoveAllRecipients: 'RemoveAllRecipients',
|
||||
AddRecipient: 'AddRecipient',
|
||||
RemoveRecipient: 'RemoveRecipient',
|
||||
};
|
||||
|
||||
const icons = {
|
||||
|
@ -71,6 +74,9 @@ const icons = {
|
|||
PaymentsCode: {
|
||||
iconValue: 'qrcode',
|
||||
},
|
||||
RemoveAllRecipients: { iconValue: 'person.2.slash' },
|
||||
AddRecipient: { iconValue: 'person.badge.plus' },
|
||||
RemoveRecipient: { iconValue: 'person.badge.minus' },
|
||||
};
|
||||
|
||||
export const CommonToolTipActions = {
|
||||
|
@ -99,6 +105,16 @@ export const CommonToolTipActions = {
|
|||
text: loc.transactions.details_copy_amount,
|
||||
icon: icons.Clipboard,
|
||||
},
|
||||
AddRecipient: {
|
||||
id: keys.AddRecipient,
|
||||
text: loc.send.details_add_rec_add,
|
||||
icon: icons.AddRecipient,
|
||||
},
|
||||
RemoveRecipient: {
|
||||
id: keys.RemoveRecipient,
|
||||
text: loc.send.details_add_rec_rem,
|
||||
icon: icons.RemoveRecipient,
|
||||
},
|
||||
CopyNote: {
|
||||
id: keys.CopyNote,
|
||||
text: loc.transactions.details_copy_note,
|
||||
|
@ -139,6 +155,11 @@ export const CommonToolTipActions = {
|
|||
text: loc.wallets.add_entropy_provide,
|
||||
icon: icons.Entropy,
|
||||
},
|
||||
RemoveAllRecipients: {
|
||||
id: keys.RemoveAllRecipients,
|
||||
text: loc.send.details_add_rec_rem_all,
|
||||
icon: icons.RemoveAllRecipients,
|
||||
},
|
||||
SearchAccount: {
|
||||
id: keys.SearchAccount,
|
||||
text: loc.wallets.import_search_accounts,
|
||||
|
|
Loading…
Add table
Reference in a new issue