diff --git a/.github/workflows/build-ios-release-pullrequest.yml b/.github/workflows/build-ios-release-pullrequest.yml
index 77a60b229..ebf654e13 100644
--- a/.github/workflows/build-ios-release-pullrequest.yml
+++ b/.github/workflows/build-ios-release-pullrequest.yml
@@ -22,12 +22,26 @@ jobs:
branch_name: ${{ steps.get_latest_commit_details.outputs.branch_name }}
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
+ MATCH_READONLY: "true"
steps:
- name: Checkout Project
uses: actions/checkout@v4
with:
fetch-depth: 0 # Ensures the full Git history is available
+
+ - name: Setup Caching
+ uses: actions/cache@v3
+ with:
+ path: |
+ ~/Library/Caches/CocoaPods
+ ios/Pods
+ ~/.npm
+ node_modules
+ vendor/bundle
+ key: ${{ runner.os }}-ios-${{ hashFiles('**/package-lock.json', '**/Podfile.lock', '**/Gemfile.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-ios-
- name: Clear All Caches
if: github.ref == 'refs/heads/master'
@@ -81,15 +95,32 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 20
+ cache: 'npm'
- uses: maxim-lobanov/setup-xcode@v1
with:
- xcode-version: 16.2.0
+ xcode-version: latest
+
+ - name: Install iOS Simulator Runtime
+ run: |
+ echo "Available iOS simulator runtimes:"
+ xcrun simctl list runtimes
+
+ # Try to download the latest iOS 16.x simulator if not present
+ if (! xcrun simctl list runtimes | grep -q "iOS 16"); then
+ echo "Installing iOS 16.4 simulator..."
+ sudo xcode-select -s /Applications/Xcode.app
+ xcodebuild -downloadPlatform iOS
+ fi
+
+ echo "Available iOS simulator runtimes after install:"
+ xcrun simctl list runtimes
- name: Set Up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.1.6
+ bundler-cache: true
- name: Install Dependencies with Bundler
run: |
@@ -102,6 +133,7 @@ jobs:
- name: Install CocoaPods Dependencies
run: |
bundle exec fastlane ios install_pods
+ echo "CocoaPods dependencies installed successfully"
- name: Generate Build Number Based on Timestamp
id: generate_build_number
@@ -147,8 +179,26 @@ jobs:
- name: Build App
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
+ bundle exec fastlane ios build_app_lane
+
+ # Ensure IPA path is set for subsequent steps
+ if [ -f "./ios/build/ipa_path.txt" ]; then
+ IPA_PATH=$(cat ./ios/build/ipa_path.txt)
+ echo "IPA_OUTPUT_PATH=$IPA_PATH" >> $GITHUB_ENV
+ echo "ipa_output_path=$IPA_PATH" >> $GITHUB_OUTPUT
+ echo "Found IPA at: $IPA_PATH"
+ else
+ echo "Warning: ipa_path.txt not found, trying to locate IPA file manually..."
+ IPA_PATH=$(find ./ios -name "*.ipa" | head -n 1)
+ if [ -n "$IPA_PATH" ]; then
+ echo "IPA_OUTPUT_PATH=$IPA_PATH" >> $GITHUB_ENV
+ echo "ipa_output_path=$IPA_PATH" >> $GITHUB_OUTPUT
+ echo "Found IPA at: $IPA_PATH"
+ else
+ echo "Error: No IPA file found"
+ exit 1
+ fi
+ fi
- name: Upload Bugsnag Sourcemaps
if: success()
@@ -156,8 +206,8 @@ jobs:
env:
BUGSNAG_API_KEY: ${{ secrets.BUGSNAG_API_KEY }}
BUGSNAG_RELEASE_STAGE: production
- PROJECT_VERSION: ${{ needs.build.outputs.project_version }}
- NEW_BUILD_NUMBER: ${{ needs.build.outputs.new_build_number }}
+ PROJECT_VERSION: ${{ env.PROJECT_VERSION }}
+ NEW_BUILD_NUMBER: ${{ env.NEW_BUILD_NUMBER }}
- name: Upload Build Logs
if: always()
@@ -165,13 +215,32 @@ jobs:
with:
name: build_logs
path: ./ios/build_logs/
+ retention-days: 7
+ - name: Verify IPA File Before Upload
+ run: |
+ echo "Checking IPA file at: $IPA_OUTPUT_PATH"
+ if [ -f "$IPA_OUTPUT_PATH" ]; then
+ echo "✅ IPA file exists"
+ ls -la "$IPA_OUTPUT_PATH"
+ else
+ echo "❌ IPA file not found at: $IPA_OUTPUT_PATH"
+ echo "Current directory contents:"
+ find ./ios -name "*.ipa"
+ exit 1
+ fi
+
- name: Upload IPA as Artifact
if: success()
uses: actions/upload-artifact@v4
with:
- name: BlueWallet_${{env.PROJECT_VERSION}}_${{env.NEW_BUILD_NUMBER}}.ipa
- path: ${{ env.IPA_OUTPUT_PATH }} # Directly from Fastfile `IPA_OUTPUT_PATH`
+ name: BlueWallet_IPA
+ path: ${{ env.IPA_OUTPUT_PATH }}
+ retention-days: 7
+
+ - name: Delete Temporary Keychain
+ if: always()
+ run: bundle exec fastlane ios delete_temp_keychain
testflight-upload:
needs: build
@@ -191,6 +260,7 @@ jobs:
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.1.6
+ bundler-cache: true
- name: Install Dependencies with Bundler
run: |
@@ -200,18 +270,11 @@ jobs:
- 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
+ name: BlueWallet_IPA
path: ./
- 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
@@ -219,19 +282,23 @@ jobs:
- name: Verify IPA Path Before Upload
run: |
if [ ! -f "$IPA_OUTPUT_PATH" ]; then
- echo "IPA file not found at path: $IPA_OUTPUT_PATH"
+ echo "❌ IPA file not found at path: $IPA_OUTPUT_PATH"
+ ls -la $(pwd)
exit 1
+ else
+ echo "✅ Found IPA at: $IPA_OUTPUT_PATH"
fi
- name: Print Environment Variables for Debugging
run: |
echo "LATEST_COMMIT_MESSAGE: $LATEST_COMMIT_MESSAGE"
echo "BRANCH_NAME: $BRANCH_NAME"
+ echo "PROJECT_VERSION: $PROJECT_VERSION"
+ echo "NEW_BUILD_NUMBER: $NEW_BUILD_NUMBER"
+ echo "IPA_OUTPUT_PATH: $IPA_OUTPUT_PATH"
- name: Upload to TestFlight
- run: |
- ls -la $IPA_OUTPUT_PATH
- bundle exec fastlane ios upload_to_testflight_lane
+ run: 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 }}
@@ -242,18 +309,19 @@ 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 }}
- IPA_OUTPUT_PATH: ${{ env.IPA_OUTPUT_PATH }}
- name: Post PR Comment
if: success() && github.event_name == 'pull_request'
uses: actions/github-script@v6
env:
BUILD_NUMBER: ${{ needs.build.outputs.new_build_number }}
+ PROJECT_VERSION: ${{ needs.build.outputs.project_version }}
LATEST_COMMIT_MESSAGE: ${{ needs.build.outputs.latest_commit_message }}
with:
script: |
const buildNumber = process.env.BUILD_NUMBER;
- const message = `The build ${buildNumber} has been uploaded to TestFlight.`;
+ const version = process.env.PROJECT_VERSION;
+ const message = `✅ Build ${version} (${buildNumber}) has been uploaded to TestFlight and will be available for testing soon.`;
const prNumber = context.payload.pull_request.number;
const repo = context.repo;
github.rest.issues.createComment({
diff --git a/Gemfile b/Gemfile
index 40894e1eb..b0a72cff0 100644
--- a/Gemfile
+++ b/Gemfile
@@ -3,10 +3,14 @@ source "https://rubygems.org"
# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
ruby "3.1.6"
gem 'rubyzip', '2.4.1'
-gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1'
+gem 'cocoapods', '~> 1.14.3'
gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0'
-gem "fastlane", ">= 2.225.0"
+gem "fastlane", "~> 2.226.0"
gem 'xcodeproj', '< 1.26.0'
gem 'concurrent-ruby', '< 1.3.4'
+
+# Required for App Store Connect API
+gem "jwt"
+
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
eval_gemfile(plugins_path) if File.exist?(plugins_path)
diff --git a/Gemfile.lock b/Gemfile.lock
index 4bb4d2132..ace9d4579 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -24,18 +24,18 @@ GEM
json (>= 1.5.1)
artifactory (3.0.17)
atomos (0.1.3)
- aws-eventstream (1.3.0)
- aws-partitions (1.1050.0)
- aws-sdk-core (3.218.1)
+ aws-eventstream (1.3.1)
+ aws-partitions (1.1058.0)
+ aws-sdk-core (3.219.0)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9)
base64
jmespath (~> 1, >= 1.6.1)
- aws-sdk-kms (1.98.0)
+ aws-sdk-kms (1.99.0)
aws-sdk-core (~> 3, >= 3.216.0)
aws-sigv4 (~> 1.5)
- aws-sdk-s3 (1.180.0)
+ aws-sdk-s3 (1.182.0)
aws-sdk-core (~> 3, >= 3.216.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5)
@@ -46,10 +46,10 @@ GEM
benchmark (0.4.0)
bigdecimal (3.1.9)
claide (1.1.0)
- cocoapods (1.15.2)
+ cocoapods (1.14.3)
addressable (~> 2.8)
claide (>= 1.0.2, < 2.0)
- cocoapods-core (= 1.15.2)
+ cocoapods-core (= 1.14.3)
cocoapods-deintegrate (>= 1.0.3, < 2.0)
cocoapods-downloader (>= 2.1, < 3.0)
cocoapods-plugins (>= 1.0.0, < 2.0)
@@ -64,7 +64,7 @@ GEM
nap (~> 1.0)
ruby-macho (>= 2.3.0, < 3.0)
xcodeproj (>= 1.23.0, < 2.0)
- cocoapods-core (1.15.2)
+ cocoapods-core (1.14.3)
activesupport (>= 5.0, < 8)
addressable (~> 2.8)
algoliasearch (~> 1.0)
@@ -173,6 +173,9 @@ GEM
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
fastlane-plugin-browserstack (0.3.3)
rest-client (~> 2.0, >= 2.0.2)
+ fastlane-plugin-bugsnag (2.3.1)
+ git
+ xml-simple
fastlane-plugin-bugsnag_sourcemaps_upload (0.2.0)
fastlane-sirp (1.0.0)
sysrandom (~> 1.0)
@@ -180,6 +183,11 @@ GEM
fourflusher (2.3.1)
fuzzy_match (2.0.4)
gh_inspector (1.1.3)
+ git (3.0.0)
+ activesupport (>= 5.0)
+ addressable (~> 2.8)
+ process_executer (~> 1.3)
+ rchardet (~> 1.9)
google-apis-androidpublisher_v3 (0.54.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-core (0.11.3)
@@ -220,24 +228,26 @@ GEM
http-accept (1.7.0)
http-cookie (1.0.8)
domain_name (~> 0.5)
- httpclient (2.8.3)
+ httpclient (2.9.0)
+ mutex_m
i18n (1.14.7)
concurrent-ruby (~> 1.0)
jmespath (1.6.2)
json (2.10.1)
jwt (2.10.1)
base64
- logger (1.6.5)
+ logger (1.6.6)
mime-types (3.6.0)
logger
mime-types-data (~> 3.2015)
- mime-types-data (3.2025.0204)
+ mime-types-data (3.2025.0220)
mini_magick (4.13.2)
mini_mime (1.1.5)
minitest (5.25.4)
molinillo (0.8.0)
multi_json (1.15.0)
multipart-post (2.4.1)
+ mutex_m (0.3.0)
nanaimo (0.3.0)
nap (1.1.0)
naturally (2.2.1)
@@ -246,8 +256,10 @@ GEM
optparse (0.6.0)
os (1.1.4)
plist (3.7.2)
+ process_executer (1.3.0)
public_suffix (4.0.7)
rake (13.2.1)
+ rchardet (1.9.0)
representable (3.2.0)
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
@@ -258,7 +270,7 @@ GEM
mime-types (>= 1.16, < 4.0)
netrc (~> 0.8)
retriable (3.1.2)
- rexml (3.4.0)
+ rexml (3.4.1)
rouge (3.28.0)
ruby-macho (2.5.1)
ruby2_keywords (0.0.5)
@@ -300,17 +312,21 @@ GEM
rouge (~> 3.28.0)
xcpretty-travis-formatter (1.0.1)
xcpretty (~> 0.2, >= 0.0.7)
+ xml-simple (1.1.9)
+ rexml
PLATFORMS
ruby
DEPENDENCIES
activesupport (>= 6.1.7.5, != 7.1.0)
- cocoapods (>= 1.13, != 1.15.1, != 1.15.0)
+ cocoapods (~> 1.14.3)
concurrent-ruby (< 1.3.4)
- fastlane (>= 2.225.0)
+ fastlane (~> 2.226.0)
fastlane-plugin-browserstack
+ fastlane-plugin-bugsnag
fastlane-plugin-bugsnag_sourcemaps_upload
+ jwt
rubyzip (= 2.4.1)
xcodeproj (< 1.26.0)
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index ba84a5d81..89bcb1bc2 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -16,7 +16,6 @@
-
= 1) {
- Toast.makeText(context, "Only one widget instance is allowed.", Toast.LENGTH_SHORT).show()
- Log.e(TAG, "Attempted to add multiple widget instances.")
- return
- }
- sharedPref.edit().putInt(WIDGET_COUNT_KEY, widgetCount + 1).apply()
Log.d(TAG, "onEnabled called")
WidgetUpdateWorker.scheduleWork(context)
}
override fun onDisabled(context: Context) {
super.onDisabled(context)
- clearWidgetCount(context)
Log.d(TAG, "onDisabled called")
clearCache(context)
WorkManager.getInstance(context).cancelUniqueWork(WidgetUpdateWorker.WORK_NAME)
@@ -47,19 +37,9 @@ class BitcoinPriceWidget : AppWidgetProvider() {
override fun onDeleted(context: Context, appWidgetIds: IntArray) {
super.onDeleted(context, appWidgetIds)
- val sharedPref = context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)
- val widgetCount = sharedPref.getInt(WIDGET_COUNT_KEY, 1)
- val newCount = widgetCount - appWidgetIds.size
- sharedPref.edit().putInt(WIDGET_COUNT_KEY, if (newCount >= 0) newCount else 0).apply()
Log.d(TAG, "onDeleted called for widgets: ${appWidgetIds.joinToString()}")
}
- private fun clearWidgetCount(context: Context) {
- val sharedPref = context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)
- sharedPref.edit().putInt(WIDGET_COUNT_KEY, 0).apply()
- Log.d(TAG, "Widget count reset to 0")
- }
-
private fun clearCache(context: Context) {
val sharedPref = context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)
sharedPref.edit().clear().apply()
diff --git a/components/TooltipMenu.tsx b/components/TooltipMenu.tsx
index addaad844..4d0a6519d 100644
--- a/components/TooltipMenu.tsx
+++ b/components/TooltipMenu.tsx
@@ -1,7 +1,6 @@
import React, { useCallback, useMemo } from 'react';
-import { Platform, Pressable, TouchableOpacity } from 'react-native';
+import { Platform, TouchableOpacity } from 'react-native';
import { MenuView, MenuAction, NativeActionEvent } from '@react-native-menu/menu';
-import { ContextMenuView, RenderItem, OnPressMenuItemEventObject, IconConfig, MenuElementConfig } from 'react-native-ios-context-menu';
import { ToolTipMenuProps, Action } from './types';
import { useSettings } from '../hooks/context/useSettings';
@@ -9,11 +8,8 @@ const ToolTipMenu = (props: ToolTipMenuProps) => {
const {
title = '',
isMenuPrimaryAction = false,
- renderPreview,
disabled = false,
onPress,
- onMenuWillShow,
- onMenuWillHide,
buttonStyle,
onPressMenuItem,
children,
@@ -23,18 +19,6 @@ const ToolTipMenu = (props: ToolTipMenuProps) => {
const { language } = useSettings();
- // Map Menu Items for iOS Context Menu
- const mapMenuItemForContextMenuView = useCallback((action: Action) => {
- if (!action.id) return null;
- return {
- actionKey: action.id.toString(),
- actionTitle: action.text,
- icon: action.icon?.iconValue ? ({ iconType: 'SYSTEM', iconValue: action.icon.iconValue } as IconConfig) : undefined,
- state: action.menuState ?? undefined,
- attributes: action.disabled ? ['disabled'] : [],
- };
- }, []);
-
// Map Menu Items for RN Menu (supports subactions and displayInline)
const mapMenuItemForMenuView = useCallback((action: Action): MenuAction | null => {
if (!action.id) return null;
@@ -88,11 +72,6 @@ const ToolTipMenu = (props: ToolTipMenuProps) => {
return menuItem;
}, []);
- const contextMenuItems = useMemo(() => {
- const flattenedActions = props.actions.flat().filter(action => action.id);
- return flattenedActions.map(mapMenuItemForContextMenuView).filter(item => item !== null) as MenuElementConfig[];
- }, [props.actions, mapMenuItemForContextMenuView]);
-
const menuViewItemsIOS = useMemo(() => {
return props.actions
.map(actionGroup => {
@@ -119,13 +98,6 @@ const ToolTipMenu = (props: ToolTipMenuProps) => {
return mergedActions.map(mapMenuItemForMenuView).filter(item => item !== null) as MenuAction[];
}, [props.actions, mapMenuItemForMenuView]);
- const handlePressMenuItemForContextMenuView = useCallback(
- (event: OnPressMenuItemEventObject) => {
- onPressMenuItem(event.nativeEvent.actionKey);
- },
- [onPressMenuItem],
- );
-
const handlePressMenuItemForMenuView = useCallback(
({ nativeEvent }: NativeActionEvent) => {
onPressMenuItem(nativeEvent.event);
@@ -133,46 +105,6 @@ const ToolTipMenu = (props: ToolTipMenuProps) => {
[onPressMenuItem],
);
- const renderContextMenuView = () => {
- return (
-
- {onPress ? (
-
- {children}
-
- ) : (
- children
- )}
-
- );
- };
-
const renderMenuView = () => {
return (
{
);
};
- return props.actions.length > 0 ? (Platform.OS === 'ios' && renderPreview ? renderContextMenuView() : renderMenuView()) : null;
+ return props.actions.length > 0 ? renderMenuView() : null;
};
export default ToolTipMenu;
diff --git a/components/TransactionListItem.tsx b/components/TransactionListItem.tsx
index 8a30d02d4..e2c4c3f63 100644
--- a/components/TransactionListItem.tsx
+++ b/components/TransactionListItem.tsx
@@ -1,4 +1,4 @@
-import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
+import React, { useCallback, useEffect, useMemo, useRef, useState, memo } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
import Clipboard from '@react-native-clipboard/clipboard';
import { Linking, View, ViewStyle } from 'react-native';
@@ -36,7 +36,7 @@ interface TransactionListItemProps {
type NavigationProps = NativeStackNavigationProp;
-export const TransactionListItem: React.FC = React.memo(
+export const TransactionListItem: React.FC = memo(
({ item, itemPriceUnit = BitcoinUnit.BTC, walletID, searchQuery, style, renderHighlightedText }) => {
const [subtitleNumberOfLines, setSubtitleNumberOfLines] = useState(1);
const { colors } = useTheme();
@@ -367,4 +367,12 @@ export const TransactionListItem: React.FC = React.mem
);
},
+ (prevProps, nextProps) => {
+ return (
+ prevProps.item.hash === nextProps.item.hash &&
+ prevProps.item.received === nextProps.item.received &&
+ prevProps.itemPriceUnit === nextProps.itemPriceUnit &&
+ prevProps.walletID === nextProps.walletID
+ );
+ },
);
diff --git a/components/addresses/AddressItem.tsx b/components/addresses/AddressItem.tsx
index de377f5ee..78254d040 100644
--- a/components/addresses/AddressItem.tsx
+++ b/components/addresses/AddressItem.tsx
@@ -149,6 +149,7 @@ const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: Ad
title={item.address}
actions={menuActions}
onPressMenuItem={onToolTipPress}
+ // Revisit once RNMenu has renderPreview prop
renderPreview={renderPreview}
onPress={navigateToReceive}
isButton
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index 9f35ee1df..c770cdb17 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -192,6 +192,40 @@ end
# ===========================
platform :ios do
+ # Add helper methods for error handling and retries
+ def ensure_env_vars(vars)
+ vars.each do |var|
+ UI.user_error!("#{var} environment variable is missing") if ENV[var].nil? || ENV[var].empty?
+ end
+ end
+
+ def log_success(message)
+ UI.success("✅ #{message}")
+ end
+
+ def log_error(message)
+ UI.error("❌ #{message}")
+ end
+
+ # Method to safely call actions with retry logic
+ def with_retry(max_attempts = 3, action_name = "")
+ attempts = 0
+ begin
+ attempts += 1
+ yield
+ rescue => e
+ if attempts < max_attempts
+ wait_time = 10 * attempts
+ log_error("Attempt #{attempts}/#{max_attempts} for #{action_name} failed: #{e.message}")
+ UI.message("Retrying in #{wait_time} seconds...")
+ sleep(wait_time)
+ retry
+ else
+ log_error("#{action_name} failed after #{max_attempts} attempts: #{e.message}")
+ raise e
+ end
+ end
+ end
desc "Register new devices from a file"
lane :register_devices_from_txt do
@@ -238,26 +272,33 @@ platform :ios do
desc "Synchronize certificates and provisioning profiles"
lane :setup_provisioning_profiles do
+ required_vars = ["GIT_ACCESS_TOKEN", "GIT_URL", "ITC_TEAM_ID", "ITC_TEAM_NAME", "KEYCHAIN_PASSWORD"]
+ ensure_env_vars(required_vars)
+
UI.message("Setting up provisioning profiles...")
-
- 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
- platform: platform,
- app_identifier: app_identifier,
- team_id: ENV["ITC_TEAM_ID"],
- team_name: ENV["ITC_TEAM_NAME"],
- readonly: true,
- keychain_name: "temp_keychain",
- keychain_password: ENV["KEYCHAIN_PASSWORD"]
- )
+ with_retry(3, "Fetching provisioning profile for #{app_identifier}") do
+ UI.message("Fetching provisioning profile for #{app_identifier}...")
+ match(
+ git_basic_authorization: ENV["GIT_ACCESS_TOKEN"],
+ git_url: ENV["GIT_URL"],
+ type: "appstore",
+ clone_branch_directly: true,
+ platform: "ios",
+ app_identifier: app_identifier,
+ team_id: ENV["ITC_TEAM_ID"],
+ team_name: ENV["ITC_TEAM_NAME"],
+ readonly: true,
+ keychain_name: "temp_keychain",
+ keychain_password: ENV["KEYCHAIN_PASSWORD"]
+ )
+ log_success("Successfully fetched provisioning profile for #{app_identifier}")
+ end
end
+
+ log_success("All provisioning profiles set up")
end
desc "Fetch development certificates and provisioning profiles for Mac Catalyst"
@@ -402,48 +443,114 @@ lane :upload_bugsnag_sourcemaps do
end
desc "Build the iOS app"
-lane :build_app_lane do
- Dir.chdir(project_root) do
- UI.message("Building the application from: #{Dir.pwd}")
+ 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")
+ workspace_path = File.join(project_root, "ios", "BlueWallet.xcworkspace")
+ export_options_path = File.join(project_root, "ios", "export_options.plist")
- clear_derived_data_lane
-
- begin
- 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 => e
- UI.user_error!("build_ios_app failed: #{e.message}")
- end
+ clear_derived_data_lane
+
+ # Determine which iOS version to use
+ ios_version = determine_ios_version
- # Use File.join to construct paths without extra slashes
- ipa_path = lane_context[SharedValues::IPA_OUTPUT_PATH]
+ UI.message("Using iOS version: #{ios_version}")
+ UI.message("Using export options from: #{export_options_path}")
+
+ # Define the IPA output path before building
+ ipa_directory = File.join(project_root, "ios", "build")
+ ipa_name = "BlueWallet_#{ENV['PROJECT_VERSION']}_#{ENV['NEW_BUILD_NUMBER']}.ipa"
+ ipa_path = File.join(ipa_directory, ipa_name)
+
+ begin
+ build_ios_app(
+ scheme: "BlueWallet",
+ workspace: workspace_path,
+ export_method: "app-store",
+ export_options: export_options_path,
+ output_directory: ipa_directory,
+ output_name: ipa_name,
+ buildlog_path: File.join(project_root, "ios", "build_logs"),
+ )
+ rescue => e
+ UI.user_error!("build_ios_app failed: #{e.message}")
+ end
- if ipa_path && File.exist?(ipa_path)
- UI.message("IPA successfully found at: #{ipa_path}")
+ # Check for IPA path from both our defined path and fastlane's context
+ ipa_path = lane_context[SharedValues::IPA_OUTPUT_PATH] || ipa_path
+
+ # Ensure the directory exists
+ FileUtils.mkdir_p(File.dirname(ipa_path)) unless Dir.exist?(File.dirname(ipa_path))
+
+ if ipa_path && File.exist?(ipa_path)
+ UI.message("IPA successfully found at: #{ipa_path}")
+ else
+ # Try to find any IPA file as fallback
+ Dir.chdir(project_root) do
+ fallback_ipa = Dir.glob("**/*.ipa").first
+ if fallback_ipa
+ ipa_path = File.join(project_root, fallback_ipa)
+ UI.message("Found fallback IPA at: #{ipa_path}")
+ else
+ UI.user_error!("No IPA file found after build")
+ end
+ end
+ end
+
+ # Set both environment variable and GitHub Actions output
ENV['IPA_OUTPUT_PATH'] = ipa_path
- sh("echo 'IPA_OUTPUT_PATH=#{ipa_path}' >> $GITHUB_ENV") # Export for GitHub Actions
- else
- UI.user_error!("IPA not found after build_ios_app.")
+ # Set both standard output format and the newer GITHUB_OUTPUT format
+ sh("echo 'ipa_output_path=#{ipa_path}' >> $GITHUB_OUTPUT") if ENV['GITHUB_OUTPUT']
+ sh("echo ::set-output name=ipa_output_path::#{ipa_path}")
+
+ # Also write path to a file that can be read by subsequent steps
+ ipa_path_file = "#{ipa_directory}/ipa_path.txt"
+ File.write(ipa_path_file, ipa_path)
+ UI.success("Saved IPA path to: #{ipa_path_file}")
end
end
-end
+
+ desc "Delete temporary keychain"
+ lane :delete_temp_keychain do
+ UI.message("Deleting temporary keychain...")
+
+ delete_keychain(
+ name: "temp_keychain"
+ ) if File.exist?(File.expand_path("~/Library/Keychains/temp_keychain-db"))
+
+ UI.message("Temporary keychain deleted successfully.")
+ end
+
+ # Helper method to determine which iOS version to use
+ private_lane :determine_ios_version do
+ # Get available iOS simulator runtimes
+ runtimes_output = sh("xcrun simctl list runtimes 2>&1", log: false) rescue ""
+
+ if runtimes_output.include?("iOS")
+ # Extract available iOS versions
+ ios_versions = runtimes_output.scan(/iOS ([0-9.]+)/)
+ .flatten
+ .map { |v| Gem::Version.new(v) }
+ .sort
+ .reverse
+
+ if ios_versions.any?
+ latest_version = ios_versions.first.to_s
+ UI.success("Found iOS simulator version: #{latest_version}")
+ latest_version # Implicit return - last expression is returned
+ else
+ # Default to a reasonable iOS version if none found
+ UI.important("No iOS simulator versions found. Using default version.")
+ "17.6" # Implicit return
+ end
+ else
+ # Default to a reasonable iOS version if no iOS runtimes
+ UI.important("No iOS simulator runtimes found. Using default version.")
+ "17.6" # Implicit return
+ end
+ end
+
end
# ===========================
# Global Lanes
diff --git a/fastlane/Matchfile b/fastlane/Matchfile
index ba2f5652b..9772ca474 100644
--- a/fastlane/Matchfile
+++ b/fastlane/Matchfile
@@ -3,33 +3,39 @@
# URL of the Git repository to store the certificates
git_url(ENV["GIT_URL"])
-# Define the type of match to run, could be one of 'appstore', 'adhoc', 'development', or 'enterprise'.
-# For example, use 'appstore' for App Store builds, 'adhoc' for Ad Hoc distribution,
-# 'development' for development builds, and 'enterprise' for In-House (enterprise) distribution.
-type("appstore")
+# Define the type of match to run
+# Default to "appstore" but can be overridden
+type(ENV["MATCH_TYPE"] || "appstore")
-app_identifier(["io.bluewallet.bluewallet", "io.bluewallet.bluewallet.watch", "io.bluewallet.bluewallet.watch.extension", "io.bluewallet.bluewallet.Stickers", "io.bluewallet.bluewallet.MarketWidget"]) # Replace with your app identifiers
+# App identifiers for all BlueWallet apps
+app_identifier([
+ "io.bluewallet.bluewallet",
+ "io.bluewallet.bluewallet.watch",
+ "io.bluewallet.bluewallet.watch.extension",
+ "io.bluewallet.bluewallet.Stickers",
+ "io.bluewallet.bluewallet.MarketWidget"
+])
-# List of app identifiers to create provisioning profiles for.
-# Replace with your app's bundle identifier(s).
-
-# Your Apple Developer account email address.
+# Your Apple Developer account email address
username(ENV["APPLE_ID"])
-# The ID of your Apple Developer team if you're part of multiple teams
+# The ID of your Apple Developer team
team_id(ENV["ITC_TEAM_ID"])
-# Set this to true if match should only read existing certificates and profiles
-# and not create new ones.
-readonly(true)
+# Set readonly based on environment (default to true for safety)
+# Set to false explicitly when new profiles need to be created
+readonly(ENV["MATCH_READONLY"] == "false" ? false : true)
-# Optional: The Git branch that is used for match.
-# Default is 'master'.
-
-# Optional: Path to a specific SSH key to be used by match.
-# Only needed if you're using a private repository and match needs to use SSH keys for authentication.
-# ssh_key("/path/to/your/private/key")
-
-# Optional: Define the platform to use, can be 'ios', 'macos', or 'tvos'.
-# For React Native projects, you'll typically use 'ios'.
+# Define the platform to use
platform("ios")
+
+# Git basic authentication through access token
+# This is useful for CI/CD environments where SSH keys aren't available
+git_basic_authorization(ENV["GIT_ACCESS_TOKEN"])
+
+# Storage mode (git by default)
+storage_mode("git")
+
+# Optional: The Git branch that is used for match
+# Default is 'master'
+# branch("main")
diff --git a/fastlane/Pluginfile b/fastlane/Pluginfile
index 99f8763a1..29820960d 100644
--- a/fastlane/Pluginfile
+++ b/fastlane/Pluginfile
@@ -4,3 +4,4 @@
gem 'fastlane-plugin-browserstack'
gem 'fastlane-plugin-bugsnag_sourcemaps_upload'
+gem "fastlane-plugin-bugsnag"
diff --git a/helpers/screenProtect.ts b/helpers/screenProtect.ts
new file mode 100644
index 000000000..f32bb49a1
--- /dev/null
+++ b/helpers/screenProtect.ts
@@ -0,0 +1,23 @@
+// import { enableSecureView, disableSecureView, forbidAndroidShare, allowAndroidShare } from 'react-native-prevent-screenshot-ios-android';
+// import { Platform } from 'react-native';
+// import { isDesktop } from '../blue_modules/environment';
+
+export const enableScreenProtect = () => {
+ // if (isDesktop) return;
+ // if (Platform.OS === 'ios') {
+ // enableSecureView();
+ // } else if (Platform.OS === 'android') {
+ // forbidAndroidShare();
+ // }
+};
+
+export const disableScreenProtect = () => {
+ // if (isDesktop) return;
+ // if (Platform.OS === 'ios') {
+ // disableSecureView();
+ // } else if (Platform.OS === 'android') {
+ // allowAndroidShare();
+ // }
+};
+
+// CURRENTLY UNUSED AS WE WAIT FOR NAV 7 SUPPORT
diff --git a/ios/BlueWallet.xcodeproj/project.pbxproj b/ios/BlueWallet.xcodeproj/project.pbxproj
index 4cdd85588..aeacd5939 100644
--- a/ios/BlueWallet.xcodeproj/project.pbxproj
+++ b/ios/BlueWallet.xcodeproj/project.pbxproj
@@ -164,7 +164,7 @@
B4D0B2682C1DED67006B6B1B /* ReceiveMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4D0B2672C1DED67006B6B1B /* ReceiveMethod.swift */; };
B4EE583C226703320003363C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B40D4E35225841ED00428FCC /* Assets.xcassets */; };
B4EFF73B2C3F6C5E0095D655 /* MockData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4EFF73A2C3F6C5E0095D655 /* MockData.swift */; };
- C978A716948AB7DEC5B6F677 /* (null) in Frameworks */ = {isa = PBXBuildFile; };
+ C978A716948AB7DEC5B6F677 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -428,7 +428,7 @@
files = (
782F075B5DD048449E2DECE9 /* libz.tbd in Frameworks */,
764B49B1420D4AEB8109BF62 /* libsqlite3.0.tbd in Frameworks */,
- C978A716948AB7DEC5B6F677 /* (null) in Frameworks */,
+ C978A716948AB7DEC5B6F677 /* BuildFile in Frameworks */,
17CDA0718F42DB2CE856C872 /* libPods-BlueWallet.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -1584,7 +1584,7 @@
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = A7W54YZ4WU;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = Stickers/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 14.1;
+ IPHONEOS_DEPLOYMENT_TARGET = 15.6;
LIBRARY_SEARCH_PATHS = (
"$(SDKROOT)/usr/lib/swift",
"$(SDKROOT)/System/iOSSupport/usr/lib/swift",
@@ -1627,7 +1627,7 @@
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = A7W54YZ4WU;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = Stickers/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 14.1;
+ IPHONEOS_DEPLOYMENT_TARGET = 15.6;
LIBRARY_SEARCH_PATHS = (
"$(SDKROOT)/usr/lib/swift",
"$(SDKROOT)/System/iOSSupport/usr/lib/swift",
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 00efe5a69..4975e296c 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -1297,7 +1297,7 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- - react-native-image-picker (7.2.3):
+ - react-native-image-picker (8.2.0):
- DoubleConversion
- glog
- hermes-engine
@@ -1318,10 +1318,29 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- - react-native-ios-context-menu (1.15.3):
- - React-Core
- react-native-menu (1.2.1):
- React
+ - react-native-prevent-screenshot-ios-android (1.1.0):
+ - DoubleConversion
+ - glog
+ - hermes-engine
+ - RCT-Folly (= 2024.01.01.00)
+ - RCTRequired
+ - RCTTypeSafety
+ - React-Core
+ - React-debug
+ - React-Fabric
+ - React-featureflags
+ - React-graphics
+ - React-ImageManager
+ - React-NativeModulesApple
+ - React-RCTFabric
+ - React-rendererdebug
+ - React-utils
+ - ReactCodegen
+ - ReactCommon/turbomodule/bridging
+ - ReactCommon/turbomodule/core
+ - Yoga
- react-native-randombytes (3.6.1):
- React-Core
- react-native-safe-area-context (5.2.0):
@@ -1841,7 +1860,7 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- - RNShare (11.1.0):
+ - RNShare (12.0.9):
- DoubleConversion
- glog
- hermes-engine
@@ -1939,8 +1958,8 @@ DEPENDENCIES:
- 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-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-prevent-screenshot-ios-android (from `../node_modules/react-native-prevent-screenshot-ios-android`)
- 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`)
@@ -2094,10 +2113,10 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-document-picker"
react-native-image-picker:
:path: "../node_modules/react-native-image-picker"
- react-native-ios-context-menu:
- :path: "../node_modules/react-native-ios-context-menu"
react-native-menu:
:path: "../node_modules/@react-native-menu/menu"
+ react-native-prevent-screenshot-ios-android:
+ :path: "../node_modules/react-native-prevent-screenshot-ios-android"
react-native-randombytes:
:path: "../node_modules/react-native-randombytes"
react-native-safe-area-context:
@@ -2258,9 +2277,9 @@ SPEC CHECKSUMS:
react-native-blue-crypto: de5babd59b17fbf3fc31d2e1e5d59ec859093fbc
react-native-bw-file-access: fe925b77dbf48500df0b294c6851f8c84607a203
react-native-document-picker: 530879d9e89b490f0954bcc4ab697c5b5e35d659
- react-native-image-picker: 130fad649d07e4eec8faaed361d3bba570e1e5ff
- react-native-ios-context-menu: 986da6dcba70094bcc2a8049f68410fe7d25aff1
+ react-native-image-picker: 86f8954a0b8c0f85d56fa1d85ae87936ae74e615
react-native-menu: 2cfe0a3b3c610ed331f00d9f0df300db14ba8692
+ react-native-prevent-screenshot-ios-android: 490b2ae701658753e819ca215201f4aa8cab3d53
react-native-randombytes: 3c8f3e89d12487fd03a2f966c288d495415fc116
react-native-safe-area-context: 3e33e7c43c8b74dba436a5a32651cb8d7064c740
react-native-screen-capture: 7b6121f529681ed2fde36cdedadd0bb39e9a3796
@@ -2313,7 +2332,7 @@ SPEC CHECKSUMS:
RNReactNativeHapticFeedback: 00ba111b82aa266bb3ee1aa576831c2ea9a9dfad
RNReanimated: 66cf0f600a26d2b5e74c6e0b1c77c1ab1f62fc05
RNScreens: b3975354ddafe0fb00112a9054898ccf0d92c78e
- RNShare: 6204e6a1987ba3e7c47071ef703e5449a0e3548a
+ RNShare: 381ed02f6c0dc42b8c24bb5a1e5df0ee5fd91354
RNSVG: a07e14363aa208062c6483bad24a438d5986d490
RNVectorIcons: 182892e7d1a2f27b52d3c627eca5d2665a22ee28
RNWatch: 28fe1f5e0c6410d45fd20925f4796fce05522e3f
@@ -2323,4 +2342,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: eb430c3fd96af23d4962c4c041798d65efc6843e
-COCOAPODS: 1.15.2
+COCOAPODS: 1.14.3
diff --git a/package-lock.json b/package-lock.json
index bf3316759..d50f616b2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -26,10 +26,10 @@
"@react-native-menu/menu": "https://github.com/BlueWallet/menu.git#038a9c9",
"@react-native/gradle-plugin": "0.76.7",
"@react-native/metro-config": "0.76.7",
- "@react-navigation/devtools": "7.0.15",
- "@react-navigation/drawer": "7.1.1",
- "@react-navigation/native": "7.0.14",
- "@react-navigation/native-stack": "7.2.0",
+ "@react-navigation/devtools": "7.0.16",
+ "@react-navigation/drawer": "7.1.2",
+ "@react-navigation/native": "7.0.15",
+ "@react-navigation/native-stack": "7.2.1",
"@rneui/base": "4.0.0-rc.8",
"@rneui/themed": "4.0.0-rc.8",
"@spsina/bip47": "github:BlueWallet/bip47#df82345",
@@ -77,12 +77,12 @@
"react-native-gesture-handler": "2.23.1",
"react-native-handoff": "github:BlueWallet/react-native-handoff#v0.0.4",
"react-native-haptic-feedback": "2.3.3",
- "react-native-image-picker": "7.2.3",
- "react-native-ios-context-menu": "github:BlueWallet/react-native-ios-context-menu#e5c1217cd220bfab6e6d9a7c65838545082e3f8e",
+ "react-native-image-picker": "8.2.0",
"react-native-keychain": "9.1.0",
"react-native-linear-gradient": "2.8.3",
"react-native-localize": "3.4.1",
"react-native-permissions": "5.2.5",
+ "react-native-prevent-screenshot-ios-android": "github:BlueWallet/react-native-prevent-screenshot-ios-android#133004e",
"react-native-prompt-android": "github:BlueWallet/react-native-prompt-android#ed168d66fed556bc2ed07cf498770f058b78a376",
"react-native-push-notification": "8.1.1",
"react-native-qrcode-svg": "6.3.12",
@@ -94,7 +94,7 @@
"react-native-screen-capture": "github:BlueWallet/react-native-screen-capture#18cb79f",
"react-native-screens": "4.9.1",
"react-native-secure-key-store": "github:BlueWallet/react-native-secure-key-store#2076b4849e88aa0a78e08bfbb4ce3923e0925cbc",
- "react-native-share": "11.1.0",
+ "react-native-share": "12.0.9",
"react-native-svg": "15.11.2",
"react-native-tcp-socket": "6.2.0",
"react-native-vector-icons": "10.2.0",
@@ -2459,12 +2459,6 @@
"bugsnag-source-maps": "bin/cli"
}
},
- "node_modules/@dominicstop/ts-event-emitter": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@dominicstop/ts-event-emitter/-/ts-event-emitter-1.1.0.tgz",
- "integrity": "sha512-CcxmJIvUb1vsFheuGGVSQf4KdPZC44XolpUT34+vlal+LyQoBUOn31pjFET5M9ctOxEpt8xa0M3/2M7uUiAoJw==",
- "license": "MIT"
- },
"node_modules/@egjs/hammerjs": {
"version": "2.0.17",
"resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz",
@@ -4701,7 +4695,7 @@
"node_modules/@lodev09/react-native-true-sheet": {
"version": "2.0.0",
"resolved": "git+ssh://git@github.com/BlueWallet/react-native-true-sheet.git#5945184a2fea9fe5ba8f5cfcdb20e3fc5eed6e37",
- "integrity": "sha512-KmqW5bBFowvbZ0d3u960PUx+avPZ80IaRPHszvarI5leeYrkTSUGqEeVVAQnaBHMfl5u6rrOVA4NoJHPsqU0Vg==",
+ "integrity": "sha512-JoMgC3w8Xgzvb4zHRnBdycBDuhz1O8MuKh9LE3QJjCElcm8x0n3asHzrt+XaLs3XfVrvq/LNyIMQSSYtvvhFKA==",
"license": "MIT",
"workspaces": [
"example",
@@ -6614,12 +6608,12 @@
}
},
"node_modules/@react-navigation/core": {
- "version": "7.3.1",
- "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-7.3.1.tgz",
- "integrity": "sha512-S3KCGvNsoqVk8ErAtQI2EAhg9185lahF5OY01ofrrD4Ij/uk3QEHHjoGQhR5l5DXSCSKr1JbMQA7MEKMsBiWZA==",
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-7.4.0.tgz",
+ "integrity": "sha512-URiDluFl7cLA7GtOAsEvRqPmEMlSSXadqQ3wi9+Dl43dNRMqnoF76WQ9BCXeUPLydJq4yVte9XeMPyD6a0lY1g==",
"license": "MIT",
"dependencies": {
- "@react-navigation/routers": "^7.1.2",
+ "@react-navigation/routers": "^7.2.0",
"escape-string-regexp": "^4.0.0",
"nanoid": "3.3.8",
"query-string": "^7.1.3",
@@ -6638,9 +6632,9 @@
"license": "MIT"
},
"node_modules/@react-navigation/devtools": {
- "version": "7.0.15",
- "resolved": "https://registry.npmjs.org/@react-navigation/devtools/-/devtools-7.0.15.tgz",
- "integrity": "sha512-pxEBVtd6e5ocT7bs6k6ghOJNyb9Fzxm+EYHemHQ53GEin1sQKYpsSHWZEJdFj1cxYp+/+KCT+TueuNDFkJOr4Q==",
+ "version": "7.0.16",
+ "resolved": "https://registry.npmjs.org/@react-navigation/devtools/-/devtools-7.0.16.tgz",
+ "integrity": "sha512-3rWR5TmI+JhxFyJMQXcAEdPumBIThpI1pj0YInON6u8olpL7nD5QGdGXaF71hilPpjID43+tgtV98fcAvhzzOA==",
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
@@ -6652,18 +6646,18 @@
}
},
"node_modules/@react-navigation/drawer": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/@react-navigation/drawer/-/drawer-7.1.1.tgz",
- "integrity": "sha512-34UqRS5OLFaNXPs5ocz3Du9c7em0P7fFMPYCZn/MxadDzQ4Mn/74pmJczmiyvyvz8vcWsNRbZ3Qswm0Dv6z60w==",
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/@react-navigation/drawer/-/drawer-7.1.2.tgz",
+ "integrity": "sha512-EIlS5PPzVQ9WJ4xQZytAUTXiVbZcUYfPFlkaAWZHIl7O/suoJIpu52yXwjbjG0XhNu6Vup23W35HJWHxpgKQEQ==",
"license": "MIT",
"dependencies": {
- "@react-navigation/elements": "^2.2.5",
+ "@react-navigation/elements": "^2.2.6",
"color": "^4.2.3",
"react-native-drawer-layout": "^4.1.1",
"use-latest-callback": "^0.2.1"
},
"peerDependencies": {
- "@react-navigation/native": "^7.0.14",
+ "@react-navigation/native": "^7.0.15",
"react": ">= 18.2.0",
"react-native": "*",
"react-native-gesture-handler": ">= 2.0.0",
@@ -6673,16 +6667,16 @@
}
},
"node_modules/@react-navigation/elements": {
- "version": "2.2.5",
- "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-2.2.5.tgz",
- "integrity": "sha512-sDhE+W14P7MNWLMxXg1MEVXwkLUpMZJGflE6nQNzLmolJQIHgcia0Mrm8uRa3bQovhxYu1UzEojLZ+caoZt7Fg==",
+ "version": "2.2.6",
+ "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-2.2.6.tgz",
+ "integrity": "sha512-UPeaCcEDSDRaxjG+qEHbur6jmNW/f9QNCyPsUt6NMgPEdbB5Z8K8oSx2swIaiCnvUbs/K5G3MuWkqQxGj8QXXA==",
"license": "MIT",
"dependencies": {
"color": "^4.2.3"
},
"peerDependencies": {
"@react-native-masked-view/masked-view": ">= 0.2.0",
- "@react-navigation/native": "^7.0.14",
+ "@react-navigation/native": "^7.0.15",
"react": ">= 18.2.0",
"react-native": "*",
"react-native-safe-area-context": ">= 4.0.0"
@@ -6694,12 +6688,12 @@
}
},
"node_modules/@react-navigation/native": {
- "version": "7.0.14",
- "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.0.14.tgz",
- "integrity": "sha512-Gi6lLw4VOGSWAhmUdJOMauOKGK51/YA1CprjXm91sNfgERWvznqEMw8QmUQx9SEqYfi0LfZhbzpMst09SJ00lw==",
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.0.15.tgz",
+ "integrity": "sha512-72PabJJ8VY3GM8i/DCutFW+ATED96ZV6NH+bW+ry1EL0ZFGHoie96H+KzTqktsrUbBw1rd9KXbEQhBQgo0N7iQ==",
"license": "MIT",
"dependencies": {
- "@react-navigation/core": "^7.3.1",
+ "@react-navigation/core": "^7.4.0",
"escape-string-regexp": "^4.0.0",
"fast-deep-equal": "^3.1.3",
"nanoid": "3.3.8",
@@ -6711,16 +6705,16 @@
}
},
"node_modules/@react-navigation/native-stack": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@react-navigation/native-stack/-/native-stack-7.2.0.tgz",
- "integrity": "sha512-mw7Nq9qQrGsmJmCTwIIWB7yY/3tWYXvQswx+HJScGAadIjemvytJXm1fcl3+YZ9T9Ym0aERcVe5kDs+ny3X4vA==",
+ "version": "7.2.1",
+ "resolved": "https://registry.npmjs.org/@react-navigation/native-stack/-/native-stack-7.2.1.tgz",
+ "integrity": "sha512-zqC6DVpO4pFZrl+8JuIgV8qk+AGdTuv+hJ5EHePmzs9gYSUrDpw6LahFCiXshwBvi9LinIw9Do7mtnQK2Q8AGA==",
"license": "MIT",
"dependencies": {
- "@react-navigation/elements": "^2.2.5",
+ "@react-navigation/elements": "^2.2.6",
"warn-once": "^0.1.1"
},
"peerDependencies": {
- "@react-navigation/native": "^7.0.14",
+ "@react-navigation/native": "^7.0.15",
"react": ">= 18.2.0",
"react-native": "*",
"react-native-safe-area-context": ">= 4.0.0",
@@ -6728,9 +6722,9 @@
}
},
"node_modules/@react-navigation/routers": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-7.1.2.tgz",
- "integrity": "sha512-emdEjpVDK8zbiu2GChC8oYIAub9i/OpNuQJekVsbyFCBz4/TzaBzms38Q53YaNhdIFNmiYLfHv/Y1Ub7KYfm3w==",
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-7.2.0.tgz",
+ "integrity": "sha512-lMyib39H4a//u+eiyt162U6TwCfI8zJbjl9ovjKtDddQ4/Vf7b8/OhyimnJH09N2CBfm4pv0gCV6Q0WnZcfaJg==",
"license": "MIT",
"dependencies": {
"nanoid": "3.3.8"
@@ -22257,28 +22251,15 @@
}
},
"node_modules/react-native-image-picker": {
- "version": "7.2.3",
- "resolved": "https://registry.npmjs.org/react-native-image-picker/-/react-native-image-picker-7.2.3.tgz",
- "integrity": "sha512-zKIZUlQNU3EtqizsXSH92zPeve4vpUrsqHu2kkpCxWE9TZhJFZBb+irDsBOY8J21k0+Edgt06TMQGJ+iPUIXyA==",
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/react-native-image-picker/-/react-native-image-picker-8.2.0.tgz",
+ "integrity": "sha512-jIGllQJuJIn0YKss/JEeb0Kos1HSsnIpU+i3bYxR27sOxSyDZQyP9dKR22olssQPlfH+rGNR/Jc6xKRkhm48vw==",
"license": "MIT",
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
- "node_modules/react-native-ios-context-menu": {
- "version": "1.15.3",
- "resolved": "git+ssh://git@github.com/BlueWallet/react-native-ios-context-menu.git#e5c1217cd220bfab6e6d9a7c65838545082e3f8e",
- "integrity": "sha512-cQnRYOcP3zGQpCEm7w8oSAKDibX3Ncu8G4xof3mXCbXkqwM00Vdbref8ZJcK9omBY5vwEvyNjFLQ+C/NW47iyQ==",
- "license": "MIT",
- "dependencies": {
- "@dominicstop/ts-event-emitter": "^1.1.0"
- },
- "peerDependencies": {
- "react": "*",
- "react-native": "*"
- }
- },
"node_modules/react-native-keychain": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/react-native-keychain/-/react-native-keychain-9.1.0.tgz",
@@ -22334,6 +22315,18 @@
}
}
},
+ "node_modules/react-native-prevent-screenshot-ios-android": {
+ "version": "1.1.0",
+ "resolved": "git+ssh://git@github.com/BlueWallet/react-native-prevent-screenshot-ios-android.git#133004eff4b2e95176ad2a8cc1d6aa61ea43be98",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 16.0.0"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-native": "*"
+ }
+ },
"node_modules/react-native-prompt-android": {
"version": "1.0.0",
"resolved": "git+ssh://git@github.com/BlueWallet/react-native-prompt-android.git#ed168d66fed556bc2ed07cf498770f058b78a376",
@@ -22482,9 +22475,9 @@
"license": "ISC"
},
"node_modules/react-native-share": {
- "version": "11.1.0",
- "resolved": "https://registry.npmjs.org/react-native-share/-/react-native-share-11.1.0.tgz",
- "integrity": "sha512-kcpBR90d5//xc8H84HnX6YFeOk4A34mtHz4UEpb7Twbu049KafJwsp4KVVr/SrJwy8W0/Rbe880En9Hq0REamw==",
+ "version": "12.0.9",
+ "resolved": "https://registry.npmjs.org/react-native-share/-/react-native-share-12.0.9.tgz",
+ "integrity": "sha512-vSuz/9aF+/AZS3I5NC19MrB56h1Yivk2Yz8lf2d8Szv3KuRw2BnDI/AfCTjMWByJLVYr6xgzfkTkAfvbDGzxLQ==",
"license": "MIT",
"engines": {
"node": ">=16"
diff --git a/package.json b/package.json
index 1729dbf8e..88f5f18ad 100644
--- a/package.json
+++ b/package.json
@@ -94,9 +94,10 @@
"@react-native-menu/menu": "https://github.com/BlueWallet/menu.git#038a9c9",
"@react-native/gradle-plugin": "0.76.7",
"@react-native/metro-config": "0.76.7",
- "@react-navigation/drawer": "7.1.1",
- "@react-navigation/native": "7.0.14",
- "@react-navigation/native-stack": "7.2.0",
+ "@react-navigation/devtools": "7.0.16",
+ "@react-navigation/drawer": "7.1.2",
+ "@react-navigation/native": "7.0.15",
+ "@react-navigation/native-stack": "7.2.1",
"@rneui/base": "4.0.0-rc.8",
"@rneui/themed": "4.0.0-rc.8",
"@spsina/bip47": "github:BlueWallet/bip47#df82345",
@@ -144,12 +145,12 @@
"react-native-gesture-handler": "2.23.1",
"react-native-handoff": "github:BlueWallet/react-native-handoff#v0.0.4",
"react-native-haptic-feedback": "2.3.3",
- "react-native-image-picker": "7.2.3",
- "react-native-ios-context-menu": "github:BlueWallet/react-native-ios-context-menu#e5c1217cd220bfab6e6d9a7c65838545082e3f8e",
+ "react-native-image-picker": "8.2.0",
"react-native-keychain": "9.1.0",
"react-native-linear-gradient": "2.8.3",
"react-native-localize": "3.4.1",
"react-native-permissions": "5.2.5",
+ "react-native-prevent-screenshot-ios-android": "github:BlueWallet/react-native-prevent-screenshot-ios-android#133004e",
"react-native-prompt-android": "github:BlueWallet/react-native-prompt-android#ed168d66fed556bc2ed07cf498770f058b78a376",
"react-native-push-notification": "8.1.1",
"react-native-qrcode-svg": "6.3.12",
@@ -157,12 +158,11 @@
"react-native-randombytes": "3.6.1",
"react-native-rate": "1.2.12",
"react-native-reanimated": "3.16.7",
- "@react-navigation/devtools": "7.0.15",
"react-native-safe-area-context": "5.2.0",
"react-native-screen-capture": "github:BlueWallet/react-native-screen-capture#18cb79f",
"react-native-screens": "4.9.1",
"react-native-secure-key-store": "github:BlueWallet/react-native-secure-key-store#2076b4849e88aa0a78e08bfbb4ce3923e0925cbc",
- "react-native-share": "11.1.0",
+ "react-native-share": "12.0.9",
"react-native-svg": "15.11.2",
"react-native-tcp-socket": "6.2.0",
"react-native-vector-icons": "10.2.0",
diff --git a/screen/send/create.js b/screen/send/create.js
index d936be714..f3e22eb66 100644
--- a/screen/send/create.js
+++ b/screen/send/create.js
@@ -9,17 +9,16 @@ import { Icon } from '@rneui/themed';
import RNFS from 'react-native-fs';
import { PERMISSIONS, request, RESULTS } from 'react-native-permissions';
import Share from 'react-native-share';
-
import { satoshiToBTC } from '../../blue_modules/currency';
import { isDesktop } from '../../blue_modules/environment';
import { BlueSpacing20, BlueText } from '../../BlueComponents';
import presentAlert from '../../components/Alert';
import { DynamicQRCode } from '../../components/DynamicQRCode';
import { useTheme } from '../../components/themes';
-import { disallowScreenshot } from 'react-native-screen-capture';
import loc from '../../loc';
import { BitcoinUnit } from '../../models/bitcoinUnits';
import { useSettings } from '../../hooks/context/useSettings';
+import { enableScreenProtect, disableScreenProtect } from '../../helpers/screenProtect';
const SendCreate = () => {
const { fee, recipients, memo = '', satoshiPerByte, psbt, showAnimatedQr, tx } = useRoute().params;
@@ -49,9 +48,11 @@ const SendCreate = () => {
useEffect(() => {
console.log('send/create - useEffect');
- if (!isDesktop) disallowScreenshot(isPrivacyBlurEnabled);
+ if (isPrivacyBlurEnabled) {
+ enableScreenProtect();
+ }
return () => {
- if (!isDesktop) disallowScreenshot(false);
+ disableScreenProtect();
};
}, [isPrivacyBlurEnabled]);
diff --git a/screen/wallets/ExportMultisigCoordinationSetup.tsx b/screen/wallets/ExportMultisigCoordinationSetup.tsx
index 8e3369821..a00f21c68 100644
--- a/screen/wallets/ExportMultisigCoordinationSetup.tsx
+++ b/screen/wallets/ExportMultisigCoordinationSetup.tsx
@@ -7,12 +7,11 @@ import { DynamicQRCode } from '../../components/DynamicQRCode';
import SaveFileButton from '../../components/SaveFileButton';
import { SquareButton } from '../../components/SquareButton';
import { useTheme } from '../../components/themes';
-import { disallowScreenshot } from 'react-native-screen-capture';
import loc from '../../loc';
import { useStorage } from '../../hooks/context/useStorage';
import { ExportMultisigCoordinationSetupStackRootParamList } from '../../navigation/ExportMultisigCoordinationSetupStack';
import { useSettings } from '../../hooks/context/useSettings';
-import { isDesktop } from '../../blue_modules/environment';
+import { enableScreenProtect, disableScreenProtect } from '../../helpers/screenProtect';
const enum ActionType {
SET_LOADING = 'SET_LOADING',
@@ -102,7 +101,6 @@ const ExportMultisigCoordinationSetup: React.FC = () => {
dispatch({ type: ActionType.SET_LOADING, isLoading: true });
const task = InteractionManager.runAfterInteractions(() => {
- if (!isDesktop) disallowScreenshot(isPrivacyBlurEnabled);
if (wallet) {
setTimeout(async () => {
try {
@@ -128,12 +126,22 @@ const ExportMultisigCoordinationSetup: React.FC = () => {
return () => {
task.cancel();
- if (!isDesktop) disallowScreenshot(false);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [walletID]),
);
+ useFocusEffect(
+ useCallback(() => {
+ if (isPrivacyBlurEnabled) {
+ enableScreenProtect();
+ }
+ return () => {
+ disableScreenProtect();
+ };
+ }, [isPrivacyBlurEnabled]),
+ );
+
useFocusEffect(
useCallback(() => {
if (closeButtonState) {
diff --git a/screen/wallets/ImportWallet.tsx b/screen/wallets/ImportWallet.tsx
index 19c2c1778..8450b1a1a 100644
--- a/screen/wallets/ImportWallet.tsx
+++ b/screen/wallets/ImportWallet.tsx
@@ -2,7 +2,6 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { RouteProp, useRoute } from '@react-navigation/native';
import Clipboard from '@react-native-clipboard/clipboard';
import { Keyboard, Platform, ScrollView, StyleSheet, TouchableWithoutFeedback, View, TouchableOpacity, Image } from 'react-native';
-import { disallowScreenshot } from 'react-native-screen-capture';
import { BlueFormLabel, BlueFormMultiInput, BlueSpacing20 } from '../../BlueComponents';
import Button from '../../components/Button';
import {
@@ -18,8 +17,8 @@ import loc from '../../loc';
import { CommonToolTipActions } from '../../typings/CommonToolTipActions';
import { AddWalletStackParamList } from '../../navigation/AddWalletStack';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
-import { isDesktop } from '../../blue_modules/environment';
import { AddressInputScanButton } from '../../components/AddressInputScanButton';
+import { enableScreenProtect, disableScreenProtect } from '../../helpers/screenProtect';
type RouteProps = RouteProp;
type NavigationProps = NativeStackNavigationProp;
@@ -156,9 +155,11 @@ const ImportWallet = () => {
);
useEffect(() => {
- if (!isDesktop) disallowScreenshot(isPrivacyBlurEnabled);
+ if (isPrivacyBlurEnabled) {
+ enableScreenProtect();
+ }
return () => {
- if (!isDesktop) disallowScreenshot(false);
+ disableScreenProtect();
};
}, [isPrivacyBlurEnabled]);
diff --git a/screen/wallets/ImportWalletDiscovery.tsx b/screen/wallets/ImportWalletDiscovery.tsx
index 07ddc72bb..dbedcb0c3 100644
--- a/screen/wallets/ImportWalletDiscovery.tsx
+++ b/screen/wallets/ImportWalletDiscovery.tsx
@@ -16,10 +16,9 @@ import { useStorage } from '../../hooks/context/useStorage';
import { AddWalletStackParamList } from '../../navigation/AddWalletStack';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { THDWalletForWatchOnly, TWallet } from '../../class/wallets/types';
-import { keepAwake, disallowScreenshot } from 'react-native-screen-capture';
import { useSettings } from '../../hooks/context/useSettings';
-import { isDesktop } from '../../blue_modules/environment';
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
+import { enableScreenProtect, disableScreenProtect } from '../../helpers/screenProtect';
type RouteProps = RouteProp;
type NavigationProp = NativeStackNavigationProp;
@@ -39,7 +38,7 @@ const ImportWalletDiscovery: React.FC = () => {
const { colors } = useTheme();
const route = useRoute();
const { importText, askPassphrase, searchAccounts } = route.params;
- const { isElectrumDisabled } = useSettings();
+ const { isElectrumDisabled, isPrivacyBlurEnabled } = useSettings();
const task = useRef(null);
const { addAndSaveWallet } = useStorage();
const [loading, setLoading] = useState(true);
@@ -115,7 +114,6 @@ const ImportWalletDiscovery: React.FC = () => {
}
};
- if (!isDesktop) keepAwake(true);
task.current = startImport(importText, askPassphrase, searchAccounts, isElectrumDisabled, onProgress, onWallet, onPassword);
task.current.promise
@@ -134,22 +132,24 @@ const ImportWalletDiscovery: React.FC = () => {
.finally(() => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
setLoading(false);
- if (!isDesktop) keepAwake(false);
});
return () => {
- if (!isDesktop) keepAwake(false);
task.current?.stop();
};
}, [askPassphrase, importText, isElectrumDisabled, navigation, saveWallet, searchAccounts]);
+ useEffect(() => {
+ if (isPrivacyBlurEnabled) {
+ enableScreenProtect();
+ }
+ return () => {
+ disableScreenProtect();
+ };
+ }, [isPrivacyBlurEnabled]);
+
const handleCustomDerivation = () => {
task.current?.stop();
- if (!isDesktop) {
- keepAwake(false);
- disallowScreenshot(false);
- }
-
navigation.navigate('ImportCustomDerivationPath', { importText, password });
};
diff --git a/screen/wallets/PleaseBackup.tsx b/screen/wallets/PleaseBackup.tsx
index 73a930a5d..0dbf74b55 100644
--- a/screen/wallets/PleaseBackup.tsx
+++ b/screen/wallets/PleaseBackup.tsx
@@ -1,17 +1,16 @@
-import { RouteProp, useNavigation, useRoute } from '@react-navigation/native';
+import { RouteProp, useFocusEffect, useNavigation, useRoute } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import React, { useCallback, useEffect } from 'react';
import { BackHandler, I18nManager, ScrollView, StyleSheet, Text, View } from 'react-native';
-import { disallowScreenshot } from 'react-native-screen-capture';
import Button from '../../components/Button';
import { useTheme } from '../../components/themes';
import { useSettings } from '../../hooks/context/useSettings';
import { useStorage } from '../../hooks/context/useStorage';
import loc from '../../loc';
import { AddWalletStackParamList } from '../../navigation/AddWalletStack';
-import { isDesktop } from '../../blue_modules/environment';
import SeedWords from '../../components/SeedWords';
+import { disableScreenProtect, enableScreenProtect } from '../../helpers/screenProtect';
type RouteProps = RouteProp;
type NavigationProp = NativeStackNavigationProp;
@@ -34,21 +33,27 @@ const PleaseBackup: React.FC = () => {
});
const handleBackButton = useCallback(() => {
- // @ts-ignore: Ignore
navigation.getParent()?.goBack();
return true;
}, [navigation]);
useEffect(() => {
BackHandler.addEventListener('hardwareBackPress', handleBackButton);
- if (!isDesktop) disallowScreenshot(isPrivacyBlurEnabled);
return () => {
BackHandler.removeEventListener('hardwareBackPress', handleBackButton);
- if (!isDesktop) disallowScreenshot(false);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
+ useFocusEffect(
+ useCallback(() => {
+ if (isPrivacyBlurEnabled) enableScreenProtect();
+ return () => {
+ disableScreenProtect();
+ };
+ }, [isPrivacyBlurEnabled]),
+ );
+
return (
{
// useFocusEffect is called on willAppear (example: when camera dismisses). we want to avoid this.
if (hasLoaded.current) return;
setIsLoading(true);
- if (!isDesktop) disallowScreenshot(isPrivacyBlurEnabled);
+ if (isPrivacyBlurEnabled) enableScreenProtect();
const task = InteractionManager.runAfterInteractions(async () => {
if (!w.current) {
@@ -207,7 +207,7 @@ const ViewEditMultisigCosigners: React.FC = () => {
setIsLoading(false);
});
return () => {
- if (!isDesktop) disallowScreenshot(false);
+ disableScreenProtect();
task.cancel();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
diff --git a/screen/wallets/WalletAddresses.tsx b/screen/wallets/WalletAddresses.tsx
index 4a1850369..c43ec430e 100644
--- a/screen/wallets/WalletAddresses.tsx
+++ b/screen/wallets/WalletAddresses.tsx
@@ -1,10 +1,9 @@
import React, { useCallback, useEffect, useLayoutEffect, useRef, useReducer, useMemo } from 'react';
-import { useFocusEffect, useRoute, RouteProp } from '@react-navigation/native';
+import { useRoute, RouteProp, useFocusEffect } from '@react-navigation/native';
import { ActivityIndicator, FlatList, StyleSheet, View, Platform, UIManager } from 'react-native';
import { WatchOnlyWallet } from '../../class';
import { AddressItem } from '../../components/addresses/AddressItem';
import { useTheme } from '../../components/themes';
-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';
@@ -12,7 +11,8 @@ import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
import SegmentedControl from '../../components/SegmentControl';
import loc from '../../loc';
import { BitcoinUnit } from '../../models/bitcoinUnits';
-import { isDesktop } from '../../blue_modules/environment';
+import { useSettings } from '../../hooks/context/useSettings';
+import { disableScreenProtect, enableScreenProtect } from '../../helpers/screenProtect';
export const TABS = {
EXTERNAL: 'receive',
@@ -131,6 +131,7 @@ const WalletAddresses: React.FC = () => {
const allowSignVerifyMessage = (wallet && 'allowSignVerifyMessage' in wallet && wallet.allowSignVerifyMessage()) ?? false;
const { colors } = useTheme();
+ const { isPrivacyBlurEnabled } = useSettings();
const { setOptions } = useExtendedNavigation();
const stylesHook = StyleSheet.create({
@@ -139,6 +140,15 @@ const WalletAddresses: React.FC = () => {
},
});
+ useFocusEffect(
+ useCallback(() => {
+ if (isPrivacyBlurEnabled) enableScreenProtect();
+ return () => {
+ disableScreenProtect();
+ };
+ }, [isPrivacyBlurEnabled]),
+ );
+
const getAddresses = useMemo(() => {
if (!walletInstance) return [];
const newAddresses: Address[] = [];
@@ -177,15 +187,6 @@ const WalletAddresses: React.FC = () => {
});
}, [setOptions]);
- useFocusEffect(
- useCallback(() => {
- if (!isDesktop) disallowScreenshot(true);
- return () => {
- if (!isDesktop) disallowScreenshot(false);
- };
- }, []),
- );
-
const data =
search.length > 0 ? filteredAddresses.filter(item => item.address.toLowerCase().includes(search.toLowerCase())) : filteredAddresses;
diff --git a/screen/wallets/WalletExport.tsx b/screen/wallets/WalletExport.tsx
index 374ecc909..b6ff96eeb 100644
--- a/screen/wallets/WalletExport.tsx
+++ b/screen/wallets/WalletExport.tsx
@@ -1,10 +1,9 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import Clipboard from '@react-native-clipboard/clipboard';
-import { RouteProp, useFocusEffect, useNavigation, useRoute } from '@react-navigation/native';
+import { RouteProp, useNavigation, useRoute } from '@react-navigation/native';
import { Icon } from '@rneui/themed';
-import { ActivityIndicator, InteractionManager, LayoutChangeEvent, ScrollView, StyleSheet, TouchableOpacity, View } from 'react-native';
-import { disallowScreenshot } from 'react-native-screen-capture';
-
+import { LayoutChangeEvent, ScrollView, StyleSheet, TouchableOpacity, View } from 'react-native';
+import { enableScreenProtect, disableScreenProtect } from '../../helpers/screenProtect';
import { validateMnemonic } from '../../blue_modules/bip39';
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
import { BlueText } from '../../BlueComponents';
@@ -15,7 +14,6 @@ import SeedWords from '../../components/SeedWords';
import { useTheme } from '../../components/themes';
import { HandOffActivityType } from '../../components/types';
import { useSettings } from '../../hooks/context/useSettings';
-import { isDesktop } from '../../blue_modules/environment';
import { useStorage } from '../../hooks/context/useStorage';
import useAppState from '../../hooks/useAppState';
import loc from '../../loc';
@@ -57,9 +55,8 @@ const DoNotDisclose: React.FC = () => {
};
const WalletExport: React.FC = () => {
- const { wallets, saveToDisk } = useStorage();
+ const { wallets } = useStorage();
const { walletID } = useRoute().params;
- const [isLoading, setIsLoading] = useState(true);
const navigation = useNavigation();
const { isPrivacyBlurEnabled } = useSettings();
const { colors } = useTheme();
@@ -85,28 +82,20 @@ const WalletExport: React.FC = () => {
}, [wallet]);
useEffect(() => {
- if (!isLoading && previousAppState === 'active' && currentAppState !== 'active') {
+ if (previousAppState === 'active' && currentAppState !== 'active') {
const timer = setTimeout(() => navigation.goBack(), 500);
return () => clearTimeout(timer);
}
- }, [currentAppState, previousAppState, navigation, isLoading]);
+ }, [currentAppState, previousAppState, navigation]);
- useFocusEffect(
- useCallback(() => {
- if (!isDesktop) disallowScreenshot(isPrivacyBlurEnabled);
- const task = InteractionManager.runAfterInteractions(async () => {
- if (!wallet.getUserHasSavedExport()) {
- wallet.setUserHasSavedExport(true);
- saveToDisk();
- }
- setIsLoading(false);
- });
- return () => {
- if (!isDesktop) disallowScreenshot(false);
- task.cancel();
- };
- }, [isPrivacyBlurEnabled, wallet, saveToDisk]),
- );
+ useEffect(() => {
+ if (isPrivacyBlurEnabled) {
+ enableScreenProtect();
+ }
+ return () => {
+ disableScreenProtect();
+ };
+ }, [isPrivacyBlurEnabled]);
const onLayout = useCallback((e: LayoutChangeEvent) => {
const { height, width } = e.nativeEvent.layout;
@@ -128,22 +117,13 @@ const WalletExport: React.FC = () => {
contentContainerStyle={styles.scrollViewContent}
onLayout={onLayout}
testID="WalletExportScroll"
- centerContent={isLoading}
>
{children}
),
- [isLoading, onLayout, stylesHook.root],
+ [onLayout, stylesHook.root],
);
- if (isLoading) {
- return (
-
-
-
- );
- }
-
// for SLIP39
if (secrets.length !== 1) {
return (
@@ -177,7 +157,6 @@ const WalletExport: React.FC = () => {
contentContainerStyle={styles.scrollViewContent}
onLayout={onLayout}
testID="WalletExportScroll"
- centerContent={isLoading}
>
{wallet.type !== WatchOnlyWallet.type && }
diff --git a/screen/wallets/WalletTransactions.tsx b/screen/wallets/WalletTransactions.tsx
index 9f2840443..d2f26d22b 100644
--- a/screen/wallets/WalletTransactions.tsx
+++ b/screen/wallets/WalletTransactions.tsx
@@ -290,10 +290,10 @@ const WalletTransactions: React.FC = ({ route }) => {
const renderItem = useCallback(
// eslint-disable-next-line react/no-unused-prop-types
- ({ item }: { item: Transaction }) => {
- return ;
- },
- [wallet, walletID],
+ ({ item }: { item: Transaction }) => (
+
+ ),
+ [wallet?.preferredBalanceUnit, walletID],
);
const choosePhoto = () => {
@@ -310,7 +310,7 @@ const WalletTransactions: React.FC = ({ route }) => {
});
};
- const _keyExtractor = (_item: any, index: number) => index.toString();
+ const _keyExtractor = useCallback((_item: any, index: number) => index.toString(), []);
const pasteFromClipboard = async () => {
onBarCodeRead({ data: await getClipboardContent() });
@@ -391,6 +391,7 @@ const WalletTransactions: React.FC = ({ route }) => {
});
return () => {
task.cancel();
+ console.debug('Next screen is focused, clearing reloadTransactionsMenuActionFunction');
setReloadTransactionsMenuActionFunction(() => {});
};
}, [setReloadTransactionsMenuActionFunction, refreshTransactions]),
@@ -519,7 +520,7 @@ const WalletTransactions: React.FC = ({ route }) => {
getItemLayout={getItemLayout}
- updateCellsBatchingPeriod={30}
+ updateCellsBatchingPeriod={50}
onEndReachedThreshold={0.3}
onEndReached={loadMoreTransactions}
ListFooterComponent={renderListFooterComponent}
@@ -531,7 +532,7 @@ const WalletTransactions: React.FC = ({ route }) => {
removeClippedSubviews
contentContainerStyle={{ backgroundColor: colors.background }}
contentInset={{ top: 0, left: 0, bottom: 90, right: 0 }}
- maxToRenderPerBatch={15}
+ maxToRenderPerBatch={10}
onScroll={handleScroll}
scrollEventThrottle={16}
stickyHeaderHiddenOnScroll
@@ -549,6 +550,10 @@ const WalletTransactions: React.FC = ({ route }) => {
refreshTransactions(true)} tintColor={colors.msSuccessCheck} />
) : undefined
}
+ windowSize={15}
+ maintainVisibleContentPosition={{
+ minIndexForVisible: 0,
+ }}
/>
{wallet?.allowReceive() && (
diff --git a/screen/wallets/WalletsList.tsx b/screen/wallets/WalletsList.tsx
index 69c1f795f..23b7c37c0 100644
--- a/screen/wallets/WalletsList.tsx
+++ b/screen/wallets/WalletsList.tsx
@@ -260,7 +260,7 @@ const WalletsList: React.FC = () => {
const renderTransactionListsRow = useCallback(
(item: ExtendedTransaction) => (
-
+
),
[],
@@ -358,9 +358,9 @@ const WalletsList: React.FC = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [scanImage, wallets.length]);
- const sectionListKeyExtractor = (item: any, index: any) => {
+ const sectionListKeyExtractor = useCallback((item: any, index: any) => {
return `${item}${index}}`;
- };
+ }, []);
const onScanButtonPressed = useCallback(() => {
navigation.navigate('ScanQRCode', {
@@ -421,6 +421,15 @@ const WalletsList: React.FC = () => {
{ key: WalletsListSections.TRANSACTIONS, data: dataSource },
];
+ const getItemLayout = useCallback(
+ (data: any, index: number) => ({
+ length: 80, // Approximate height of each item
+ offset: 80 * index,
+ index,
+ }),
+ [],
+ );
+
return (
@@ -439,6 +448,7 @@ const WalletsList: React.FC = () => {
windowSize={21}
maxToRenderPerBatch={10}
updateCellsBatchingPeriod={50}
+ getItemLayout={getItemLayout}
/>
{renderScanButton()}
diff --git a/screen/wallets/addMultisigStep2.js b/screen/wallets/addMultisigStep2.js
index 698192fa1..d5453f435 100644
--- a/screen/wallets/addMultisigStep2.js
+++ b/screen/wallets/addMultisigStep2.js
@@ -26,14 +26,12 @@ import QRCodeComponent from '../../components/QRCodeComponent';
import { useTheme } from '../../components/themes';
import confirm from '../../helpers/confirm';
import prompt from '../../helpers/prompt';
-import { disallowScreenshot } from 'react-native-screen-capture';
import loc from '../../loc';
import { useStorage } from '../../hooks/context/useStorage';
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
import ToolTipMenu from '../../components/TooltipMenu';
import { CommonToolTipActions } from '../../typings/CommonToolTipActions';
import { useSettings } from '../../hooks/context/useSettings';
-import { isDesktop } from '../../blue_modules/environment';
import { useKeyboard } from '../../hooks/useKeyboard';
import {
DoneAndDismissKeyboardInputAccessory,
@@ -45,6 +43,7 @@ import MultipleStepsListItem, {
MultipleStepsListItemDashType,
} from '../../components/MultipleStepsListItem';
import { AddressInputScanButton } from '../../components/AddressInputScanButton';
+import { enableScreenProtect, disableScreenProtect } from '../../helpers/screenProtect';
const staticCache = {};
@@ -72,9 +71,11 @@ const WalletsAddMultisigStep2 = () => {
useFocusEffect(
useCallback(() => {
- if (!isDesktop) disallowScreenshot(isPrivacyBlurEnabled);
+ if (isPrivacyBlurEnabled) {
+ enableScreenProtect();
+ }
return () => {
- if (!isDesktop) disallowScreenshot(false);
+ disableScreenProtect();
};
}, [isPrivacyBlurEnabled]),
);
diff --git a/screen/wallets/pleaseBackupLNDHub.js b/screen/wallets/pleaseBackupLNDHub.js
index 54c6b8bec..8f23e398b 100644
--- a/screen/wallets/pleaseBackupLNDHub.js
+++ b/screen/wallets/pleaseBackupLNDHub.js
@@ -7,11 +7,10 @@ import CopyTextToClipboard from '../../components/CopyTextToClipboard';
import QRCodeComponent from '../../components/QRCodeComponent';
import SafeArea from '../../components/SafeArea';
import { useTheme } from '../../components/themes';
-import { disallowScreenshot } from 'react-native-screen-capture';
import loc from '../../loc';
import { useStorage } from '../../hooks/context/useStorage';
import { useSettings } from '../../hooks/context/useSettings';
-import { isDesktop } from '../../blue_modules/environment';
+import { enableScreenProtect, disableScreenProtect } from '../../helpers/screenProtect';
const PleaseBackupLNDHub = () => {
const { wallets } = useStorage();
@@ -44,10 +43,12 @@ const PleaseBackupLNDHub = () => {
});
useEffect(() => {
- if (!isDesktop) disallowScreenshot(isPrivacyBlurEnabled);
+ if (isPrivacyBlurEnabled) {
+ enableScreenProtect();
+ }
BackHandler.addEventListener('hardwareBackPress', handleBackButton);
return () => {
- if (!isDesktop) disallowScreenshot(false);
+ disableScreenProtect();
BackHandler.removeEventListener('hardwareBackPress', handleBackButton);
};
}, [handleBackButton, isPrivacyBlurEnabled]);
diff --git a/screen/wallets/xpub.tsx b/screen/wallets/xpub.tsx
index eac6a1f55..004211d0d 100644
--- a/screen/wallets/xpub.tsx
+++ b/screen/wallets/xpub.tsx
@@ -8,13 +8,12 @@ import CopyTextToClipboard from '../../components/CopyTextToClipboard';
import HandOffComponent from '../../components/HandOffComponent';
import QRCodeComponent from '../../components/QRCodeComponent';
import SafeArea from '../../components/SafeArea';
-import { disallowScreenshot } from 'react-native-screen-capture';
+import { enableScreenProtect, disableScreenProtect } from '../../helpers/screenProtect';
import loc from '../../loc';
import { styles, useDynamicStyles } from './xpub.styles';
import { useStorage } from '../../hooks/context/useStorage';
import { HandOffActivityType } from '../../components/types';
import { useSettings } from '../../hooks/context/useSettings';
-import { isDesktop } from '../../blue_modules/environment';
type WalletXpubRouteProp = RouteProp<{ params: { walletID: string; xpub: string } }, 'params'>;
export type RootStackParamList = {
@@ -39,7 +38,7 @@ const WalletXpub: React.FC = () => {
useFocusEffect(
useCallback(() => {
- if (!isDesktop) disallowScreenshot(isPrivacyBlurEnabled);
+ if (isPrivacyBlurEnabled) enableScreenProtect();
// Skip execution if walletID hasn't changed
if (lastWalletIdRef.current === walletID) {
return;
@@ -58,7 +57,7 @@ const WalletXpub: React.FC = () => {
});
lastWalletIdRef.current = walletID;
return () => {
- if (!isDesktop) disallowScreenshot(false);
+ disableScreenProtect();
task.cancel();
};
}, [isPrivacyBlurEnabled, walletID, wallet, xpub, navigation]),
diff --git a/tests/setup.js b/tests/setup.js
index 16ba6217e..b41ce83a6 100644
--- a/tests/setup.js
+++ b/tests/setup.js
@@ -229,10 +229,6 @@ jest.mock('realm', () => {
};
});
-jest.mock('react-native-ios-context-menu', () => {
- return {};
-});
-
jest.mock('rn-qr-generator', () => ({
detect: jest.fn(uri => {
if (uri === 'invalid-image') {