From 3fcf3c08407d97a1a4478590d25b58c83b0950c0 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Tue, 25 Feb 2025 22:24:31 -0400 Subject: [PATCH 01/26] FIX: Disable screen protect until it supports nav 7. Fix speed issue exposed by disabling it --- components/TransactionListItem.tsx | 12 ++++- helpers/screenProtect.ts | 23 +++++++++ ios/BlueWallet.xcodeproj/project.pbxproj | 20 ++++---- ios/Podfile.lock | 35 ++++++++++--- package-lock.json | 31 ++++++----- package.json | 4 +- screen/send/create.js | 9 ++-- .../ExportMultisigCoordinationSetup.tsx | 16 ++++-- screen/wallets/ImportWalletDiscovery.tsx | 22 ++++---- screen/wallets/PleaseBackup.tsx | 17 ++++--- screen/wallets/ViewEditMultisigCosigners.tsx | 6 +-- screen/wallets/WalletAddresses.tsx | 25 ++++----- screen/wallets/WalletExport.tsx | 51 ++++++------------- screen/wallets/WalletTransactions.tsx | 19 ++++--- screen/wallets/WalletsList.tsx | 17 +++++-- screen/wallets/addMultisigStep2.js | 9 ++-- screen/wallets/pleaseBackupLNDHub.js | 9 ++-- screen/wallets/xpub.tsx | 7 ++- 18 files changed, 198 insertions(+), 134 deletions(-) create mode 100644 helpers/screenProtect.ts diff --git a/components/TransactionListItem.tsx b/components/TransactionListItem.tsx index 890dd7dc4..e926c8daa 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(); @@ -366,4 +366,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/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 b6c1790a9..6a1cfc089 100644 --- a/ios/BlueWallet.xcodeproj/project.pbxproj +++ b/ios/BlueWallet.xcodeproj/project.pbxproj @@ -1443,7 +1443,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1703137999; + CURRENT_PROJECT_VERSION = 1703138999; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = A7W54YZ4WU; @@ -1506,7 +1506,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1703137999; + CURRENT_PROJECT_VERSION = 1703138999; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = A7W54YZ4WU; @@ -1565,7 +1565,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1703137999; + CURRENT_PROJECT_VERSION = 1703138999; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = ""; @@ -1608,7 +1608,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1703137999; + CURRENT_PROJECT_VERSION = 1703138999; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; @@ -1652,7 +1652,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1703137999; + CURRENT_PROJECT_VERSION = 1703138999; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = ""; @@ -1708,7 +1708,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Distribution"; CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1703137999; + CURRENT_PROJECT_VERSION = 1703138999; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; @@ -1895,7 +1895,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1703137999; + CURRENT_PROJECT_VERSION = 1703138999; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = ""; @@ -1948,7 +1948,7 @@ "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1703137999; + CURRENT_PROJECT_VERSION = 1703138999; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; @@ -2000,7 +2000,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1703137999; + CURRENT_PROJECT_VERSION = 1703138999; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = ""; @@ -2049,7 +2049,7 @@ "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1703137999; + CURRENT_PROJECT_VERSION = 1703138999; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 2f171489f..3232dc5b7 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1322,18 +1322,37 @@ PODS: - 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): - React-Core - - react-native-screen-capture (0.2.3): - - React - react-native-secure-key-store (2.0.10): - React-Core - react-native-tcp-socket (6.2.0): - CocoaAsyncSocket - React-Core - - react-native-true-sheet (1.1.1): + - react-native-true-sheet (2.0.0): - DoubleConversion - glog - hermes-engine @@ -1941,9 +1960,9 @@ DEPENDENCIES: - 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`) - react-native-secure-key-store (from `../node_modules/react-native-secure-key-store`) - react-native-tcp-socket (from `../node_modules/react-native-tcp-socket`) - "react-native-true-sheet (from `../node_modules/@lodev09/react-native-true-sheet`)" @@ -2098,12 +2117,12 @@ EXTERNAL SOURCES: :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: :path: "../node_modules/react-native-safe-area-context" - react-native-screen-capture: - :path: "../node_modules/react-native-screen-capture" react-native-secure-key-store: :path: "../node_modules/react-native-secure-key-store" react-native-tcp-socket: @@ -2261,12 +2280,12 @@ SPEC CHECKSUMS: react-native-image-picker: 130fad649d07e4eec8faaed361d3bba570e1e5ff react-native-ios-context-menu: 986da6dcba70094bcc2a8049f68410fe7d25aff1 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 react-native-secure-key-store: eb45b44bdec3f48e9be5cdfca0f49ddf64892ea6 react-native-tcp-socket: 61379457d7e702e83e28c213b6e085ac079e480f - react-native-true-sheet: 58c0848a4326fd2eb7eedd266ec0f39e7c70e5bd + react-native-true-sheet: 15f8d1bfbf2aceca472b9ba585b4116041d20f34 React-nativeconfig: 67fa7a63ea288cb5b1d0dd2deaf240405fec164f React-NativeModulesApple: 34b7a4d7441a4ee78d18109ff107c1ccf7c074a9 React-perflogger: d1149037ac466ad2141d4ae541ca16cb73b2343b diff --git a/package-lock.json b/package-lock.json index de67b945e..ef0ee7ae1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@bugsnag/react-native": "8.2.0", "@bugsnag/source-maps": "2.3.3", "@keystonehq/bc-ur-registry": "0.7.0", - "@lodev09/react-native-true-sheet": "github:BlueWallet/react-native-true-sheet#0fefdd1aca07fcc3c204f6f1511f9fc0a2c59111", + "@lodev09/react-native-true-sheet": "github:BlueWallet/react-native-true-sheet#5945184a2fea9fe5ba8f5cfcdb20e3fc5eed6e37", "@ngraveio/bc-ur": "1.1.13", "@noble/secp256k1": "1.6.3", "@react-native-async-storage/async-storage": "2.1.0", @@ -83,6 +83,7 @@ "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", @@ -91,7 +92,6 @@ "react-native-rate": "1.2.12", "react-native-reanimated": "3.16.7", "react-native-safe-area-context": "5.2.0", - "react-native-screen-capture": "github:BlueWallet/react-native-screen-capture#18cb79f", "react-native-screens": "4.9.0", "react-native-secure-key-store": "github:BlueWallet/react-native-secure-key-store#2076b4849e88aa0a78e08bfbb4ce3923e0925cbc", "react-native-share": "11.1.0", @@ -4699,9 +4699,9 @@ } }, "node_modules/@lodev09/react-native-true-sheet": { - "version": "1.1.1", - "resolved": "git+ssh://git@github.com/BlueWallet/react-native-true-sheet.git#0fefdd1aca07fcc3c204f6f1511f9fc0a2c59111", - "integrity": "sha512-44sy92Jofy5dmKYpu9CYAHEHnEUnZPvy5bCSCvlg/2W3HlIUY2IKp81VzrFLf7XvDO7ZREqNsTt4QsaOH+GUnQ==", + "version": "2.0.0", + "resolved": "git+ssh://git@github.com/BlueWallet/react-native-true-sheet.git#5945184a2fea9fe5ba8f5cfcdb20e3fc5eed6e37", + "integrity": "sha512-JoMgC3w8Xgzvb4zHRnBdycBDuhz1O8MuKh9LE3QJjCElcm8x0n3asHzrt+XaLs3XfVrvq/LNyIMQSSYtvvhFKA==", "license": "MIT", "workspaces": [ "example", @@ -21973,6 +21973,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", @@ -22091,15 +22103,6 @@ "react-native": "*" } }, - "node_modules/react-native-screen-capture": { - "version": "0.2.3", - "resolved": "git+ssh://git@github.com/BlueWallet/react-native-screen-capture.git#18cb79f0cd3edf189c8fac926ee8cdb8a58b1174", - "license": "MIT", - "peerDependencies": { - "react": ">=17.0.2", - "react-native": ">=0.67.0-rc.0 <1.0.x" - } - }, "node_modules/react-native-screens": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.9.0.tgz", diff --git a/package.json b/package.json index db3e7a6fe..80109154b 100644 --- a/package.json +++ b/package.json @@ -94,6 +94,7 @@ "@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", @@ -150,6 +151,7 @@ "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,9 +159,7 @@ "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.0", "react-native-secure-key-store": "github:BlueWallet/react-native-secure-key-store#2076b4849e88aa0a78e08bfbb4ce3923e0925cbc", "react-native-share": "11.1.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/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) { @@ -197,7 +197,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 758edaa1f..e2fc37eb8 100644 --- a/screen/wallets/WalletTransactions.tsx +++ b/screen/wallets/WalletTransactions.tsx @@ -297,10 +297,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 = () => { @@ -317,7 +317,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() }); @@ -398,6 +398,7 @@ const WalletTransactions: React.FC = ({ route }) => { }); return () => { task.cancel(); + console.debug('Next screen is focused, clearing reloadTransactionsMenuActionFunction'); setReloadTransactionsMenuActionFunction(() => {}); }; }, [setReloadTransactionsMenuActionFunction, refreshTransactions]), @@ -525,7 +526,7 @@ const WalletTransactions: React.FC = ({ route }) => { getItemLayout={getItemLayout} - updateCellsBatchingPeriod={30} + updateCellsBatchingPeriod={50} onEndReachedThreshold={0.3} onEndReached={loadMoreTransactions} ListFooterComponent={renderListFooterComponent} @@ -537,7 +538,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 @@ -555,6 +556,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 5e98b046b..ce69e2fcd 100644 --- a/screen/wallets/WalletsList.tsx +++ b/screen/wallets/WalletsList.tsx @@ -168,6 +168,7 @@ const WalletsList: React.FC = () => { }); return () => { task.cancel(); + console.debug('Next screen is focused, clearing reloadTransactionsMenuActionFunction'); setReloadTransactionsMenuActionFunction(() => {}); }; }, [onRefresh, setReloadTransactionsMenuActionFunction, verifyBalance, setSelectedWalletID]), @@ -259,7 +260,7 @@ const WalletsList: React.FC = () => { const renderTransactionListsRow = useCallback( (item: ExtendedTransaction) => ( - + ), [], @@ -357,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', { @@ -420,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 ( @@ -438,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]), From 37a88fd60d74449b9785e05bd11e661a195b40b1 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Tue, 25 Feb 2025 22:24:33 -0400 Subject: [PATCH 02/26] Update ImportWallet.tsx --- screen/wallets/ImportWallet.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) 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]); From b01aa58e3bbb64710b2883cfa1b99d66f7a75cd4 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Sat, 1 Mar 2025 10:17:56 -0400 Subject: [PATCH 03/26] DEL: ios-context package RNMenu covers 95% of use case --- components/TooltipMenu.tsx | 72 +----------------------- components/addresses/AddressItem.tsx | 1 + ios/BlueWallet.xcodeproj/project.pbxproj | 4 +- ios/Podfile.lock | 6 -- package-lock.json | 20 ------- package.json | 1 - tests/setup.js | 4 -- 7 files changed, 5 insertions(+), 103 deletions(-) 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/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/ios/BlueWallet.xcodeproj/project.pbxproj b/ios/BlueWallet.xcodeproj/project.pbxproj index 4cdd85588..f529c8ec1 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; diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 00efe5a69..353999a26 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1318,8 +1318,6 @@ 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-randombytes (3.6.1): @@ -1939,7 +1937,6 @@ 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-randombytes (from `../node_modules/react-native-randombytes`) - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) @@ -2094,8 +2091,6 @@ 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-randombytes: @@ -2259,7 +2254,6 @@ SPEC CHECKSUMS: react-native-bw-file-access: fe925b77dbf48500df0b294c6851f8c84607a203 react-native-document-picker: 530879d9e89b490f0954bcc4ab697c5b5e35d659 react-native-image-picker: 130fad649d07e4eec8faaed361d3bba570e1e5ff - react-native-ios-context-menu: 986da6dcba70094bcc2a8049f68410fe7d25aff1 react-native-menu: 2cfe0a3b3c610ed331f00d9f0df300db14ba8692 react-native-randombytes: 3c8f3e89d12487fd03a2f966c288d495415fc116 react-native-safe-area-context: 3e33e7c43c8b74dba436a5a32651cb8d7064c740 diff --git a/package-lock.json b/package-lock.json index bf3316759..6a0b46b5c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -78,7 +78,6 @@ "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-keychain": "9.1.0", "react-native-linear-gradient": "2.8.3", "react-native-localize": "3.4.1", @@ -2459,12 +2458,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", @@ -22266,19 +22259,6 @@ "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", diff --git a/package.json b/package.json index 1729dbf8e..9e5389668 100644 --- a/package.json +++ b/package.json @@ -145,7 +145,6 @@ "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-keychain": "9.1.0", "react-native-linear-gradient": "2.8.3", "react-native-localize": "3.4.1", 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') { From 07b93d521d0e719139255650b64ba402f5e3488d Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Sat, 1 Mar 2025 10:38:33 -0400 Subject: [PATCH 04/26] FIX: Android cold open crash --- Gemfile | 2 +- Gemfile.lock | 22 ++++++++++--------- .../bluewallet/BitcoinPriceWidget.kt | 5 ----- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/Gemfile b/Gemfile index 40894e1eb..a6764b8b0 100644 --- a/Gemfile +++ b/Gemfile @@ -5,7 +5,7 @@ ruby "3.1.6" gem 'rubyzip', '2.4.1' gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1' 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' plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') diff --git a/Gemfile.lock b/Gemfile.lock index 4bb4d2132..b5bd5033f 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) @@ -220,24 +220,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) @@ -258,7 +260,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) @@ -308,7 +310,7 @@ DEPENDENCIES activesupport (>= 6.1.7.5, != 7.1.0) cocoapods (>= 1.13, != 1.15.1, != 1.15.0) concurrent-ruby (< 1.3.4) - fastlane (>= 2.225.0) + fastlane (>= 2.226.0) fastlane-plugin-browserstack fastlane-plugin-bugsnag_sourcemaps_upload rubyzip (= 2.4.1) diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/BitcoinPriceWidget.kt b/android/app/src/main/java/io/bluewallet/bluewallet/BitcoinPriceWidget.kt index 6ce5129eb..b55ac67c6 100644 --- a/android/app/src/main/java/io/bluewallet/bluewallet/BitcoinPriceWidget.kt +++ b/android/app/src/main/java/io/bluewallet/bluewallet/BitcoinPriceWidget.kt @@ -27,11 +27,6 @@ class BitcoinPriceWidget : AppWidgetProvider() { 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) From 9507a483148b05c4ca26191836c298f2610c73a7 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Sat, 1 Mar 2025 10:44:15 -0400 Subject: [PATCH 05/26] Update BitcoinPriceWidget.kt --- .../bluewallet/bluewallet/BitcoinPriceWidget.kt | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/BitcoinPriceWidget.kt b/android/app/src/main/java/io/bluewallet/bluewallet/BitcoinPriceWidget.kt index b55ac67c6..d27bc4434 100644 --- a/android/app/src/main/java/io/bluewallet/bluewallet/BitcoinPriceWidget.kt +++ b/android/app/src/main/java/io/bluewallet/bluewallet/BitcoinPriceWidget.kt @@ -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,16 +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) - 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) @@ -42,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() From 7eb420c561813a388988d76820fb3f042936789a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20Rodriguez=20V=C3=A9lez?= Date: Sat, 1 Mar 2025 13:41:18 -0400 Subject: [PATCH 06/26] Update build-ios-release-pullrequest.yml --- .github/workflows/build-ios-release-pullrequest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-ios-release-pullrequest.yml b/.github/workflows/build-ios-release-pullrequest.yml index 77a60b229..fbc02d2f0 100644 --- a/.github/workflows/build-ios-release-pullrequest.yml +++ b/.github/workflows/build-ios-release-pullrequest.yml @@ -84,7 +84,7 @@ jobs: - uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: 16.2.0 + xcode-version: 16.0 - name: Set Up Ruby uses: ruby/setup-ruby@v1 From 10f145d0127538c525176bf1dd0debfa3af17a02 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Sat, 1 Mar 2025 15:21:18 -0400 Subject: [PATCH 07/26] wip --- fastlane/Fastfile | 1 + ios/BlueWallet.xcodeproj/project.pbxproj | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 9f35ee1df..0eb7ddeab 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -418,6 +418,7 @@ lane :build_app_lane do export_method: "app-store", include_bitcode: false, configuration: "Release", + destination: "generic/platform=iOS", skip_profile_detection: false, include_symbols: true, export_team_id: ENV["ITC_TEAM_ID"], diff --git a/ios/BlueWallet.xcodeproj/project.pbxproj b/ios/BlueWallet.xcodeproj/project.pbxproj index f529c8ec1..07ad0ecdd 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 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; }; + C978A716948AB7DEC5B6F677 /* (null) 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 /* BuildFile in Frameworks */, + C978A716948AB7DEC5B6F677 /* (null) 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", From 307e950d15c59b4dd959ecad645feaf14772fc25 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Sat, 1 Mar 2025 15:30:21 -0400 Subject: [PATCH 08/26] OPS: Downgrade fastlane --- Gemfile | 2 +- Gemfile.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Gemfile b/Gemfile index a6764b8b0..d117b37e2 100644 --- a/Gemfile +++ b/Gemfile @@ -5,7 +5,7 @@ ruby "3.1.6" gem 'rubyzip', '2.4.1' gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1' gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0' -gem "fastlane", ">= 2.226.0" +gem "fastlane", "2.225.0" gem 'xcodeproj', '< 1.26.0' gem 'concurrent-ruby', '< 1.3.4' plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') diff --git a/Gemfile.lock b/Gemfile.lock index b5bd5033f..5fb20ce94 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -129,7 +129,7 @@ GEM faraday_middleware (1.2.1) faraday (~> 1.0) fastimage (2.4.0) - fastlane (2.226.0) + fastlane (2.225.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -169,7 +169,7 @@ GEM tty-spinner (>= 0.8.0, < 1.0.0) word_wrap (~> 1.0.0) xcodeproj (>= 1.13.0, < 2.0.0) - xcpretty (~> 0.4.0) + xcpretty (~> 0.3.0) xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) fastlane-plugin-browserstack (0.3.3) rest-client (~> 2.0, >= 2.0.2) @@ -261,7 +261,7 @@ GEM netrc (~> 0.8) retriable (3.1.2) rexml (3.4.1) - rouge (3.28.0) + rouge (2.0.7) ruby-macho (2.5.1) ruby2_keywords (0.0.5) rubyzip (2.4.1) @@ -298,8 +298,8 @@ GEM colored2 (~> 3.1) nanaimo (~> 0.3.0) rexml (>= 3.3.6, < 4.0) - xcpretty (0.4.0) - rouge (~> 3.28.0) + xcpretty (0.3.0) + rouge (~> 2.0.7) xcpretty-travis-formatter (1.0.1) xcpretty (~> 0.2, >= 0.0.7) @@ -310,7 +310,7 @@ DEPENDENCIES activesupport (>= 6.1.7.5, != 7.1.0) cocoapods (>= 1.13, != 1.15.1, != 1.15.0) concurrent-ruby (< 1.3.4) - fastlane (>= 2.226.0) + fastlane (= 2.225.0) fastlane-plugin-browserstack fastlane-plugin-bugsnag_sourcemaps_upload rubyzip (= 2.4.1) From d14b4265f878b5974dd6ddbe5242b409f917752d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20Rodriguez=20V=C3=A9lez?= Date: Sat, 1 Mar 2025 15:41:07 -0400 Subject: [PATCH 09/26] Update build-ios-release-pullrequest.yml --- .github/workflows/build-ios-release-pullrequest.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-ios-release-pullrequest.yml b/.github/workflows/build-ios-release-pullrequest.yml index fbc02d2f0..de4eccea9 100644 --- a/.github/workflows/build-ios-release-pullrequest.yml +++ b/.github/workflows/build-ios-release-pullrequest.yml @@ -12,7 +12,7 @@ on: jobs: build: - runs-on: macos-15 + runs-on: macos-latest timeout-minutes: 180 outputs: new_build_number: ${{ steps.generate_build_number.outputs.build_number }} @@ -84,7 +84,7 @@ jobs: - uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: 16.0 + xcode-version: latest-stable - name: Set Up Ruby uses: ruby/setup-ruby@v1 From 4c0fd895308d0670c0b36c455bbfa57673259766 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20Rodriguez=20V=C3=A9lez?= Date: Sat, 1 Mar 2025 19:57:55 -0400 Subject: [PATCH 10/26] Update Fastfile --- fastlane/Fastfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 0eb7ddeab..9b236b387 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -402,7 +402,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}") @@ -418,7 +418,6 @@ lane :build_app_lane do export_method: "app-store", include_bitcode: false, configuration: "Release", - destination: "generic/platform=iOS", skip_profile_detection: false, include_symbols: true, export_team_id: ENV["ITC_TEAM_ID"], From 1946fa0ddee1bad3142c06ea0171b5dabba74576 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Sat, 1 Mar 2025 23:51:54 -0400 Subject: [PATCH 11/26] OPS: Refactor iOS pipeline --- .../build-ios-release-pullrequest.yml | 59 ++- fastlane/Fastfile | 476 +++++++++++------- fastlane/Matchfile | 53 +- 3 files changed, 366 insertions(+), 222 deletions(-) diff --git a/.github/workflows/build-ios-release-pullrequest.yml b/.github/workflows/build-ios-release-pullrequest.yml index de4eccea9..104e97d8e 100644 --- a/.github/workflows/build-ios-release-pullrequest.yml +++ b/.github/workflows/build-ios-release-pullrequest.yml @@ -22,12 +22,28 @@ jobs: branch_name: ${{ steps.get_latest_commit_details.outputs.branch_name }} env: APPLE_ID: ${{ secrets.APPLE_ID }} + # Set Match to read-only for builds, only write new profiles manually + MATCH_READONLY: "true" steps: - name: Checkout Project uses: actions/checkout@v4 with: fetch-depth: 0 # Ensures the full Git history is available + + # Setup caching to speed up builds + - 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,6 +97,7 @@ jobs: uses: actions/setup-node@v4 with: node-version: 20 + cache: 'npm' - uses: maxim-lobanov/setup-xcode@v1 with: @@ -90,6 +107,7 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: 3.1.6 + bundler-cache: true # This caches gems installed via bundler - name: Install Dependencies with Bundler run: | @@ -102,6 +120,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 +166,7 @@ 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 - name: Upload Bugsnag Sourcemaps if: success() @@ -156,8 +174,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 +183,19 @@ jobs: with: name: build_logs path: ./ios/build_logs/ + retention-days: 7 - 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` + 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 +215,7 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: 3.1.6 + bundler-cache: true - name: Install Dependencies with Bundler run: | @@ -205,13 +230,6 @@ jobs: - 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 +237,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 +264,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/fastlane/Fastfile b/fastlane/Fastfile index 9b236b387..1d974bff3 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -192,223 +192,222 @@ end # =========================== platform :ios do + # ==== Helper Methods ==== + + 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 - desc "Register new devices from a file" + # ==== Device Management ==== + + desc "Register new devices from a file and update provisioning profiles" lane :register_devices_from_txt do UI.message("Registering new devices from file...") + + csv_path = "../../devices.txt" # Update with actual path + + unless File.exist?(csv_path) + UI.user_error!("Devices file not found at: #{csv_path}") + end - csv_path = "../../devices.txt" # Update this with the actual path to your file + register_devices(devices_file: csv_path) + log_success("Devices registered successfully") - # Register devices using the devices_file parameter - register_devices( - devices_file: csv_path - ) - - UI.message("Devices registered successfully.") - - # Update provisioning profiles for all app identifiers + update_provisioning_profiles_for_new_devices + end + + desc "Update provisioning profiles for all app identifiers after adding new devices" + private_lane :update_provisioning_profiles_for_new_devices do + UI.message("Updating provisioning profiles for new devices...") + app_identifiers.each do |app_identifier| match( type: "development", app_identifier: app_identifier, - readonly: false, # Regenerate provisioning profile if needed + readonly: false, force_for_new_devices: true, clone_branch_directly: true ) end - - UI.message("Development provisioning profiles updated.") - end - - desc "Create a temporary keychain" - lane :create_temp_keychain do - UI.message("Creating a temporary keychain...") - - create_keychain( - name: "temp_keychain", - password: ENV["KEYCHAIN_PASSWORD"], - default_keychain: true, - unlock: true, - timeout: 3600, - lock_when_sleeps: true - ) - - UI.message("Temporary keychain created successfully.") + + log_success("Development provisioning profiles updated") end - desc "Synchronize certificates and provisioning profiles" + # ==== Keychain Management ==== + + desc "Create a temporary keychain for CI builds" + lane :create_temp_keychain do + ensure_env_vars(["KEYCHAIN_PASSWORD"]) + UI.message("Creating temporary keychain...") + + begin + create_keychain( + name: "temp_keychain", + password: ENV["KEYCHAIN_PASSWORD"], + default_keychain: true, + unlock: true, + timeout: 3600, + lock_when_sleeps: true + ) + + log_success("Temporary keychain created") + rescue => e + log_error("Failed to create temporary keychain: #{e.message}") + raise e + end + end + + desc "Delete temporary keychain when done with the build" + lane :delete_temp_keychain do + UI.message("Deleting temporary keychain...") + + begin + delete_keychain(name: "temp_keychain") + log_success("Temporary keychain deleted") + rescue => e + log_error("Failed to delete temporary keychain: #{e.message}") + # Don't raise error here, as this is cleanup code + end + end + + # ==== Provisioning Profile Management ==== + + desc "Setup provisioning profiles for all app identifiers" 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"] - ) + begin + 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"] + ) + rescue => e + log_error("Failed to fetch provisioning profile for #{app_identifier}: #{e.message}") + raise e + 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( - type: "development", - platform: "catalyst", - app_identifier: app_identifiers, - readonly: true, - clone_branch_directly: true - ) - end - - desc "Fetch App Store certificates and provisioning profiles for Mac Catalyst" - lane :fetch_appstore_profiles_catalyst do - match( - type: "appstore", - platform: "catalyst", - app_identifier: app_identifiers, - readonly: true, - clone_branch_directly: true - ) - end - + # ==== Catalyst Support Lanes ==== + desc "Setup provisioning profiles for Mac Catalyst" - lane :setup_catalyst_provisioning_profiles do + lane :setup_catalyst_provisioning do + UI.message("Setting up Mac Catalyst provisioning profiles...") + + # First development profiles + fetch_catalyst_profiles(type: "development") + + # Then App Store profiles + fetch_catalyst_profiles(type: "appstore") + + log_success("Mac Catalyst provisioning profiles set up") + end + + private_lane :fetch_catalyst_profiles do |options| + type = options[:type] + readonly = options[:readonly] || true + force_for_new_devices = options[:force_for_new_devices] || false + app_identifiers.each do |app_identifier| match( - type: "development", + type: type, platform: "catalyst", app_identifier: app_identifier, - readonly: false, - force_for_new_devices: true, - clone_branch_directly: true - ) - - match( - type: "appstore", - platform: "catalyst", - app_identifier: app_identifier, - readonly: false, + readonly: readonly, + force_for_new_devices: force_for_new_devices, clone_branch_directly: true ) end end + # ==== Build Preparation ==== + desc "Clear derived data" lane :clear_derived_data_lane do UI.message("Clearing derived data...") - clear_derived_data + + begin + clear_derived_data + log_success("Derived data cleared") + rescue => e + log_error("Failed to clear derived data: #{e.message}") + # Continue despite error + end end desc "Increment build number" lane :increment_build_number_lane do - UI.message("Incrementing build number to current timestamp...") + ensure_env_vars(["NEW_BUILD_NUMBER"]) + UI.message("Incrementing build number to #{ENV['NEW_BUILD_NUMBER']}...") - # Set the new build number - increment_build_number( - xcodeproj: "ios/BlueWallet.xcodeproj", - build_number: ENV["NEW_BUILD_NUMBER"] - ) - - UI.message("Build number set to: #{ENV['NEW_BUILD_NUMBER']}") + begin + increment_build_number( + xcodeproj: "ios/BlueWallet.xcodeproj", + build_number: ENV["NEW_BUILD_NUMBER"] + ) + + log_success("Build number set to: #{ENV['NEW_BUILD_NUMBER']}") + rescue => e + log_error("Failed to increment build number: #{e.message}") + raise e + end end desc "Install CocoaPods dependencies" lane :install_pods do UI.message("Installing CocoaPods dependencies...") - cocoapods(podfile: "ios/Podfile") + + begin + cocoapods(podfile: "ios/Podfile") + log_success("CocoaPods dependencies installed") + rescue => e + log_error("Failed to install CocoaPods dependencies: #{e.message}") + raise e + end end - - desc "Upload IPA to TestFlight" - lane :upload_to_testflight_lane do - - branch_name = ENV['BRANCH_NAME'] || "unknown-branch" - last_commit_message = ENV['LATEST_COMMIT_MESSAGE'] || "No commit message found" - - - changelog = <<~CHANGELOG - Build Information: - CHANGELOG - - # Include the branch name only if it is not 'master' - if branch_name != 'master' - changelog += <<~CHANGELOG - - Branch: #{branch_name} - CHANGELOG - end - - changelog += <<~CHANGELOG - - Commit: #{last_commit_message} - CHANGELOG - - ipa_path = ENV['IPA_OUTPUT_PATH'] + # ==== Build and Upload ==== - if ipa_path.nil? || ipa_path.empty? || !File.exist?(ipa_path) - UI.user_error!("IPA file not found at path: #{ipa_path}") - end - - UI.message("Uploading IPA to TestFlight from path: #{ipa_path}") - UI.message("Changelog:\n#{changelog}") - - - upload_to_testflight( - api_key_path: "./appstore_api_key.json", - ipa: ipa_path, - skip_waiting_for_build_processing: true, - changelog: changelog - ) - - UI.success("Successfully uploaded IPA to TestFlight!") -end - -desc "Upload iOS source maps to Bugsnag" -lane :upload_bugsnag_sourcemaps do - bugsnag_api_key = ENV['BUGSNAG_API_KEY'] - bugsnag_release_stage = ENV['BUGSNAG_RELEASE_STAGE'] || "production" - version = ENV['PROJECT_VERSION'] - build_number = ENV['NEW_BUILD_NUMBER'] - - UI.user_error!("BUGSNAG_API_KEY environment variable is missing") if bugsnag_api_key.nil? - UI.user_error!("PROJECT_VERSION environment variable is missing") if version.nil? - UI.user_error!("NEW_BUILD_NUMBER environment variable is missing") if build_number.nil? - - ios_sourcemap = "./ios/build/Build/Products/Release-iphonesimulator/main.jsbundle.map" - - if File.exist?(ios_sourcemap) - UI.message("Uploading iOS source map to Bugsnag...") - bugsnag_sourcemaps_upload( - api_key: bugsnag_api_key, - source_map: ios_sourcemap, - minified_file: "./ios/main.jsbundle", - code_bundle_id: "#{version}-#{build_number}", - release_stage: bugsnag_release_stage, - app_version: version - ) - UI.success("iOS source map uploaded successfully.") - else - UI.error("iOS source map not found at #{ios_sourcemap}") - end -end - - desc "Build the iOS app" + desc "Build the iOS app for distribution" lane :build_app_lane do - Dir.chdir(project_root) do - UI.message("Building the application from: #{Dir.pwd}") - + UI.message("Building the application...") + workspace_path = File.join(project_root, "ios", "BlueWallet.xcworkspace") export_options_path = File.join(project_root, "ios", "export_options.plist") - + output_name = "BlueWallet_#{ENV['PROJECT_VERSION']}_#{ENV['NEW_BUILD_NUMBER']}.ipa" + output_dir = File.join(project_root, "ios", "build") + logs_dir = File.join(project_root, "ios", "build_logs") + + # Ensure the build logs directory exists + FileUtils.mkdir_p(logs_dir) unless Dir.exist?(logs_dir) + + # Clear derived data before building clear_derived_data_lane begin @@ -422,29 +421,142 @@ end 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"), + output_directory: output_dir, + output_name: output_name, + buildlog_path: logs_dir, silent: false, clean: true ) + + # Set environment variables for the IPA path + ipa_path = lane_context[SharedValues::IPA_OUTPUT_PATH] + + if ipa_path && File.exist?(ipa_path) + ENV['IPA_OUTPUT_PATH'] = ipa_path + sh("echo 'ipa_output_path=#{ipa_path}' >> $GITHUB_OUTPUT") if ENV['GITHUB_OUTPUT'] + log_success("IPA built successfully at: #{ipa_path}") + else + UI.user_error!("IPA not found after build_ios_app") + end 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] - - 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.") + log_error("Failed to build app: #{e.message}") + raise e end end + + desc "Upload IPA to TestFlight" + lane :upload_to_testflight_lane do + required_vars = ["IPA_OUTPUT_PATH"] + ensure_env_vars(required_vars) + + branch_name = ENV['BRANCH_NAME'] || "unknown-branch" + commit_message = ENV['LATEST_COMMIT_MESSAGE'] || "No commit message found" + + changelog = <<~CHANGELOG + Build Information: + CHANGELOG + + # Include branch name only if not master + if branch_name != 'master' + changelog += <<~CHANGELOG + - Branch: #{branch_name} + CHANGELOG + end + + changelog += <<~CHANGELOG + - Commit: #{commit_message} + CHANGELOG + + ipa_path = ENV['IPA_OUTPUT_PATH'] + + if !File.exist?(ipa_path) + UI.user_error!("IPA file not found at path: #{ipa_path}") + end + + UI.message("Uploading IPA to TestFlight from path: #{ipa_path}") + UI.message("Changelog:\n#{changelog}") + + begin + upload_to_testflight( + api_key_path: "./appstore_api_key.json", + ipa: ipa_path, + skip_waiting_for_build_processing: true, + changelog: changelog + ) + + log_success("Successfully uploaded IPA to TestFlight!") + rescue => e + log_error("Failed to upload to TestFlight: #{e.message}") + raise e + end + end + + # ==== Bugsnag Integration ==== + + desc "Upload iOS source maps to Bugsnag" + lane :upload_bugsnag_sourcemaps do + required_vars = ["BUGSNAG_API_KEY", "PROJECT_VERSION", "NEW_BUILD_NUMBER"] + ensure_env_vars(required_vars) + + bugsnag_api_key = ENV['BUGSNAG_API_KEY'] + bugsnag_release_stage = ENV['BUGSNAG_RELEASE_STAGE'] || "production" + version = ENV['PROJECT_VERSION'] + build_number = ENV['NEW_BUILD_NUMBER'] + + ios_sourcemap = "./ios/build/Build/Products/Release-iphonesimulator/main.jsbundle.map" + + if File.exist?(ios_sourcemap) + UI.message("Uploading iOS source map to Bugsnag...") + + begin + bugsnag_sourcemaps_upload( + api_key: bugsnag_api_key, + source_map: ios_sourcemap, + minified_file: "./ios/main.jsbundle", + code_bundle_id: "#{version}-#{build_number}", + release_stage: bugsnag_release_stage, + app_version: version + ) + + log_success("iOS source map uploaded successfully") + rescue => e + log_error("Failed to upload sourcemaps to Bugsnag: #{e.message}") + # Continue despite error, don't fail the build + end + else + log_error("iOS source map not found at #{ios_sourcemap}") + end + end + + # ==== Complete Deployment Workflow ==== + + desc "Complete iOS deployment workflow" + lane :deploy_ios do |options| + UI.message("Starting iOS deployment process...") + + # Setup everything + create_temp_keychain + setup_provisioning_profiles + clear_derived_data_lane + increment_build_number_lane + + # Install pods if needed + unless File.directory?("ios/Pods") + install_pods + end + + # Build and upload + build_app_lane + upload_to_testflight_lane + upload_bugsnag_sourcemaps + + # Cleanup + delete_temp_keychain + + log_success("iOS deployment completed successfully!") + end end -end + # =========================== # Global Lanes # =========================== diff --git a/fastlane/Matchfile b/fastlane/Matchfile index ba2f5652b..579a14b42 100644 --- a/fastlane/Matchfile +++ b/fastlane/Matchfile @@ -3,33 +3,42 @@ # 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") + +# Always retry on network failures +retry_on_exception(true) + +# Optional: The Git branch that is used for match +# Default is 'master' +# branch("main") From b75aa7b2694ef000d3103e3a147d7be1217c62e9 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Sun, 2 Mar 2025 01:11:24 -0400 Subject: [PATCH 12/26] wip --- fastlane/Fastfile | 437 +++++++++++++++++++-------------------------- fastlane/Matchfile | 3 - 2 files changed, 183 insertions(+), 257 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 1d974bff3..adf441f25 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -192,8 +192,7 @@ end # =========================== platform :ios do - # ==== Helper Methods ==== - + # 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? @@ -207,82 +206,71 @@ platform :ios do 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 - # ==== Device Management ==== - - desc "Register new devices from a file and update provisioning profiles" + desc "Register new devices from a file" lane :register_devices_from_txt do UI.message("Registering new devices from file...") - - csv_path = "../../devices.txt" # Update with actual path - - unless File.exist?(csv_path) - UI.user_error!("Devices file not found at: #{csv_path}") - end - register_devices(devices_file: csv_path) - log_success("Devices registered successfully") + csv_path = "../../devices.txt" # Update this with the actual path to your file - update_provisioning_profiles_for_new_devices - end - - desc "Update provisioning profiles for all app identifiers after adding new devices" - private_lane :update_provisioning_profiles_for_new_devices do - UI.message("Updating provisioning profiles for new devices...") - + # Register devices using the devices_file parameter + register_devices( + devices_file: csv_path + ) + + UI.message("Devices registered successfully.") + + # Update provisioning profiles for all app identifiers app_identifiers.each do |app_identifier| match( type: "development", app_identifier: app_identifier, - readonly: false, + readonly: false, # Regenerate provisioning profile if needed force_for_new_devices: true, clone_branch_directly: true ) end - - log_success("Development provisioning profiles updated") - end - # ==== Keychain Management ==== + UI.message("Development provisioning profiles updated.") + end - desc "Create a temporary keychain for CI builds" + desc "Create a temporary keychain" lane :create_temp_keychain do - ensure_env_vars(["KEYCHAIN_PASSWORD"]) - UI.message("Creating temporary keychain...") - - begin - create_keychain( - name: "temp_keychain", - password: ENV["KEYCHAIN_PASSWORD"], - default_keychain: true, - unlock: true, - timeout: 3600, - lock_when_sleeps: true - ) - - log_success("Temporary keychain created") - rescue => e - log_error("Failed to create temporary keychain: #{e.message}") - raise e - end - end - - desc "Delete temporary keychain when done with the build" - lane :delete_temp_keychain do - UI.message("Deleting temporary keychain...") - - begin - delete_keychain(name: "temp_keychain") - log_success("Temporary keychain deleted") - rescue => e - log_error("Failed to delete temporary keychain: #{e.message}") - # Don't raise error here, as this is cleanup code - end + UI.message("Creating a temporary keychain...") + + create_keychain( + name: "temp_keychain", + password: ENV["KEYCHAIN_PASSWORD"], + default_keychain: true, + unlock: true, + timeout: 3600, + lock_when_sleeps: true + ) + + UI.message("Temporary keychain created successfully.") end - # ==== Provisioning Profile Management ==== - - desc "Setup provisioning profiles for all app identifiers" + 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) @@ -291,7 +279,8 @@ platform :ios do # Iterate over app identifiers to fetch provisioning profiles app_identifiers.each do |app_identifier| - begin + 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"], @@ -305,109 +294,162 @@ platform :ios do keychain_name: "temp_keychain", keychain_password: ENV["KEYCHAIN_PASSWORD"] ) - rescue => e - log_error("Failed to fetch provisioning profile for #{app_identifier}: #{e.message}") - raise e + log_success("Successfully fetched provisioning profile for #{app_identifier}") end end log_success("All provisioning profiles set up") end - # ==== Catalyst Support Lanes ==== - - desc "Setup provisioning profiles for Mac Catalyst" - lane :setup_catalyst_provisioning do - UI.message("Setting up Mac Catalyst provisioning profiles...") - - # First development profiles - fetch_catalyst_profiles(type: "development") - - # Then App Store profiles - fetch_catalyst_profiles(type: "appstore") - - log_success("Mac Catalyst provisioning profiles set up") + desc "Fetch development certificates and provisioning profiles for Mac Catalyst" + lane :fetch_dev_profiles_catalyst do + match( + type: "development", + platform: "catalyst", + app_identifier: app_identifiers, + readonly: true, + clone_branch_directly: true + ) end - - private_lane :fetch_catalyst_profiles do |options| - type = options[:type] - readonly = options[:readonly] || true - force_for_new_devices = options[:force_for_new_devices] || false - + + desc "Fetch App Store certificates and provisioning profiles for Mac Catalyst" + lane :fetch_appstore_profiles_catalyst do + match( + type: "appstore", + platform: "catalyst", + app_identifier: app_identifiers, + readonly: true, + clone_branch_directly: true + ) + end + + desc "Setup provisioning profiles for Mac Catalyst" + lane :setup_catalyst_provisioning_profiles do app_identifiers.each do |app_identifier| match( - type: type, + type: "development", platform: "catalyst", app_identifier: app_identifier, - readonly: readonly, - force_for_new_devices: force_for_new_devices, + readonly: false, + force_for_new_devices: true, + clone_branch_directly: true + ) + + match( + type: "appstore", + platform: "catalyst", + app_identifier: app_identifier, + readonly: false, clone_branch_directly: true ) end end - # ==== Build Preparation ==== - desc "Clear derived data" lane :clear_derived_data_lane do UI.message("Clearing derived data...") - - begin - clear_derived_data - log_success("Derived data cleared") - rescue => e - log_error("Failed to clear derived data: #{e.message}") - # Continue despite error - end + clear_derived_data end desc "Increment build number" lane :increment_build_number_lane do - ensure_env_vars(["NEW_BUILD_NUMBER"]) - UI.message("Incrementing build number to #{ENV['NEW_BUILD_NUMBER']}...") + UI.message("Incrementing build number to current timestamp...") - begin - increment_build_number( - xcodeproj: "ios/BlueWallet.xcodeproj", - build_number: ENV["NEW_BUILD_NUMBER"] - ) - - log_success("Build number set to: #{ENV['NEW_BUILD_NUMBER']}") - rescue => e - log_error("Failed to increment build number: #{e.message}") - raise e - end + # Set the new build number + increment_build_number( + xcodeproj: "ios/BlueWallet.xcodeproj", + build_number: ENV["NEW_BUILD_NUMBER"] + ) + + UI.message("Build number set to: #{ENV['NEW_BUILD_NUMBER']}") end desc "Install CocoaPods dependencies" lane :install_pods do UI.message("Installing CocoaPods dependencies...") - - begin - cocoapods(podfile: "ios/Podfile") - log_success("CocoaPods dependencies installed") - rescue => e - log_error("Failed to install CocoaPods dependencies: #{e.message}") - raise e - end + cocoapods(podfile: "ios/Podfile") end - # ==== Build and Upload ==== + + desc "Upload IPA to TestFlight" + lane :upload_to_testflight_lane do + + branch_name = ENV['BRANCH_NAME'] || "unknown-branch" + last_commit_message = ENV['LATEST_COMMIT_MESSAGE'] || "No commit message found" + + + changelog = <<~CHANGELOG + Build Information: + CHANGELOG + + # Include the branch name only if it is not 'master' + if branch_name != 'master' + changelog += <<~CHANGELOG + - Branch: #{branch_name} + CHANGELOG + end + + changelog += <<~CHANGELOG + - Commit: #{last_commit_message} + CHANGELOG + + ipa_path = ENV['IPA_OUTPUT_PATH'] - desc "Build the iOS app for distribution" + if ipa_path.nil? || ipa_path.empty? || !File.exist?(ipa_path) + UI.user_error!("IPA file not found at path: #{ipa_path}") + end + + UI.message("Uploading IPA to TestFlight from path: #{ipa_path}") + UI.message("Changelog:\n#{changelog}") + + + upload_to_testflight( + api_key_path: "./appstore_api_key.json", + ipa: ipa_path, + skip_waiting_for_build_processing: true, + changelog: changelog + ) + + UI.success("Successfully uploaded IPA to TestFlight!") +end + +desc "Upload iOS source maps to Bugsnag" +lane :upload_bugsnag_sourcemaps do + bugsnag_api_key = ENV['BUGSNAG_API_KEY'] + bugsnag_release_stage = ENV['BUGSNAG_RELEASE_STAGE'] || "production" + version = ENV['PROJECT_VERSION'] + build_number = ENV['NEW_BUILD_NUMBER'] + + UI.user_error!("BUGSNAG_API_KEY environment variable is missing") if bugsnag_api_key.nil? + UI.user_error!("PROJECT_VERSION environment variable is missing") if version.nil? + UI.user_error!("NEW_BUILD_NUMBER environment variable is missing") if build_number.nil? + + ios_sourcemap = "./ios/build/Build/Products/Release-iphonesimulator/main.jsbundle.map" + + if File.exist?(ios_sourcemap) + UI.message("Uploading iOS source map to Bugsnag...") + bugsnag_sourcemaps_upload( + api_key: bugsnag_api_key, + source_map: ios_sourcemap, + minified_file: "./ios/main.jsbundle", + code_bundle_id: "#{version}-#{build_number}", + release_stage: bugsnag_release_stage, + app_version: version + ) + UI.success("iOS source map uploaded successfully.") + else + UI.error("iOS source map not found at #{ios_sourcemap}") + end +end + + desc "Build the iOS app" lane :build_app_lane do - UI.message("Building the application...") - + 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") - output_name = "BlueWallet_#{ENV['PROJECT_VERSION']}_#{ENV['NEW_BUILD_NUMBER']}.ipa" - output_dir = File.join(project_root, "ios", "build") - logs_dir = File.join(project_root, "ios", "build_logs") - - # Ensure the build logs directory exists - FileUtils.mkdir_p(logs_dir) unless Dir.exist?(logs_dir) - - # Clear derived data before building + clear_derived_data_lane begin @@ -421,142 +463,29 @@ platform :ios do include_symbols: true, export_team_id: ENV["ITC_TEAM_ID"], export_options: export_options_path, - output_directory: output_dir, - output_name: output_name, - buildlog_path: logs_dir, + 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 ) - - # Set environment variables for the IPA path - ipa_path = lane_context[SharedValues::IPA_OUTPUT_PATH] - - if ipa_path && File.exist?(ipa_path) - ENV['IPA_OUTPUT_PATH'] = ipa_path - sh("echo 'ipa_output_path=#{ipa_path}' >> $GITHUB_OUTPUT") if ENV['GITHUB_OUTPUT'] - log_success("IPA built successfully at: #{ipa_path}") - else - UI.user_error!("IPA not found after build_ios_app") - end rescue => e - log_error("Failed to build app: #{e.message}") - raise e + UI.user_error!("build_ios_app failed: #{e.message}") end - end - desc "Upload IPA to TestFlight" - lane :upload_to_testflight_lane do - required_vars = ["IPA_OUTPUT_PATH"] - ensure_env_vars(required_vars) - - branch_name = ENV['BRANCH_NAME'] || "unknown-branch" - commit_message = ENV['LATEST_COMMIT_MESSAGE'] || "No commit message found" - - changelog = <<~CHANGELOG - Build Information: - CHANGELOG - - # Include branch name only if not master - if branch_name != 'master' - changelog += <<~CHANGELOG - - Branch: #{branch_name} - CHANGELOG - end - - changelog += <<~CHANGELOG - - Commit: #{commit_message} - CHANGELOG - - ipa_path = ENV['IPA_OUTPUT_PATH'] - - if !File.exist?(ipa_path) - UI.user_error!("IPA file not found at path: #{ipa_path}") - end - - UI.message("Uploading IPA to TestFlight from path: #{ipa_path}") - UI.message("Changelog:\n#{changelog}") - - begin - upload_to_testflight( - api_key_path: "./appstore_api_key.json", - ipa: ipa_path, - skip_waiting_for_build_processing: true, - changelog: changelog - ) - - log_success("Successfully uploaded IPA to TestFlight!") - rescue => e - log_error("Failed to upload to TestFlight: #{e.message}") - raise e - end - end + # Use File.join to construct paths without extra slashes + ipa_path = lane_context[SharedValues::IPA_OUTPUT_PATH] - # ==== Bugsnag Integration ==== - - desc "Upload iOS source maps to Bugsnag" - lane :upload_bugsnag_sourcemaps do - required_vars = ["BUGSNAG_API_KEY", "PROJECT_VERSION", "NEW_BUILD_NUMBER"] - ensure_env_vars(required_vars) - - bugsnag_api_key = ENV['BUGSNAG_API_KEY'] - bugsnag_release_stage = ENV['BUGSNAG_RELEASE_STAGE'] || "production" - version = ENV['PROJECT_VERSION'] - build_number = ENV['NEW_BUILD_NUMBER'] - - ios_sourcemap = "./ios/build/Build/Products/Release-iphonesimulator/main.jsbundle.map" - - if File.exist?(ios_sourcemap) - UI.message("Uploading iOS source map to Bugsnag...") - - begin - bugsnag_sourcemaps_upload( - api_key: bugsnag_api_key, - source_map: ios_sourcemap, - minified_file: "./ios/main.jsbundle", - code_bundle_id: "#{version}-#{build_number}", - release_stage: bugsnag_release_stage, - app_version: version - ) - - log_success("iOS source map uploaded successfully") - rescue => e - log_error("Failed to upload sourcemaps to Bugsnag: #{e.message}") - # Continue despite error, don't fail the build - end + 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 - log_error("iOS source map not found at #{ios_sourcemap}") + UI.user_error!("IPA not found after build_ios_app.") end end - - # ==== Complete Deployment Workflow ==== - - desc "Complete iOS deployment workflow" - lane :deploy_ios do |options| - UI.message("Starting iOS deployment process...") - - # Setup everything - create_temp_keychain - setup_provisioning_profiles - clear_derived_data_lane - increment_build_number_lane - - # Install pods if needed - unless File.directory?("ios/Pods") - install_pods - end - - # Build and upload - build_app_lane - upload_to_testflight_lane - upload_bugsnag_sourcemaps - - # Cleanup - delete_temp_keychain - - log_success("iOS deployment completed successfully!") - end end - +end # =========================== # Global Lanes # =========================== diff --git a/fastlane/Matchfile b/fastlane/Matchfile index 579a14b42..9772ca474 100644 --- a/fastlane/Matchfile +++ b/fastlane/Matchfile @@ -36,9 +36,6 @@ git_basic_authorization(ENV["GIT_ACCESS_TOKEN"]) # Storage mode (git by default) storage_mode("git") -# Always retry on network failures -retry_on_exception(true) - # Optional: The Git branch that is used for match # Default is 'master' # branch("main") From ccdb492ba0b8f6df5cd63382a94472b5e159c5ea Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Sun, 2 Mar 2025 01:49:30 -0400 Subject: [PATCH 13/26] w --- .../build-ios-release-pullrequest.yml | 15 +++ Gemfile | 4 +- fastlane/Fastfile | 103 ++++++++++++------ 3 files changed, 85 insertions(+), 37 deletions(-) diff --git a/.github/workflows/build-ios-release-pullrequest.yml b/.github/workflows/build-ios-release-pullrequest.yml index 104e97d8e..d37db54e9 100644 --- a/.github/workflows/build-ios-release-pullrequest.yml +++ b/.github/workflows/build-ios-release-pullrequest.yml @@ -103,6 +103,21 @@ jobs: with: xcode-version: latest-stable + - 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: diff --git a/Gemfile b/Gemfile index d117b37e2..31e042742 100644 --- a/Gemfile +++ b/Gemfile @@ -3,9 +3,9 @@ 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" # Update to the latest version gem 'xcodeproj', '< 1.26.0' gem 'concurrent-ruby', '< 1.3.4' plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') diff --git a/fastlane/Fastfile b/fastlane/Fastfile index adf441f25..36f690187 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -444,47 +444,80 @@ end desc "Build the iOS app" lane :build_app_lane do - Dir.chdir(project_root) do - UI.message("Building the application from: #{Dir.pwd}") + 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}") + + 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, + destination: "generic/platform=iOS", + xcargs: "SKIP_INSTALL=NO" + ) + 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}") - 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.") + # Use File.join to construct paths without extra slashes + ipa_path = lane_context[SharedValues::IPA_OUTPUT_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_OUTPUT") # Export for GitHub Actions + else + UI.user_error!("IPA not found after build_ios_app.") + end end end -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}") + return latest_version + end + end + + # Default to a reasonable iOS version if none found + UI.important("No iOS simulator runtimes found. Using default version.") + return "16.4" + end + end # =========================== # Global Lanes From 15c618b59a69789774d267998883d6e00dc1d96f Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Sun, 2 Mar 2025 02:01:12 -0400 Subject: [PATCH 14/26] Update Gemfile.lock --- Gemfile.lock | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 5fb20ce94..d65dc286f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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) @@ -129,7 +129,7 @@ GEM faraday_middleware (1.2.1) faraday (~> 1.0) fastimage (2.4.0) - fastlane (2.225.0) + fastlane (2.226.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -169,7 +169,7 @@ GEM tty-spinner (>= 0.8.0, < 1.0.0) word_wrap (~> 1.0.0) xcodeproj (>= 1.13.0, < 2.0.0) - xcpretty (~> 0.3.0) + xcpretty (~> 0.4.0) xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) fastlane-plugin-browserstack (0.3.3) rest-client (~> 2.0, >= 2.0.2) @@ -261,7 +261,7 @@ GEM netrc (~> 0.8) retriable (3.1.2) rexml (3.4.1) - rouge (2.0.7) + rouge (3.28.0) ruby-macho (2.5.1) ruby2_keywords (0.0.5) rubyzip (2.4.1) @@ -298,8 +298,8 @@ GEM colored2 (~> 3.1) nanaimo (~> 0.3.0) rexml (>= 3.3.6, < 4.0) - xcpretty (0.3.0) - rouge (~> 2.0.7) + xcpretty (0.4.0) + rouge (~> 3.28.0) xcpretty-travis-formatter (1.0.1) xcpretty (~> 0.2, >= 0.0.7) @@ -308,9 +308,9 @@ PLATFORMS 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_sourcemaps_upload rubyzip (= 2.4.1) From 15fc708a0a70691d824de3de4d896196f6554900 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Sun, 2 Mar 2025 04:47:37 -0400 Subject: [PATCH 15/26] Update Fastfile --- fastlane/Fastfile | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 36f690187..dca1c9f12 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -509,13 +509,17 @@ end if ios_versions.any? latest_version = ios_versions.first.to_s UI.success("Found iOS simulator version: #{latest_version}") - return 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 - - # Default to a reasonable iOS version if none found - UI.important("No iOS simulator runtimes found. Using default version.") - return "16.4" end end From 35deca58e0279813bf9895a37408a3cbd98dff09 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Sun, 2 Mar 2025 04:52:48 -0400 Subject: [PATCH 16/26] Update build-ios-release-pullrequest.yml --- .github/workflows/build-ios-release-pullrequest.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-ios-release-pullrequest.yml b/.github/workflows/build-ios-release-pullrequest.yml index d37db54e9..96fbb9e2d 100644 --- a/.github/workflows/build-ios-release-pullrequest.yml +++ b/.github/workflows/build-ios-release-pullrequest.yml @@ -12,7 +12,7 @@ on: jobs: build: - runs-on: macos-latest + runs-on: macos-15 timeout-minutes: 180 outputs: new_build_number: ${{ steps.generate_build_number.outputs.build_number }} @@ -101,7 +101,7 @@ jobs: - uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: latest-stable + xcode-version: latest - name: Install iOS Simulator Runtime run: | From e4e16a8f407bf076f5f557f6d0a5038f7a523793 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Sun, 2 Mar 2025 05:16:02 -0400 Subject: [PATCH 17/26] Update Fastfile --- fastlane/Fastfile | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index dca1c9f12..a22621782 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -456,25 +456,17 @@ end ios_version = determine_ios_version UI.message("Using iOS version: #{ios_version}") + UI.message("Using export options from: #{export_options_path}") 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, - destination: "generic/platform=iOS", - xcargs: "SKIP_INSTALL=NO" ) rescue => e UI.user_error!("build_ios_app failed: #{e.message}") From bb6d44367015af240ead0b7dc9944d54eedad549 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Sun, 2 Mar 2025 11:36:33 -0400 Subject: [PATCH 18/26] wwip --- .../build-ios-release-pullrequest.yml | 34 ++++++++++++++- fastlane/Fastfile | 42 +++++++++++++++---- 2 files changed, 67 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-ios-release-pullrequest.yml b/.github/workflows/build-ios-release-pullrequest.yml index 96fbb9e2d..1c8ef1cf2 100644 --- a/.github/workflows/build-ios-release-pullrequest.yml +++ b/.github/workflows/build-ios-release-pullrequest.yml @@ -182,6 +182,25 @@ jobs: id: build_app run: | 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() @@ -200,11 +219,24 @@ jobs: 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 + name: BlueWallet_IPA path: ${{ env.IPA_OUTPUT_PATH }} retention-days: 7 diff --git a/fastlane/Fastfile b/fastlane/Fastfile index a22621782..ec8377e9e 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -458,30 +458,56 @@ end 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: 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"), ) 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_OUTPUT") # 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 + + # 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 From 54166c05927c77b83b4b73f38ecfdf654b62aa80 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Sun, 2 Mar 2025 11:54:38 -0400 Subject: [PATCH 19/26] Update Fastfile --- fastlane/Fastfile | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index ec8377e9e..c770cdb17 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -511,6 +511,17 @@ 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 From 16936fca2776333c63b9c42d5215ff1aa3f14ace Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Sun, 2 Mar 2025 12:20:42 -0400 Subject: [PATCH 20/26] wip --- .../workflows/build-ios-release-pullrequest.yml | 4 ++-- Gemfile | 10 +++++++++- Gemfile.lock | 14 ++++++++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-ios-release-pullrequest.yml b/.github/workflows/build-ios-release-pullrequest.yml index 1c8ef1cf2..0cb602ce5 100644 --- a/.github/workflows/build-ios-release-pullrequest.yml +++ b/.github/workflows/build-ios-release-pullrequest.yml @@ -109,7 +109,7 @@ jobs: 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 + 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 @@ -272,7 +272,7 @@ 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 diff --git a/Gemfile b/Gemfile index 31e042742..68c1f3c85 100644 --- a/Gemfile +++ b/Gemfile @@ -5,8 +5,16 @@ ruby "3.1.6" gem 'rubyzip', '2.4.1' gem 'cocoapods', '~> 1.14.3' gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0' -gem "fastlane", "~> 2.226.0" # Update to the latest version +gem "fastlane", "~> 2.226.0" gem 'xcodeproj', '< 1.26.0' gem 'concurrent-ruby', '< 1.3.4' + +# Add fastlane plugins +gem "fastlane-plugin-browserstack" +gem "fastlane-plugin-bugsnag" + +# 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 d65dc286f..ace9d4579 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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) @@ -248,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) @@ -302,6 +312,8 @@ 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 @@ -312,7 +324,9 @@ DEPENDENCIES concurrent-ruby (< 1.3.4) fastlane (~> 2.226.0) fastlane-plugin-browserstack + fastlane-plugin-bugsnag fastlane-plugin-bugsnag_sourcemaps_upload + jwt rubyzip (= 2.4.1) xcodeproj (< 1.26.0) From 09394ff4f96a2c9ec8aafa973100e346ab08b2a3 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Sun, 2 Mar 2025 12:37:30 -0400 Subject: [PATCH 21/26] Update Fastfile --- fastlane/Fastfile | 508 ++++++++++++++++++++++++++-------------------- 1 file changed, 292 insertions(+), 216 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index c770cdb17..125fa4637 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -12,6 +12,62 @@ end default_platform(:android) project_root = File.expand_path("..", __dir__) +# =========================== +# Helper Methods +# =========================== + +desc "Update Apple Worldwide Developer Relations certificate" +lane :update_wwdr_certificate do + UI.message("Updating Apple WWDR certificate...") + + sh("curl -sL https://developer.apple.com/certificationauthority/AppleWWDRCA.cer -o /tmp/AppleWWDRCA.cer") + sh("security import /tmp/AppleWWDRCA.cer -k /Library/Keychains/System.keychain -T /usr/bin/codesign") + + UI.message("Apple WWDR certificate updated successfully") +rescue => e + UI.important("Failed to update WWDR certificate: #{e.message}") + UI.important("This is not critical, continuing with the process...") +end + +desc "Setup App Store Connect API Key" +lane :setup_app_store_connect_api_key do + UI.message("Setting up App Store Connect API Key...") + + # Check if the key file exists + api_key_path = ENV['APP_STORE_CONNECT_API_KEY_PATH'] || "./appstore_api_key.p8" + api_key_content = ENV['APP_STORE_CONNECT_API_KEY_CONTENT'] + + if api_key_content && !File.exist?(api_key_path) + UI.message("Creating API key file from content...") + File.write(api_key_path, api_key_content) + end + + unless File.exist?(api_key_path) + UI.user_error!("App Store Connect API key not found at path: #{api_key_path}") + end + + # Read required environment variables + key_id = ENV['APP_STORE_CONNECT_API_KEY_KEY_ID'] + issuer_id = ENV['APP_STORE_CONNECT_API_KEY_ISSUER_ID'] + + if key_id.nil? || issuer_id.nil? + UI.user_error!("Missing required environment variables: APP_STORE_CONNECT_API_KEY_KEY_ID or APP_STORE_CONNECT_API_KEY_ISSUER_ID") + end + + # Create JSON file required by Fastlane + api_key_json = { + "key_id" => key_id, + "issuer_id" => issuer_id, + "key" => api_key_path, + "duration" => 1200, # 20 minutes + "in_house" => false + }.to_json + + File.write("./appstore_api_key.json", api_key_json) + + UI.success("App Store Connect API Key setup complete") +end + # =========================== # Android Lanes # =========================== @@ -27,6 +83,7 @@ platform :android do UI.message("Creating keystore from HEX...") File.write("bluewallet-release-key.keystore.hex", keystore_file_hex) + # Using shell command here as there's no direct Fastlane action for xxd conversion sh("xxd -plain -revert bluewallet-release-key.keystore.hex > bluewallet-release-key.keystore") do |status| UI.user_error!("Error reverting hex to keystore") unless status.success? end @@ -42,14 +99,15 @@ platform :android do build_number = ENV['BUILD_NUMBER'] UI.user_error!("BUILD_NUMBER environment variable is missing") if build_number.nil? - # Extract versionName from build.gradle - version_name = sh("grep versionName android/app/build.gradle | awk '{print $2}' | tr -d '\"'").strip + # Extract versionName from build.gradle using Ruby file operations instead of grep + build_gradle_path = "android/app/build.gradle" + build_gradle_contents = File.read(build_gradle_path) + version_match = build_gradle_contents.match(/versionName\s+"([^"]+)"/) + version_name = version_match ? version_match[1] : nil UI.user_error!("Failed to extract versionName from build.gradle") if version_name.nil? || version_name.empty? # Update versionCode in build.gradle UI.message("Updating versionCode in build.gradle to #{build_number}...") - build_gradle_path = "android/app/build.gradle" - build_gradle_contents = File.read(build_gradle_path) new_build_gradle_contents = build_gradle_contents.gsub(/versionCode\s+\d+/, "versionCode #{build_number}") File.write(build_gradle_path, new_build_gradle_contents) @@ -67,9 +125,16 @@ platform :android do unsigned_apk_path = "android/app/build/outputs/apk/release/app-release-unsigned.apk" signed_apk_path = "android/app/build/outputs/apk/release/#{signed_apk_name}" - # Build APK + # Build APK using Fastlane's gradle action instead of shell UI.message("Building APK...") - sh("cd android && ./gradlew assembleRelease --no-daemon") + gradle( + task: "assembleRelease", + project_dir: "android", + properties: { + "android.optional.compilation": "PREFER_KOTLIN_WORKER", + }, + flags: "--no-daemon" + ) UI.message("APK build completed.") # Rename APK @@ -82,7 +147,7 @@ platform :android do next end - # Sign APK + # Sign APK - no direct Fastlane action for this specific task UI.message("Signing APK with apksigner...") apksigner_path = Dir.glob("#{ENV['ANDROID_HOME']}/build-tools/*/apksigner").sort.last UI.user_error!("apksigner not found in Android build-tools") if apksigner_path.nil? || apksigner_path.empty? @@ -95,11 +160,12 @@ end desc "Upload APK to BrowserStack and post result as PR comment" lane :upload_to_browserstack_and_comment do Dir.chdir(project_root) do - # Determine APK path + # Determine APK path using Fastlane's find_files instead of shell find command apk_path = ENV['APK_PATH'] if apk_path.nil? || apk_path.empty? UI.message("No APK path provided, searching for APK...") - apk_path = `find ./ -name "*.apk"`.strip + apk_files = Dir.glob("./**/*.apk") + apk_path = apk_files.first UI.user_error!("No APK file found") if apk_path.nil? || apk_path.empty? end @@ -151,7 +217,8 @@ end repo_owner, repo_name = repo.split('/') UI.message("Fetching existing comments for PR ##{pr_number}...") - + + # No direct Fastlane alternative for GitHub API calls comments_json = `gh api -X GET /repos/#{repo_owner}/#{repo_name}/issues/#{pr_number}/comments` comments = JSON.parse(comments_json) @@ -175,6 +242,7 @@ end if pr_number begin escaped_comment = comment.gsub("'", "'\\''") + # No direct Fastlane alternative for GitHub CLI operations sh("GH_TOKEN=#{ENV['GH_TOKEN']} gh pr comment #{pr_number} --body '#{escaped_comment}'") UI.success("Posted new comment to PR ##{pr_number}") rescue => e @@ -231,7 +299,12 @@ platform :ios do lane :register_devices_from_txt do UI.message("Registering new devices from file...") - csv_path = "../../devices.txt" # Update this with the actual path to your file + # Allow specifying a custom path but use a default if not provided + csv_path = ENV['DEVICES_FILE'] || File.join(project_root, "devices.txt") + + unless File.exist?(csv_path) + UI.user_error!("Devices file not found at path: #{csv_path}") + end # Register devices using the devices_file parameter register_devices( @@ -252,7 +325,7 @@ platform :ios do end UI.message("Development provisioning profiles updated.") - end + end desc "Create a temporary keychain" lane :create_temp_keychain do @@ -292,12 +365,12 @@ platform :ios do team_name: ENV["ITC_TEAM_NAME"], readonly: true, keychain_name: "temp_keychain", - keychain_password: ENV["KEYCHAIN_PASSWORD"] + keychain_password: ENV["KEYCHAIN_PASSWORD"], ) log_success("Successfully fetched provisioning profile for #{app_identifier}") end end - + log_success("All provisioning profiles set up") end @@ -360,7 +433,6 @@ platform :ios do xcodeproj: "ios/BlueWallet.xcodeproj", build_number: ENV["NEW_BUILD_NUMBER"] ) - UI.message("Build number set to: #{ENV['NEW_BUILD_NUMBER']}") end @@ -370,14 +442,12 @@ platform :ios do cocoapods(podfile: "ios/Podfile") end - desc "Upload IPA to TestFlight" lane :upload_to_testflight_lane do branch_name = ENV['BRANCH_NAME'] || "unknown-branch" last_commit_message = ENV['LATEST_COMMIT_MESSAGE'] || "No commit message found" - changelog = <<~CHANGELOG Build Information: CHANGELOG @@ -394,7 +464,6 @@ platform :ios do CHANGELOG ipa_path = ENV['IPA_OUTPUT_PATH'] - if ipa_path.nil? || ipa_path.empty? || !File.exist?(ipa_path) UI.user_error!("IPA file not found at path: #{ipa_path}") end @@ -402,7 +471,6 @@ platform :ios do UI.message("Uploading IPA to TestFlight from path: #{ipa_path}") UI.message("Changelog:\n#{changelog}") - upload_to_testflight( api_key_path: "./appstore_api_key.json", ipa: ipa_path, @@ -412,7 +480,6 @@ platform :ios do UI.success("Successfully uploaded IPA to TestFlight!") end - desc "Upload iOS source maps to Bugsnag" lane :upload_bugsnag_sourcemaps do bugsnag_api_key = ENV['BUGSNAG_API_KEY'] @@ -424,10 +491,23 @@ lane :upload_bugsnag_sourcemaps do UI.user_error!("PROJECT_VERSION environment variable is missing") if version.nil? UI.user_error!("NEW_BUILD_NUMBER environment variable is missing") if build_number.nil? - ios_sourcemap = "./ios/build/Build/Products/Release-iphonesimulator/main.jsbundle.map" + # Check multiple possible locations for source maps + source_map_paths = [ + "./ios/build/Build/Products/Release-iphonesimulator/main.jsbundle.map", + "./ios/main.jsbundle.map", + "./ios/assets/main.jsbundle.map" + ] + + ios_sourcemap = nil + source_map_paths.each do |path| + if File.exist?(path) + ios_sourcemap = path + break + end + end - if File.exist?(ios_sourcemap) - UI.message("Uploading iOS source map to Bugsnag...") + if ios_sourcemap + UI.message("Uploading iOS source map from #{ios_sourcemap} to Bugsnag...") bugsnag_sourcemaps_upload( api_key: bugsnag_api_key, source_map: ios_sourcemap, @@ -438,126 +518,14 @@ lane :upload_bugsnag_sourcemaps do ) UI.success("iOS source map uploaded successfully.") else - UI.error("iOS source map not found at #{ios_sourcemap}") + UI.error("iOS source map not found. Checked paths: #{source_map_paths.join(', ')}") end end - desc "Build the iOS app" - lane :build_app_lane do - Dir.chdir(project_root) do - UI.message("Building the application from: #{Dir.pwd}") - - workspace_path = File.join(project_root, "ios", "BlueWallet.xcworkspace") - export_options_path = File.join(project_root, "ios", "export_options.plist") - - 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", - 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 - - # 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 - # 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 # =========================== - - desc "Deploy to TestFlight" lane :deploy do |options| UI.message("Starting deployment process...") @@ -597,107 +565,215 @@ lane :deploy do |options| File.write(already_built_flag, Time.now.to_s) end -desc "Update 'What's New' section in App Store Connect for the 'Prepare for Submission' version" +desc "Interactively update 'What's New' section in App Store Connect" lane :update_release_notes do |options| require 'spaceship' - - UI.message("Logging in to App Store Connect...") - Spaceship::ConnectAPI.login - + + UI.message("📝 Interactive Release Notes Update 📝") + UI.message("This will update the 'What's New' section for the next version in App Store Connect.") + UI.message("=================================================================") + + # Get release notes from user input + UI.message("\nPlease enter your release notes (press Enter twice when finished):") + UI.message("Markdown format is supported. Keep it concise and clear.\n") + + release_notes_lines = [] + while (line = STDIN.gets) do + break if line.strip.empty? && !release_notes_lines.empty? + release_notes_lines << line + end + + release_notes_text = release_notes_lines.join("").strip + + if release_notes_text.empty? + UI.user_error!("No release notes entered. Operation cancelled.") + end + + # Show preview with proper formatting + UI.header("Preview of Release Notes:") + UI.message(release_notes_text) + UI.message("\n") + + # Connect to App Store Connect + UI.message("Connecting to App Store Connect...") + + begin + Spaceship::ConnectAPI.login + UI.success("✅ Successfully connected to App Store Connect") + rescue => e + UI.user_error!("❌ Failed to connect to App Store Connect: #{e.message}") + end + app = Spaceship::ConnectAPI::App.find(app_identifiers.first) - UI.user_error!("Could not find the app with identifier: #{app_identifiers.first}") unless app - - # Retry logic for fetching or creating the edit version - retries = 5 + + # Find or create editable version + UI.message("Looking for a version in 'Prepare for Submission' state...") + + retries = 3 + prepare_version = nil + begin prepare_version = app.get_edit_app_store_version(platform: Spaceship::ConnectAPI::Platform::IOS) if prepare_version.nil? - UI.message("No version in 'Prepare for Submission' found. Creating a new version...") - latest_version = app.get_latest_version(platform: Spaceship::ConnectAPI::Platform::IOS) - new_version_number = (latest_version.version_string.to_f + 0.1).to_s - prepare_version = app.create_version!(platform: Spaceship::ConnectAPI::Platform::IOS, version_string: new_version_number) - UI.message("Created new version: #{new_version_number}") + UI.important("No version found in 'Prepare for Submission' state.") + + if UI.confirm("Do you want to create a new version?") + UI.message("Finding the latest version...") + latest_version = app.get_latest_version(platform: Spaceship::ConnectAPI::Platform::IOS) + + # Calculate next version number - handle both semver formats + version_parts = latest_version.version_string.split('.') + if version_parts.length >= 3 + # Semantic versioning - increment patch version + version_parts[-1] = (version_parts[-1].to_i + 1).to_s + new_version_number = version_parts.join('.') + else + # Simple versioning - increment by 0.1 + new_version_number = (latest_version.version_string.to_f + 0.1).round(1).to_s + end + + # Allow user to customize version number + custom_version = UI.input("Enter version number (default: #{new_version_number}):") + new_version_number = custom_version unless custom_version.strip.empty? + + UI.message("Creating new version #{new_version_number}...") + prepare_version = app.create_version!(platform: Spaceship::ConnectAPI::Platform::IOS, version_string: new_version_number) + UI.success("✅ Created new version: #{new_version_number}") + else + UI.user_error!("Operation cancelled. No version to update.") + end else - UI.message("Found existing version in 'Prepare for Submission': #{prepare_version.version_string}") + UI.success("✅ Found existing version in 'Prepare for Submission': #{prepare_version.version_string}") end rescue => e retries -= 1 if retries > 0 - delay = 20 - UI.message("Cannot find edit app info... Retrying after #{delay} seconds (remaining: #{retries})") - sleep(delay) + UI.error("Error: #{e.message}. Retrying... (#{retries} attempts left)") + sleep(5) retry else - UI.user_error!("Failed to fetch or create the app version: #{e.message}") + UI.user_error!("❌ Failed to access or create app version: #{e.message}") end end - - # Extract existing metadata + + # Extract available localizations + UI.message("Fetching available localizations...") localized_metadata = prepare_version.get_app_store_version_localizations - - # Get enabled locales enabled_locales = localized_metadata.map(&:locale) - - # Define release notes - release_notes_text = options[:release_notes] - if release_notes_text.nil? || release_notes_text.strip.empty? - release_notes_path = "../release-notes.txt" - unless File.exist?(release_notes_path) - UI.error("Release notes file does not exist at path: #{release_notes_path}") - UI.user_error!("No release notes provided and no file found. Failing the lane.") + + UI.message("Found #{enabled_locales.count} enabled locales.") + + # Ask which locales to update + selected_locales = [] + + if UI.confirm("Do you want to update all available localizations with the same text? (No to select specific ones)") + selected_locales = enabled_locales + else + UI.message("Available locales:") + + # Display locales in a formatted way + locale_display = {} + enabled_locales.each_with_index do |locale, index| + locale_name = case locale + when 'en-US' then 'English (US) - Primary' + when 'ar-SA' then 'Arabic' + when 'zh-Hans' then 'Chinese (Simplified)' + when 'hr' then 'Croatian' + when 'da' then 'Danish' + when 'nl-NL' then 'Dutch' + when 'fi' then 'Finnish' + when 'fr-FR' then 'French' + when 'de-DE' then 'German' + when 'el' then 'Greek' + when 'he' then 'Hebrew' + when 'hu' then 'Hungarian' + when 'it' then 'Italian' + when 'ja' then 'Japanese' + when 'ms' then 'Malay' + when 'nb' then 'Norwegian' + when 'pl' then 'Polish' + when 'pt-BR' then 'Portuguese (Brazil)' + when 'pt-PT' then 'Portuguese (Portugal)' + when 'ro' then 'Romanian' + when 'ru' then 'Russian' + when 'es-MX' then 'Spanish (Mexico)' + when 'es-ES' then 'Spanish (Spain)' + when 'sv' then 'Swedish' + when 'th' then 'Thai' + else locale + end + + locale_display[locale] = "#{index + 1}. #{locale_name} (#{locale})" + UI.message(locale_display[locale]) end - release_notes_text = File.read(release_notes_path) - end - - # Define localized release notes - localized_release_notes = { - 'en-US' => release_notes_text, # English (U.S.) - Primary - 'ar-SA' => release_notes_text, # Arabic - 'zh-Hans' => release_notes_text, # Chinese (Simplified) - 'hr' => release_notes_text, # Croatian - 'da' => release_notes_text, # Danish - 'nl-NL' => release_notes_text, # Dutch - 'fi' => release_notes_text, # Finnish - 'fr-FR' => release_notes_text, # French - 'de-DE' => release_notes_text, # German - 'el' => release_notes_text, # Greek - 'he' => release_notes_text, # Hebrew - 'hu' => release_notes_text, # Hungarian - 'it' => release_notes_text, # Italian - 'ja' => release_notes_text, # Japanese - 'ms' => release_notes_text, # Malay - 'nb' => release_notes_text, # Norwegian - 'pl' => release_notes_text, # Polish - 'pt-BR' => release_notes_text, # Portuguese (Brazil) - 'pt-PT' => release_notes_text, # Portuguese (Portugal) - 'ro' => release_notes_text, # Romanian - 'ru' => release_notes_text, # Russian - 'es-MX' => release_notes_text, # Spanish (Mexico) - 'es-ES' => release_notes_text, # Spanish (Spain) - 'sv' => release_notes_text, # Swedish - 'th' => release_notes_text, # Thai - }.select { |locale, _| enabled_locales.include?(locale) } # Only include enabled locales - - # Review release notes updates - UI.message("Review the following release notes updates:") - localized_release_notes.each do |locale, notes| - UI.message("Locale: #{locale} - Notes: #{notes}") - end - - unless options[:force_yes] - confirm = UI.confirm("Do you want to proceed with these release notes updates?") - UI.user_error!("User aborted the lane.") unless confirm - end - - # Update release notes in App Store Connect - localized_release_notes.each do |locale, notes| - app_store_version_localization = localized_metadata.find { |loc| loc.locale == locale } - if app_store_version_localization - app_store_version_localization.update(attributes: { "whats_new" => notes }) + + UI.message("\nEnter the numbers of locales to update (comma-separated, e.g. '1,3,5'), or press Enter for all:") + locale_input = STDIN.gets.strip + + if locale_input.empty? + selected_locales = enabled_locales else - UI.error("No localization found for locale #{locale}") + selected_indices = locale_input.split(',').map(&:strip).map(&:to_i) + selected_indices.each do |idx| + if idx > 0 && idx <= enabled_locales.length + selected_locales << enabled_locales[idx - 1] + end + end + + # Ensure at least primary locale (en-US) is selected + if selected_locales.empty? || !selected_locales.include?('en-US') + if enabled_locales.include?('en-US') + selected_locales << 'en-US' + UI.important("Adding English (US) as it's required.") + end + end end end + + # Final confirmation with selected locales + UI.important("You are about to update release notes for version #{prepare_version.version_string}") + UI.important("Selected locales: #{selected_locales.count > 5 ? "All #{selected_locales.count} locales" : selected_locales.join(', ')}") + UI.important("Release notes:\n#{release_notes_text}") + + unless UI.confirm("Do you want to proceed with these updates?") + UI.user_error!("Operation cancelled by user.") + end + + # Update release notes + UI.message("Updating release notes...") + + update_count = 0 + selected_locales.each do |locale| + app_store_version_localization = localized_metadata.find { |loc| loc.locale == locale } + + if app_store_version_localization + begin + app_store_version_localization.update(attributes: { "whats_new" => release_notes_text }) + update_count += 1 + UI.success("✅ Updated #{locale}") + rescue => e + UI.error("❌ Failed to update #{locale}: #{e.message}") + end + else + UI.error("❌ No localization found for locale #{locale}") + end + end + + # Final result + if update_count == selected_locales.count + UI.success("✅ Successfully updated release notes for all selected locales.") + elsif update_count > 0 + UI.important("⚠️ Updated release notes for #{update_count} out of #{selected_locales.count} selected locales.") + else + UI.error("❌ Failed to update release notes for any locale.") + end + + # Save release notes to file for future reference + timestamp = Time.now.strftime("%Y%m%d_%H%M%S") + release_notes_file = "release_notes_#{prepare_version.version_string}_#{timestamp}.txt" + File.write(release_notes_file, release_notes_text) + UI.success("📝 Saved release notes to #{release_notes_file}") +end end \ No newline at end of file From 472307c271922454f476af2ef772f8fcd39a86f4 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Sun, 2 Mar 2025 12:47:13 -0400 Subject: [PATCH 22/26] Revert "Update Fastfile" This reverts commit 09394ff4f96a2c9ec8aafa973100e346ab08b2a3. --- fastlane/Fastfile | 500 ++++++++++++++++++++-------------------------- 1 file changed, 212 insertions(+), 288 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 125fa4637..c770cdb17 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -12,62 +12,6 @@ end default_platform(:android) project_root = File.expand_path("..", __dir__) -# =========================== -# Helper Methods -# =========================== - -desc "Update Apple Worldwide Developer Relations certificate" -lane :update_wwdr_certificate do - UI.message("Updating Apple WWDR certificate...") - - sh("curl -sL https://developer.apple.com/certificationauthority/AppleWWDRCA.cer -o /tmp/AppleWWDRCA.cer") - sh("security import /tmp/AppleWWDRCA.cer -k /Library/Keychains/System.keychain -T /usr/bin/codesign") - - UI.message("Apple WWDR certificate updated successfully") -rescue => e - UI.important("Failed to update WWDR certificate: #{e.message}") - UI.important("This is not critical, continuing with the process...") -end - -desc "Setup App Store Connect API Key" -lane :setup_app_store_connect_api_key do - UI.message("Setting up App Store Connect API Key...") - - # Check if the key file exists - api_key_path = ENV['APP_STORE_CONNECT_API_KEY_PATH'] || "./appstore_api_key.p8" - api_key_content = ENV['APP_STORE_CONNECT_API_KEY_CONTENT'] - - if api_key_content && !File.exist?(api_key_path) - UI.message("Creating API key file from content...") - File.write(api_key_path, api_key_content) - end - - unless File.exist?(api_key_path) - UI.user_error!("App Store Connect API key not found at path: #{api_key_path}") - end - - # Read required environment variables - key_id = ENV['APP_STORE_CONNECT_API_KEY_KEY_ID'] - issuer_id = ENV['APP_STORE_CONNECT_API_KEY_ISSUER_ID'] - - if key_id.nil? || issuer_id.nil? - UI.user_error!("Missing required environment variables: APP_STORE_CONNECT_API_KEY_KEY_ID or APP_STORE_CONNECT_API_KEY_ISSUER_ID") - end - - # Create JSON file required by Fastlane - api_key_json = { - "key_id" => key_id, - "issuer_id" => issuer_id, - "key" => api_key_path, - "duration" => 1200, # 20 minutes - "in_house" => false - }.to_json - - File.write("./appstore_api_key.json", api_key_json) - - UI.success("App Store Connect API Key setup complete") -end - # =========================== # Android Lanes # =========================== @@ -83,7 +27,6 @@ platform :android do UI.message("Creating keystore from HEX...") File.write("bluewallet-release-key.keystore.hex", keystore_file_hex) - # Using shell command here as there's no direct Fastlane action for xxd conversion sh("xxd -plain -revert bluewallet-release-key.keystore.hex > bluewallet-release-key.keystore") do |status| UI.user_error!("Error reverting hex to keystore") unless status.success? end @@ -99,15 +42,14 @@ platform :android do build_number = ENV['BUILD_NUMBER'] UI.user_error!("BUILD_NUMBER environment variable is missing") if build_number.nil? - # Extract versionName from build.gradle using Ruby file operations instead of grep - build_gradle_path = "android/app/build.gradle" - build_gradle_contents = File.read(build_gradle_path) - version_match = build_gradle_contents.match(/versionName\s+"([^"]+)"/) - version_name = version_match ? version_match[1] : nil + # Extract versionName from build.gradle + version_name = sh("grep versionName android/app/build.gradle | awk '{print $2}' | tr -d '\"'").strip UI.user_error!("Failed to extract versionName from build.gradle") if version_name.nil? || version_name.empty? # Update versionCode in build.gradle UI.message("Updating versionCode in build.gradle to #{build_number}...") + build_gradle_path = "android/app/build.gradle" + build_gradle_contents = File.read(build_gradle_path) new_build_gradle_contents = build_gradle_contents.gsub(/versionCode\s+\d+/, "versionCode #{build_number}") File.write(build_gradle_path, new_build_gradle_contents) @@ -125,16 +67,9 @@ platform :android do unsigned_apk_path = "android/app/build/outputs/apk/release/app-release-unsigned.apk" signed_apk_path = "android/app/build/outputs/apk/release/#{signed_apk_name}" - # Build APK using Fastlane's gradle action instead of shell + # Build APK UI.message("Building APK...") - gradle( - task: "assembleRelease", - project_dir: "android", - properties: { - "android.optional.compilation": "PREFER_KOTLIN_WORKER", - }, - flags: "--no-daemon" - ) + sh("cd android && ./gradlew assembleRelease --no-daemon") UI.message("APK build completed.") # Rename APK @@ -147,7 +82,7 @@ platform :android do next end - # Sign APK - no direct Fastlane action for this specific task + # Sign APK UI.message("Signing APK with apksigner...") apksigner_path = Dir.glob("#{ENV['ANDROID_HOME']}/build-tools/*/apksigner").sort.last UI.user_error!("apksigner not found in Android build-tools") if apksigner_path.nil? || apksigner_path.empty? @@ -160,12 +95,11 @@ end desc "Upload APK to BrowserStack and post result as PR comment" lane :upload_to_browserstack_and_comment do Dir.chdir(project_root) do - # Determine APK path using Fastlane's find_files instead of shell find command + # Determine APK path apk_path = ENV['APK_PATH'] if apk_path.nil? || apk_path.empty? UI.message("No APK path provided, searching for APK...") - apk_files = Dir.glob("./**/*.apk") - apk_path = apk_files.first + apk_path = `find ./ -name "*.apk"`.strip UI.user_error!("No APK file found") if apk_path.nil? || apk_path.empty? end @@ -217,8 +151,7 @@ end repo_owner, repo_name = repo.split('/') UI.message("Fetching existing comments for PR ##{pr_number}...") - - # No direct Fastlane alternative for GitHub API calls + comments_json = `gh api -X GET /repos/#{repo_owner}/#{repo_name}/issues/#{pr_number}/comments` comments = JSON.parse(comments_json) @@ -242,7 +175,6 @@ end if pr_number begin escaped_comment = comment.gsub("'", "'\\''") - # No direct Fastlane alternative for GitHub CLI operations sh("GH_TOKEN=#{ENV['GH_TOKEN']} gh pr comment #{pr_number} --body '#{escaped_comment}'") UI.success("Posted new comment to PR ##{pr_number}") rescue => e @@ -299,12 +231,7 @@ platform :ios do lane :register_devices_from_txt do UI.message("Registering new devices from file...") - # Allow specifying a custom path but use a default if not provided - csv_path = ENV['DEVICES_FILE'] || File.join(project_root, "devices.txt") - - unless File.exist?(csv_path) - UI.user_error!("Devices file not found at path: #{csv_path}") - end + csv_path = "../../devices.txt" # Update this with the actual path to your file # Register devices using the devices_file parameter register_devices( @@ -325,7 +252,7 @@ platform :ios do end UI.message("Development provisioning profiles updated.") - end + end desc "Create a temporary keychain" lane :create_temp_keychain do @@ -365,12 +292,12 @@ platform :ios do team_name: ENV["ITC_TEAM_NAME"], readonly: true, keychain_name: "temp_keychain", - keychain_password: ENV["KEYCHAIN_PASSWORD"], + keychain_password: ENV["KEYCHAIN_PASSWORD"] ) log_success("Successfully fetched provisioning profile for #{app_identifier}") end end - + log_success("All provisioning profiles set up") end @@ -433,6 +360,7 @@ platform :ios do xcodeproj: "ios/BlueWallet.xcodeproj", build_number: ENV["NEW_BUILD_NUMBER"] ) + UI.message("Build number set to: #{ENV['NEW_BUILD_NUMBER']}") end @@ -442,12 +370,14 @@ platform :ios do cocoapods(podfile: "ios/Podfile") end + desc "Upload IPA to TestFlight" lane :upload_to_testflight_lane do branch_name = ENV['BRANCH_NAME'] || "unknown-branch" last_commit_message = ENV['LATEST_COMMIT_MESSAGE'] || "No commit message found" + changelog = <<~CHANGELOG Build Information: CHANGELOG @@ -464,6 +394,7 @@ platform :ios do CHANGELOG ipa_path = ENV['IPA_OUTPUT_PATH'] + if ipa_path.nil? || ipa_path.empty? || !File.exist?(ipa_path) UI.user_error!("IPA file not found at path: #{ipa_path}") end @@ -471,6 +402,7 @@ platform :ios do UI.message("Uploading IPA to TestFlight from path: #{ipa_path}") UI.message("Changelog:\n#{changelog}") + upload_to_testflight( api_key_path: "./appstore_api_key.json", ipa: ipa_path, @@ -480,6 +412,7 @@ platform :ios do UI.success("Successfully uploaded IPA to TestFlight!") end + desc "Upload iOS source maps to Bugsnag" lane :upload_bugsnag_sourcemaps do bugsnag_api_key = ENV['BUGSNAG_API_KEY'] @@ -491,23 +424,10 @@ lane :upload_bugsnag_sourcemaps do UI.user_error!("PROJECT_VERSION environment variable is missing") if version.nil? UI.user_error!("NEW_BUILD_NUMBER environment variable is missing") if build_number.nil? - # Check multiple possible locations for source maps - source_map_paths = [ - "./ios/build/Build/Products/Release-iphonesimulator/main.jsbundle.map", - "./ios/main.jsbundle.map", - "./ios/assets/main.jsbundle.map" - ] - - ios_sourcemap = nil - source_map_paths.each do |path| - if File.exist?(path) - ios_sourcemap = path - break - end - end + ios_sourcemap = "./ios/build/Build/Products/Release-iphonesimulator/main.jsbundle.map" - if ios_sourcemap - UI.message("Uploading iOS source map from #{ios_sourcemap} to Bugsnag...") + if File.exist?(ios_sourcemap) + UI.message("Uploading iOS source map to Bugsnag...") bugsnag_sourcemaps_upload( api_key: bugsnag_api_key, source_map: ios_sourcemap, @@ -518,14 +438,126 @@ lane :upload_bugsnag_sourcemaps do ) UI.success("iOS source map uploaded successfully.") else - UI.error("iOS source map not found. Checked paths: #{source_map_paths.join(', ')}") + UI.error("iOS source map not found at #{ios_sourcemap}") end end + desc "Build the iOS app" + lane :build_app_lane do + Dir.chdir(project_root) do + UI.message("Building the application from: #{Dir.pwd}") + + workspace_path = File.join(project_root, "ios", "BlueWallet.xcworkspace") + export_options_path = File.join(project_root, "ios", "export_options.plist") + + 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", + 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 + + # 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 + # 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 # =========================== + + desc "Deploy to TestFlight" lane :deploy do |options| UI.message("Starting deployment process...") @@ -565,215 +597,107 @@ lane :deploy do |options| File.write(already_built_flag, Time.now.to_s) end -desc "Interactively update 'What's New' section in App Store Connect" +desc "Update 'What's New' section in App Store Connect for the 'Prepare for Submission' version" lane :update_release_notes do |options| require 'spaceship' - - UI.message("📝 Interactive Release Notes Update 📝") - UI.message("This will update the 'What's New' section for the next version in App Store Connect.") - UI.message("=================================================================") - - # Get release notes from user input - UI.message("\nPlease enter your release notes (press Enter twice when finished):") - UI.message("Markdown format is supported. Keep it concise and clear.\n") - - release_notes_lines = [] - while (line = STDIN.gets) do - break if line.strip.empty? && !release_notes_lines.empty? - release_notes_lines << line - end - - release_notes_text = release_notes_lines.join("").strip - - if release_notes_text.empty? - UI.user_error!("No release notes entered. Operation cancelled.") - end - - # Show preview with proper formatting - UI.header("Preview of Release Notes:") - UI.message(release_notes_text) - UI.message("\n") - - # Connect to App Store Connect - UI.message("Connecting to App Store Connect...") - - begin - Spaceship::ConnectAPI.login - UI.success("✅ Successfully connected to App Store Connect") - rescue => e - UI.user_error!("❌ Failed to connect to App Store Connect: #{e.message}") - end - + + UI.message("Logging in to App Store Connect...") + Spaceship::ConnectAPI.login + app = Spaceship::ConnectAPI::App.find(app_identifiers.first) + UI.user_error!("Could not find the app with identifier: #{app_identifiers.first}") unless app - - # Find or create editable version - UI.message("Looking for a version in 'Prepare for Submission' state...") - - retries = 3 - prepare_version = nil - + + # Retry logic for fetching or creating the edit version + retries = 5 begin prepare_version = app.get_edit_app_store_version(platform: Spaceship::ConnectAPI::Platform::IOS) if prepare_version.nil? - UI.important("No version found in 'Prepare for Submission' state.") - - if UI.confirm("Do you want to create a new version?") - UI.message("Finding the latest version...") - latest_version = app.get_latest_version(platform: Spaceship::ConnectAPI::Platform::IOS) - - # Calculate next version number - handle both semver formats - version_parts = latest_version.version_string.split('.') - if version_parts.length >= 3 - # Semantic versioning - increment patch version - version_parts[-1] = (version_parts[-1].to_i + 1).to_s - new_version_number = version_parts.join('.') - else - # Simple versioning - increment by 0.1 - new_version_number = (latest_version.version_string.to_f + 0.1).round(1).to_s - end - - # Allow user to customize version number - custom_version = UI.input("Enter version number (default: #{new_version_number}):") - new_version_number = custom_version unless custom_version.strip.empty? - - UI.message("Creating new version #{new_version_number}...") - prepare_version = app.create_version!(platform: Spaceship::ConnectAPI::Platform::IOS, version_string: new_version_number) - UI.success("✅ Created new version: #{new_version_number}") - else - UI.user_error!("Operation cancelled. No version to update.") - end + UI.message("No version in 'Prepare for Submission' found. Creating a new version...") + latest_version = app.get_latest_version(platform: Spaceship::ConnectAPI::Platform::IOS) + new_version_number = (latest_version.version_string.to_f + 0.1).to_s + prepare_version = app.create_version!(platform: Spaceship::ConnectAPI::Platform::IOS, version_string: new_version_number) + UI.message("Created new version: #{new_version_number}") else - UI.success("✅ Found existing version in 'Prepare for Submission': #{prepare_version.version_string}") + UI.message("Found existing version in 'Prepare for Submission': #{prepare_version.version_string}") end rescue => e retries -= 1 if retries > 0 - UI.error("Error: #{e.message}. Retrying... (#{retries} attempts left)") - sleep(5) + delay = 20 + UI.message("Cannot find edit app info... Retrying after #{delay} seconds (remaining: #{retries})") + sleep(delay) retry else - UI.user_error!("❌ Failed to access or create app version: #{e.message}") + UI.user_error!("Failed to fetch or create the app version: #{e.message}") end end - - # Extract available localizations - UI.message("Fetching available localizations...") + + # Extract existing metadata localized_metadata = prepare_version.get_app_store_version_localizations + + # Get enabled locales enabled_locales = localized_metadata.map(&:locale) - - UI.message("Found #{enabled_locales.count} enabled locales.") - - # Ask which locales to update - selected_locales = [] - - if UI.confirm("Do you want to update all available localizations with the same text? (No to select specific ones)") - selected_locales = enabled_locales - else - UI.message("Available locales:") - - # Display locales in a formatted way - locale_display = {} - enabled_locales.each_with_index do |locale, index| - locale_name = case locale - when 'en-US' then 'English (US) - Primary' - when 'ar-SA' then 'Arabic' - when 'zh-Hans' then 'Chinese (Simplified)' - when 'hr' then 'Croatian' - when 'da' then 'Danish' - when 'nl-NL' then 'Dutch' - when 'fi' then 'Finnish' - when 'fr-FR' then 'French' - when 'de-DE' then 'German' - when 'el' then 'Greek' - when 'he' then 'Hebrew' - when 'hu' then 'Hungarian' - when 'it' then 'Italian' - when 'ja' then 'Japanese' - when 'ms' then 'Malay' - when 'nb' then 'Norwegian' - when 'pl' then 'Polish' - when 'pt-BR' then 'Portuguese (Brazil)' - when 'pt-PT' then 'Portuguese (Portugal)' - when 'ro' then 'Romanian' - when 'ru' then 'Russian' - when 'es-MX' then 'Spanish (Mexico)' - when 'es-ES' then 'Spanish (Spain)' - when 'sv' then 'Swedish' - when 'th' then 'Thai' - else locale - end - - locale_display[locale] = "#{index + 1}. #{locale_name} (#{locale})" - UI.message(locale_display[locale]) - end - - UI.message("\nEnter the numbers of locales to update (comma-separated, e.g. '1,3,5'), or press Enter for all:") - locale_input = STDIN.gets.strip - - if locale_input.empty? - selected_locales = enabled_locales - else - selected_indices = locale_input.split(',').map(&:strip).map(&:to_i) - selected_indices.each do |idx| - if idx > 0 && idx <= enabled_locales.length - selected_locales << enabled_locales[idx - 1] - end - end - - # Ensure at least primary locale (en-US) is selected - if selected_locales.empty? || !selected_locales.include?('en-US') - if enabled_locales.include?('en-US') - selected_locales << 'en-US' - UI.important("Adding English (US) as it's required.") - end - end + + # Define release notes + release_notes_text = options[:release_notes] + if release_notes_text.nil? || release_notes_text.strip.empty? + release_notes_path = "../release-notes.txt" + unless File.exist?(release_notes_path) + UI.error("Release notes file does not exist at path: #{release_notes_path}") + UI.user_error!("No release notes provided and no file found. Failing the lane.") end + release_notes_text = File.read(release_notes_path) end - - # Final confirmation with selected locales - UI.important("You are about to update release notes for version #{prepare_version.version_string}") - UI.important("Selected locales: #{selected_locales.count > 5 ? "All #{selected_locales.count} locales" : selected_locales.join(', ')}") - UI.important("Release notes:\n#{release_notes_text}") - - unless UI.confirm("Do you want to proceed with these updates?") - UI.user_error!("Operation cancelled by user.") + + # Define localized release notes + localized_release_notes = { + 'en-US' => release_notes_text, # English (U.S.) - Primary + 'ar-SA' => release_notes_text, # Arabic + 'zh-Hans' => release_notes_text, # Chinese (Simplified) + 'hr' => release_notes_text, # Croatian + 'da' => release_notes_text, # Danish + 'nl-NL' => release_notes_text, # Dutch + 'fi' => release_notes_text, # Finnish + 'fr-FR' => release_notes_text, # French + 'de-DE' => release_notes_text, # German + 'el' => release_notes_text, # Greek + 'he' => release_notes_text, # Hebrew + 'hu' => release_notes_text, # Hungarian + 'it' => release_notes_text, # Italian + 'ja' => release_notes_text, # Japanese + 'ms' => release_notes_text, # Malay + 'nb' => release_notes_text, # Norwegian + 'pl' => release_notes_text, # Polish + 'pt-BR' => release_notes_text, # Portuguese (Brazil) + 'pt-PT' => release_notes_text, # Portuguese (Portugal) + 'ro' => release_notes_text, # Romanian + 'ru' => release_notes_text, # Russian + 'es-MX' => release_notes_text, # Spanish (Mexico) + 'es-ES' => release_notes_text, # Spanish (Spain) + 'sv' => release_notes_text, # Swedish + 'th' => release_notes_text, # Thai + }.select { |locale, _| enabled_locales.include?(locale) } # Only include enabled locales + + # Review release notes updates + UI.message("Review the following release notes updates:") + localized_release_notes.each do |locale, notes| + UI.message("Locale: #{locale} - Notes: #{notes}") end - - # Update release notes - UI.message("Updating release notes...") - - update_count = 0 - selected_locales.each do |locale| + + unless options[:force_yes] + confirm = UI.confirm("Do you want to proceed with these release notes updates?") + UI.user_error!("User aborted the lane.") unless confirm + end + + # Update release notes in App Store Connect + localized_release_notes.each do |locale, notes| app_store_version_localization = localized_metadata.find { |loc| loc.locale == locale } - if app_store_version_localization - begin - app_store_version_localization.update(attributes: { "whats_new" => release_notes_text }) - update_count += 1 - UI.success("✅ Updated #{locale}") - rescue => e - UI.error("❌ Failed to update #{locale}: #{e.message}") - end + app_store_version_localization.update(attributes: { "whats_new" => notes }) else - UI.error("❌ No localization found for locale #{locale}") + UI.error("No localization found for locale #{locale}") end end - - # Final result - if update_count == selected_locales.count - UI.success("✅ Successfully updated release notes for all selected locales.") - elsif update_count > 0 - UI.important("⚠️ Updated release notes for #{update_count} out of #{selected_locales.count} selected locales.") - else - UI.error("❌ Failed to update release notes for any locale.") - end - - # Save release notes to file for future reference - timestamp = Time.now.strftime("%Y%m%d_%H%M%S") - release_notes_file = "release_notes_#{prepare_version.version_string}_#{timestamp}.txt" - File.write(release_notes_file, release_notes_text) - UI.success("📝 Saved release notes to #{release_notes_file}") -end end \ No newline at end of file From 1da481542aa47ddabaeae07b818a0ab758790847 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Sun, 2 Mar 2025 12:57:34 -0400 Subject: [PATCH 23/26] Update build-ios-release-pullrequest.yml --- .github/workflows/build-ios-release-pullrequest.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/build-ios-release-pullrequest.yml b/.github/workflows/build-ios-release-pullrequest.yml index 0cb602ce5..ebf654e13 100644 --- a/.github/workflows/build-ios-release-pullrequest.yml +++ b/.github/workflows/build-ios-release-pullrequest.yml @@ -22,7 +22,6 @@ jobs: branch_name: ${{ steps.get_latest_commit_details.outputs.branch_name }} env: APPLE_ID: ${{ secrets.APPLE_ID }} - # Set Match to read-only for builds, only write new profiles manually MATCH_READONLY: "true" steps: @@ -31,7 +30,6 @@ jobs: with: fetch-depth: 0 # Ensures the full Git history is available - # Setup caching to speed up builds - name: Setup Caching uses: actions/cache@v3 with: @@ -122,7 +120,7 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: 3.1.6 - bundler-cache: true # This caches gems installed via bundler + bundler-cache: true - name: Install Dependencies with Bundler run: | From 62a5efc82cb17513571fa6350792044c1f5a13d2 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Mon, 3 Mar 2025 03:39:00 -0400 Subject: [PATCH 24/26] OPS: Packages updates --- ios/Podfile.lock | 10 +++--- package-lock.json | 80 +++++++++++++++++++++++------------------------ package.json | 12 +++---- 3 files changed, 51 insertions(+), 51 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 353999a26..a5e8592e3 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 @@ -1839,7 +1839,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - RNShare (11.1.0): + - RNShare (12.0.9): - DoubleConversion - glog - hermes-engine @@ -2253,7 +2253,7 @@ 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-image-picker: 86f8954a0b8c0f85d56fa1d85ae87936ae74e615 react-native-menu: 2cfe0a3b3c610ed331f00d9f0df300db14ba8692 react-native-randombytes: 3c8f3e89d12487fd03a2f966c288d495415fc116 react-native-safe-area-context: 3e33e7c43c8b74dba436a5a32651cb8d7064c740 @@ -2307,7 +2307,7 @@ SPEC CHECKSUMS: RNReactNativeHapticFeedback: 00ba111b82aa266bb3ee1aa576831c2ea9a9dfad RNReanimated: 66cf0f600a26d2b5e74c6e0b1c77c1ab1f62fc05 RNScreens: b3975354ddafe0fb00112a9054898ccf0d92c78e - RNShare: 6204e6a1987ba3e7c47071ef703e5449a0e3548a + RNShare: 381ed02f6c0dc42b8c24bb5a1e5df0ee5fd91354 RNSVG: a07e14363aa208062c6483bad24a438d5986d490 RNVectorIcons: 182892e7d1a2f27b52d3c627eca5d2665a22ee28 RNWatch: 28fe1f5e0c6410d45fd20925f4796fce05522e3f @@ -2317,4 +2317,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 6a0b46b5c..4d5690b16 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,7 +77,7 @@ "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-image-picker": "^8.2.0", "react-native-keychain": "9.1.0", "react-native-linear-gradient": "2.8.3", "react-native-localize": "3.4.1", @@ -93,7 +93,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", @@ -6607,12 +6607,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", @@ -6631,9 +6631,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", @@ -6645,18 +6645,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", @@ -6666,16 +6666,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" @@ -6687,12 +6687,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", @@ -6704,16 +6704,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", @@ -6721,9 +6721,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" @@ -22250,9 +22250,9 @@ } }, "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": "*", @@ -22462,9 +22462,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 9e5389668..4735b11ed 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,7 +145,7 @@ "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-image-picker": "8.2.0", "react-native-keychain": "9.1.0", "react-native-linear-gradient": "2.8.3", "react-native-localize": "3.4.1", @@ -156,12 +157,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", From 77f9cf7e169742cf0059cb3737403dc85770e2a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20Rodriguez=20V=C3=A9lez?= Date: Mon, 3 Mar 2025 09:39:13 -0400 Subject: [PATCH 25/26] Update AndroidManifest.xml --- android/app/src/main/AndroidManifest.xml | 1 - 1 file changed, 1 deletion(-) 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 @@ - Date: Mon, 3 Mar 2025 12:06:53 -0400 Subject: [PATCH 26/26] wip --- Gemfile | 4 ---- fastlane/Pluginfile | 1 + ios/BlueWallet.xcodeproj/project.pbxproj | 4 ++-- ios/Podfile.lock | 6 ++++++ package-lock.json | 12 ++++++------ 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/Gemfile b/Gemfile index 68c1f3c85..b0a72cff0 100644 --- a/Gemfile +++ b/Gemfile @@ -9,10 +9,6 @@ gem "fastlane", "~> 2.226.0" gem 'xcodeproj', '< 1.26.0' gem 'concurrent-ruby', '< 1.3.4' -# Add fastlane plugins -gem "fastlane-plugin-browserstack" -gem "fastlane-plugin-bugsnag" - # Required for App Store Connect API gem "jwt" 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/ios/BlueWallet.xcodeproj/project.pbxproj b/ios/BlueWallet.xcodeproj/project.pbxproj index 07ad0ecdd..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; diff --git a/ios/Podfile.lock b/ios/Podfile.lock index c2d0e5d10..4975e296c 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1345,6 +1345,8 @@ PODS: - React-Core - react-native-safe-area-context (5.2.0): - React-Core + - react-native-screen-capture (0.2.3): + - React - react-native-secure-key-store (2.0.10): - React-Core - react-native-tcp-socket (6.2.0): @@ -1960,6 +1962,7 @@ DEPENDENCIES: - 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`) - react-native-secure-key-store (from `../node_modules/react-native-secure-key-store`) - react-native-tcp-socket (from `../node_modules/react-native-tcp-socket`) - "react-native-true-sheet (from `../node_modules/@lodev09/react-native-true-sheet`)" @@ -2118,6 +2121,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-randombytes" react-native-safe-area-context: :path: "../node_modules/react-native-safe-area-context" + react-native-screen-capture: + :path: "../node_modules/react-native-screen-capture" react-native-secure-key-store: :path: "../node_modules/react-native-secure-key-store" react-native-tcp-socket: @@ -2277,6 +2282,7 @@ SPEC CHECKSUMS: react-native-prevent-screenshot-ios-android: 490b2ae701658753e819ca215201f4aa8cab3d53 react-native-randombytes: 3c8f3e89d12487fd03a2f966c288d495415fc116 react-native-safe-area-context: 3e33e7c43c8b74dba436a5a32651cb8d7064c740 + react-native-screen-capture: 7b6121f529681ed2fde36cdedadd0bb39e9a3796 react-native-secure-key-store: eb45b44bdec3f48e9be5cdfca0f49ddf64892ea6 react-native-tcp-socket: 61379457d7e702e83e28c213b6e085ac079e480f react-native-true-sheet: 15f8d1bfbf2aceca472b9ba585b4116041d20f34 diff --git a/package-lock.json b/package-lock.json index 4718797a8..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.16", - "@react-navigation/drawer": "^7.1.2", - "@react-navigation/native": "^7.0.15", - "@react-navigation/native-stack": "^7.2.1", + "@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,7 +77,7 @@ "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": "^8.2.0", + "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", @@ -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": "^12.0.9", + "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",