Merge branch 'master' into virw

This commit is contained in:
Marcos Rodriguez Velez 2025-03-03 14:00:14 -04:00
commit d7743a740f
30 changed files with 556 additions and 392 deletions

View file

@ -22,6 +22,7 @@ jobs:
branch_name: ${{ steps.get_latest_commit_details.outputs.branch_name }}
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
MATCH_READONLY: "true"
steps:
- name: Checkout Project
@ -29,6 +30,19 @@ jobs:
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'
run: |
@ -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,38 +270,35 @@ 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
- 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({

View file

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

View file

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

View file

@ -16,7 +16,6 @@
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<application
android:name=".MainApplication"

View file

@ -12,7 +12,6 @@ class BitcoinPriceWidget : AppWidgetProvider() {
companion object {
private const val TAG = "BitcoinPriceWidget"
private const val SHARED_PREF_NAME = "group.io.bluewallet.bluewallet"
private const val WIDGET_COUNT_KEY = "widget_count"
}
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
@ -25,21 +24,12 @@ class BitcoinPriceWidget : AppWidgetProvider() {
override fun onEnabled(context: Context) {
super.onEnabled(context)
val sharedPref = context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)
val widgetCount = sharedPref.getInt(WIDGET_COUNT_KEY, 0)
if (widgetCount >= 1) {
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()

View file

@ -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 (
<ContextMenuView
lazyPreview
accessibilityLabel={props.accessibilityLabel}
accessibilityHint={props.accessibilityHint}
accessibilityRole={props.accessibilityRole}
accessibilityState={props.accessibilityState}
accessibilityLanguage={language}
shouldEnableAggressiveCleanup
internalCleanupMode="automatic"
onPressMenuItem={handlePressMenuItemForContextMenuView}
onMenuWillShow={onMenuWillShow}
onMenuWillHide={onMenuWillHide}
useActionSheetFallback={false}
menuConfig={{
menuTitle: title,
menuItems: contextMenuItems,
}}
{...(renderPreview
? {
previewConfig: {
previewType: 'CUSTOM',
backgroundColor: 'white',
},
renderPreview: renderPreview as RenderItem,
}
: {})}
>
{onPress ? (
<Pressable accessibilityRole="button" onPress={onPress} {...restProps}>
{children}
</Pressable>
) : (
children
)}
</ContextMenuView>
);
};
const renderMenuView = () => {
return (
<MenuView
@ -198,7 +130,7 @@ const ToolTipMenu = (props: ToolTipMenuProps) => {
);
};
return props.actions.length > 0 ? (Platform.OS === 'ios' && renderPreview ? renderContextMenuView() : renderMenuView()) : null;
return props.actions.length > 0 ? renderMenuView() : null;
};
export default ToolTipMenu;

View file

@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useRef, useState, memo } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
import Clipboard from '@react-native-clipboard/clipboard';
import { Linking, View, ViewStyle } from 'react-native';
@ -36,7 +36,7 @@ interface TransactionListItemProps {
type NavigationProps = NativeStackNavigationProp<DetailViewStackParamList>;
export const TransactionListItem: React.FC<TransactionListItemProps> = React.memo(
export const TransactionListItem: React.FC<TransactionListItemProps> = memo(
({ item, itemPriceUnit = BitcoinUnit.BTC, walletID, searchQuery, style, renderHighlightedText }) => {
const [subtitleNumberOfLines, setSubtitleNumberOfLines] = useState(1);
const { colors } = useTheme();
@ -367,4 +367,12 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
</ToolTipMenu>
);
},
(prevProps, nextProps) => {
return (
prevProps.item.hash === nextProps.item.hash &&
prevProps.item.received === nextProps.item.received &&
prevProps.itemPriceUnit === nextProps.itemPriceUnit &&
prevProps.walletID === nextProps.walletID
);
},
);

View file

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

View file

@ -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,18 +272,21 @@ platform :ios do
desc "Synchronize certificates and provisioning profiles"
lane :setup_provisioning_profiles do
UI.message("Setting up provisioning profiles...")
required_vars = ["GIT_ACCESS_TOKEN", "GIT_URL", "ITC_TEAM_ID", "ITC_TEAM_NAME", "KEYCHAIN_PASSWORD"]
ensure_env_vars(required_vars)
platform = "ios"
UI.message("Setting up provisioning profiles...")
# Iterate over app identifiers to fetch provisioning profiles
app_identifiers.each do |app_identifier|
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, # Skip if the branch already exists
platform: platform,
clone_branch_directly: true,
platform: "ios",
app_identifier: app_identifier,
team_id: ENV["ITC_TEAM_ID"],
team_name: ENV["ITC_TEAM_NAME"],
@ -257,9 +294,13 @@ platform :ios do
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"
lane :fetch_dev_profiles_catalyst do
match(
@ -402,7 +443,7 @@ lane :upload_bugsnag_sourcemaps do
end
desc "Build the iOS app"
lane :build_app_lane do
lane :build_app_lane do
Dir.chdir(project_root) do
UI.message("Building the application from: #{Dir.pwd}")
@ -411,39 +452,105 @@ lane :build_app_lane do
clear_derived_data_lane
# Determine which iOS version to use
ios_version = determine_ios_version
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",
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",
output_directory: ipa_directory,
output_name: ipa_name,
buildlog_path: File.join(project_root, "ios", "build_logs"),
silent: false,
clean: true
)
rescue => e
UI.user_error!("build_ios_app failed: #{e.message}")
end
# Use File.join to construct paths without extra slashes
ipa_path = lane_context[SharedValues::IPA_OUTPUT_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}")
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.")
# 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
end
# Set both environment variable and GitHub Actions output
ENV['IPA_OUTPUT_PATH'] = ipa_path
# 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
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

View file

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

View file

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

23
helpers/screenProtect.ts Normal file
View file

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

View file

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

View file

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

115
package-lock.json generated
View file

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

View file

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

View file

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

View file

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

View file

@ -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<AddWalletStackParamList, 'ImportWallet'>;
type NavigationProps = NativeStackNavigationProp<AddWalletStackParamList, 'ImportWallet'>;
@ -156,9 +155,11 @@ const ImportWallet = () => {
);
useEffect(() => {
if (!isDesktop) disallowScreenshot(isPrivacyBlurEnabled);
if (isPrivacyBlurEnabled) {
enableScreenProtect();
}
return () => {
if (!isDesktop) disallowScreenshot(false);
disableScreenProtect();
};
}, [isPrivacyBlurEnabled]);

View file

@ -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<AddWalletStackParamList, 'ImportWalletDiscovery'>;
type NavigationProp = NativeStackNavigationProp<AddWalletStackParamList, 'ImportWalletDiscovery'>;
@ -39,7 +38,7 @@ const ImportWalletDiscovery: React.FC = () => {
const { colors } = useTheme();
const route = useRoute<RouteProps>();
const { importText, askPassphrase, searchAccounts } = route.params;
const { isElectrumDisabled } = useSettings();
const { isElectrumDisabled, isPrivacyBlurEnabled } = useSettings();
const task = useRef<TImport | null>(null);
const { addAndSaveWallet } = useStorage();
const [loading, setLoading] = useState<boolean>(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 });
};

View file

@ -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<AddWalletStackParamList, 'PleaseBackup'>;
type NavigationProp = NativeStackNavigationProp<AddWalletStackParamList, 'PleaseBackup'>;
@ -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 (
<ScrollView
style={styles.root}

View file

@ -41,7 +41,7 @@ import { useTheme } from '../../components/themes';
import prompt from '../../helpers/prompt';
import { unlockWithBiometrics, useBiometrics } from '../../hooks/useBiometrics';
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
import { disallowScreenshot } from 'react-native-screen-capture';
import { enableScreenProtect, disableScreenProtect } from '../../helpers/screenProtect';
import loc from '../../loc';
import ActionSheet from '../ActionSheet';
import { useStorage } from '../../hooks/context/useStorage';
@ -191,7 +191,7 @@ const ViewEditMultisigCosigners: React.FC = () => {
// 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

View file

@ -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<NavigationProps>();
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;

View file

@ -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<RouteProps>().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();
useEffect(() => {
if (isPrivacyBlurEnabled) {
enableScreenProtect();
}
setIsLoading(false);
});
return () => {
if (!isDesktop) disallowScreenshot(false);
task.cancel();
disableScreenProtect();
};
}, [isPrivacyBlurEnabled, wallet, saveToDisk]),
);
}, [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}
</ScrollView>
),
[isLoading, onLayout, stylesHook.root],
[onLayout, stylesHook.root],
);
if (isLoading) {
return (
<Scroll>
<ActivityIndicator />
</Scroll>
);
}
// 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 && <DoNotDisclose />}

