Merge branch 'master' into wallrtd

This commit is contained in:
Marcos Rodriguez Velez 2024-10-12 16:08:53 -04:00
commit a9c4aaaf63
14 changed files with 924 additions and 819 deletions

View File

@ -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,
});
});

View File

@ -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)

View File

@ -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>

View File

@ -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,

View File

@ -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,26 @@ 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')
platform = "ios"
# Remove local master branch if it exists to avoid conflicts
sh("git branch -D master || true")
target_to_app_identifier.each do |target, app_identifier|
# 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 +247,6 @@ platform :ios do
app_identifier: app_identifiers,
readonly: true,
clone_branch_directly: true
)
end
@ -306,94 +297,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 +415,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 +446,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 +460,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 +489,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 +497,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

View File

@ -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&apos;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&apos;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>

33
ios/export_options.plist Normal file
View 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>

View File

@ -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}",

View File

@ -302,8 +302,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 +315,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."
},

View File

@ -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'));

14
package-lock.json generated
View File

@ -20,7 +20,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",
@ -89,7 +89,7 @@
"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-reanimated": "3.15.5",
"react-native-safe-area-context": "4.11.0",
"react-native-screens": "3.34.0",
"react-native-secure-key-store": "github:BlueWallet/react-native-secure-key-store#2076b4849e88aa0a78e08bfbb4ce3923e0925cbc",
@ -5601,8 +5601,8 @@
}
},
"node_modules/@react-native-menu/menu": {
"version": "1.1.2",
"resolved": "git+ssh://git@github.com/BlueWallet/menu.git#8c6004bae317e00ea8f163612af96dddb9cdf7e9",
"version": "1.1.3",
"resolved": "git+ssh://git@github.com/BlueWallet/menu.git#a33379d2bf2349066488ab9726c7d52bf76c49ac",
"license": "MIT",
"peerDependencies": {
"react": "*",
@ -20882,9 +20882,9 @@
}
},
"node_modules/react-native-reanimated": {
"version": "3.15.4",
"resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.15.4.tgz",
"integrity": "sha512-jcpHE+MnsvSbClhHgAFoken7SnaHrUJ5gVA8BUw8S1j6rkrw2VzRpht6cxn14NlqYx5ytjfG9IXJDOzq8tFvfw==",
"version": "3.15.5",
"resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.15.5.tgz",
"integrity": "sha512-admqeZ0w235vQqYPy+IUgmHu5gwKi9+b7AQRV1yIK3MbAMLYx+RY+tTUtx1CNE5X+rNZ6eSQssW5z77yTwIusg==",
"license": "MIT",
"dependencies": {
"@babel/plugin-transform-arrow-functions": "^7.0.0-0",

View File

@ -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",
@ -153,7 +153,7 @@
"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-reanimated": "3.15.5",
"react-native-safe-area-context": "4.11.0",
"react-native-screens": "3.34.0",
"react-native-secure-key-store": "github:BlueWallet/react-native-secure-key-store#2076b4849e88aa0a78e08bfbb4ce3923e0925cbc",

View 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;

View File

@ -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;