View file

@ -290,10 +290,10 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }) => {
const renderItem = useCallback(
// eslint-disable-next-line react/no-unused-prop-types
({ item }: { item: Transaction }) => {
return <TransactionListItem item={item} itemPriceUnit={wallet?.preferredBalanceUnit} walletID={walletID} />;
},
[wallet, walletID],
({ item }: { item: Transaction }) => (
<TransactionListItem key={item.hash} item={item} itemPriceUnit={wallet?.preferredBalanceUnit} walletID={walletID} />
),
[wallet?.preferredBalanceUnit, walletID],
);
const choosePhoto = () => {
@ -310,7 +310,7 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ 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<WalletTransactionsProps> = ({ route }) => {
});
return () => {
task.cancel();
console.debug('Next screen is focused, clearing reloadTransactionsMenuActionFunction');
setReloadTransactionsMenuActionFunction(() => {});
};
}, [setReloadTransactionsMenuActionFunction, refreshTransactions]),
@ -519,7 +520,7 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }) => {
<FlatList<Transaction>
getItemLayout={getItemLayout}
updateCellsBatchingPeriod={30}
updateCellsBatchingPeriod={50}
onEndReachedThreshold={0.3}
onEndReached={loadMoreTransactions}
ListFooterComponent={renderListFooterComponent}
@ -531,7 +532,7 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ 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<WalletTransactionsProps> = ({ route }) => {
<RefreshControl refreshing={isLoading} onRefresh={() => refreshTransactions(true)} tintColor={colors.msSuccessCheck} />
) : undefined
}
windowSize={15}
maintainVisibleContentPosition={{
minIndexForVisible: 0,
}}
/>
<FContainer ref={walletActionButtonsRef}>
{wallet?.allowReceive() && (

View file

@ -260,7 +260,7 @@ const WalletsList: React.FC = () => {
const renderTransactionListsRow = useCallback(
(item: ExtendedTransaction) => (
<View style={styles.transaction}>
<TransactionListItem item={item} itemPriceUnit={item.walletPreferredBalanceUnit} walletID={item.walletID} />
<TransactionListItem key={item.hash} item={item} itemPriceUnit={item.walletPreferredBalanceUnit} walletID={item.walletID} />
</View>
),
[],
@ -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 (
<View style={styles.root}>
<View style={[styles.walletsListWrapper, stylesHook.walletsListWrapper]}>
@ -439,6 +448,7 @@ const WalletsList: React.FC = () => {
windowSize={21}
maxToRenderPerBatch={10}
updateCellsBatchingPeriod={50}
getItemLayout={getItemLayout}
/>
{renderScanButton()}
</View>

View file

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

View file

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

View file

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

View file

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