mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-03-12 02:08:22 +01:00
Merge branch 'master' into inputacc
This commit is contained in:
commit
7b32f5b1d7
281 changed files with 759 additions and 331 deletions
|
@ -12,7 +12,7 @@ on:
|
|||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-14
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 180
|
||||
outputs:
|
||||
new_build_number: ${{ steps.generate_build_number.outputs.build_number }}
|
||||
|
@ -25,27 +25,34 @@ jobs:
|
|||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # Fetches all history
|
||||
|
||||
|
||||
- name: Specify node version
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: 15.4
|
||||
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 3.1.6
|
||||
bundler-cache: true
|
||||
|
||||
- name: Install dependencies with Bundler
|
||||
run: bundle install
|
||||
run: |
|
||||
bundle config path vendor/bundle
|
||||
bundle install --jobs 4 --retry 3 --quiet
|
||||
|
||||
- name: Install node_modules
|
||||
run: npm install
|
||||
run: npm install --production
|
||||
|
||||
- name: Install CocoaPods Dependencies
|
||||
run: |
|
||||
gem install cocoapods
|
||||
bundle exec pod install
|
||||
working-directory: ./ios
|
||||
bundle exec fastlane ios install_pods
|
||||
- name: Cache CocoaPods Pods
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
|
@ -67,7 +74,6 @@ jobs:
|
|||
git config --global http.https://github.com/.extraheader "AUTHORIZATION: basic $(echo -n x-access-token:${ACCESS_TOKEN} | base64)"
|
||||
- name: Create Temporary Keychain
|
||||
run: bundle exec fastlane ios create_temp_keychain
|
||||
working-directory: ./ios
|
||||
env:
|
||||
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
|
||||
- name: Setup Provisioning Profiles
|
||||
|
@ -80,7 +86,6 @@ jobs:
|
|||
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
|
||||
run: |
|
||||
bundle exec fastlane ios setup_provisioning_profiles
|
||||
working-directory: ./ios
|
||||
- name: Cache Provisioning Profiles
|
||||
id: cache_provisioning_profiles
|
||||
uses: actions/cache@v2
|
||||
|
@ -112,19 +117,19 @@ jobs:
|
|||
echo "::set-output name=build_number::$NEW_BUILD_NUMBER"
|
||||
- name: Set Build Number
|
||||
run: bundle exec fastlane ios increment_build_number_lane
|
||||
working-directory: ./ios
|
||||
- name: Determine Marketing Version
|
||||
id: determine_marketing_version
|
||||
run: |
|
||||
MARKETING_VERSION=$(grep MARKETING_VERSION ios/BlueWallet.xcodeproj/project.pbxproj | awk -F '= ' '{print $2}' | tr -d ' ;' | head -1)
|
||||
MARKETING_VERSION=$(grep MARKETING_VERSION BlueWallet.xcodeproj/project.pbxproj | awk -F '= ' '{print $2}' | tr -d ' ;' | head -1)
|
||||
echo "PROJECT_VERSION=$MARKETING_VERSION" >> $GITHUB_ENV
|
||||
echo "::set-output name=project_version::$MARKETING_VERSION"
|
||||
working-directory: ios
|
||||
|
||||
- name: Expected IPA file name
|
||||
run: |
|
||||
echo "IPA file name: BlueWallet.${{env.PROJECT_VERSION}}(${{env.NEW_BUILD_NUMBER}}).ipa"
|
||||
- name: Build App
|
||||
run: bundle exec fastlane ios build_app_lane
|
||||
working-directory: ./ios
|
||||
- name: Upload IPA as Artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
@ -178,7 +183,6 @@ jobs:
|
|||
APP_STORE_CONNECT_API_KEY_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }}
|
||||
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
|
||||
run: bundle exec fastlane ios upload_to_testflight_lane
|
||||
working-directory: ./ios
|
||||
- name: Post PR Comment
|
||||
if: success() && github.event_name == 'pull_request'
|
||||
uses: actions/github-script@v6
|
||||
|
|
105
.github/workflows/build-release-apk.yml
vendored
105
.github/workflows/build-release-apk.yml
vendored
|
@ -1,19 +1,21 @@
|
|||
name: BuildReleaseApk
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
types: [opened, synchronize, reopened]
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
buildReleaseApk:
|
||||
runs-on: macos-latest
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout project
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: "0"
|
||||
|
||||
|
@ -23,7 +25,7 @@ jobs:
|
|||
node-version: 20
|
||||
|
||||
- name: Use npm caches
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
||||
|
@ -31,7 +33,7 @@ jobs:
|
|||
${{ runner.os }}-npm-
|
||||
|
||||
- name: Use specific Java version for sdkmanager to work
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
|
@ -40,28 +42,91 @@ jobs:
|
|||
- name: Install node_modules
|
||||
run: npm install --production
|
||||
|
||||
- name: Extract Version Name
|
||||
id: version_name
|
||||
run: |
|
||||
VERSION_NAME=$(grep versionName android/app/build.gradle | awk '{print $2}' | tr -d '"')
|
||||
echo "VERSION_NAME=$VERSION_NAME" >> $GITHUB_ENV
|
||||
echo "::set-output name=version_name::$VERSION_NAME"
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 3.1.6
|
||||
bundler-cache: true
|
||||
|
||||
- name: Cache Ruby Gems
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: vendor/bundle
|
||||
key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gems-
|
||||
|
||||
- name: Generate Build Number based on timestamp
|
||||
id: build_number
|
||||
run: |
|
||||
NEW_BUILD_NUMBER=$(date +%s)
|
||||
NEW_BUILD_NUMBER="$(date +%s)"
|
||||
echo "NEW_BUILD_NUMBER=$NEW_BUILD_NUMBER" >> $GITHUB_ENV
|
||||
echo "::set-output name=build_number::$NEW_BUILD_NUMBER"
|
||||
|
||||
- name: Build
|
||||
- name: Prepare Keystore
|
||||
run: bundle exec fastlane android prepare_keystore
|
||||
env:
|
||||
KEYSTORE_FILE_HEX: ${{ secrets.KEYSTORE_FILE_HEX }}
|
||||
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
|
||||
BUILD_NUMBER: ${{ env.NEW_BUILD_NUMBER }}
|
||||
run: ./scripts/build-release-apk.sh
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
if: success()
|
||||
- name: Update Version Code, Build, and Sign APK
|
||||
id: build_and_sign_apk
|
||||
run: |
|
||||
bundle exec fastlane android update_version_build_and_sign_apk
|
||||
env:
|
||||
BUILD_NUMBER: ${{ env.NEW_BUILD_NUMBER }}
|
||||
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
|
||||
|
||||
- name: Determine APK Filename and Path
|
||||
id: determine_apk_path
|
||||
run: |
|
||||
VERSION_NAME=$(grep versionName android/app/build.gradle | awk '{print $2}' | tr -d '"')
|
||||
BRANCH_NAME=$(echo ${{ github.head_ref || github.ref_name }} | sed 's/[^a-zA-Z0-9_-]/-/g')
|
||||
if [ "$BRANCH_NAME" != "master" ]; then
|
||||
EXPECTED_FILENAME="BlueWallet-${VERSION_NAME}-${NEW_BUILD_NUMBER}-${BRANCH_NAME}.apk"
|
||||
else
|
||||
EXPECTED_FILENAME="BlueWallet-${VERSION_NAME}-${NEW_BUILD_NUMBER}.apk"
|
||||
fi
|
||||
APK_PATH="android/app/build/outputs/apk/release/${EXPECTED_FILENAME}"
|
||||
echo "EXPECTED_FILENAME=${EXPECTED_FILENAME}" >> $GITHUB_ENV
|
||||
echo "APK_PATH=${APK_PATH}" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload APK as artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: BlueWallet-${{ env.VERSION_NAME }}(${{ env.NEW_BUILD_NUMBER }}).apk
|
||||
path: ./android/app/build/outputs/apk/release/BlueWallet-${{ env.VERSION_NAME }}(${{ env.NEW_BUILD_NUMBER }}).apk
|
||||
name: signed-apk
|
||||
path: ${{ env.APK_PATH }}
|
||||
|
||||
browserstack:
|
||||
runs-on: ubuntu-latest
|
||||
needs: buildReleaseApk
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 3.1.6
|
||||
bundler-cache: true
|
||||
|
||||
- name: Install dependencies with Bundler
|
||||
run: bundle install --jobs 4 --retry 3
|
||||
|
||||
- name: Download APK artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: signed-apk
|
||||
|
||||
- name: Set APK Path
|
||||
run: |
|
||||
APK_PATH=$(find ${{ github.workspace }} -name '*.apk')
|
||||
echo "APK_PATH=$APK_PATH" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload APK to BrowserStack and Post PR Comment
|
||||
env:
|
||||
BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }}
|
||||
BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }}
|
||||
GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: bundle exec fastlane upload_to_browserstack_and_comment
|
4
Gemfile
4
Gemfile
|
@ -5,4 +5,6 @@ ruby "3.1.6"
|
|||
gem 'rubyzip', '2.3.2'
|
||||
gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1'
|
||||
gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0'
|
||||
gem "fastlane"
|
||||
gem "fastlane"
|
||||
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
|
||||
eval_gemfile(plugins_path) if File.exist?(plugins_path)
|
||||
|
|
12
Gemfile.lock
12
Gemfile.lock
|
@ -167,6 +167,8 @@ GEM
|
|||
xcodeproj (>= 1.13.0, < 2.0.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)
|
||||
ffi (1.17.0)
|
||||
fourflusher (2.3.1)
|
||||
fuzzy_match (2.0.4)
|
||||
|
@ -208,6 +210,7 @@ GEM
|
|||
os (>= 0.9, < 2.0)
|
||||
signet (>= 0.16, < 2.a)
|
||||
highline (2.0.3)
|
||||
http-accept (1.7.0)
|
||||
http-cookie (1.0.7)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
|
@ -218,6 +221,9 @@ GEM
|
|||
jwt (2.8.2)
|
||||
base64
|
||||
logger (1.6.1)
|
||||
mime-types (3.5.2)
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2024.0820)
|
||||
mini_magick (4.13.2)
|
||||
mini_mime (1.1.5)
|
||||
minitest (5.25.1)
|
||||
|
@ -238,6 +244,11 @@ GEM
|
|||
declarative (< 0.1.0)
|
||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
rest-client (2.1.0)
|
||||
http-accept (>= 1.7.0, < 2.0)
|
||||
http-cookie (>= 1.0.2, < 2.0)
|
||||
mime-types (>= 1.16, < 4.0)
|
||||
netrc (~> 0.8)
|
||||
retriable (3.1.2)
|
||||
rexml (3.3.6)
|
||||
strscan
|
||||
|
@ -290,6 +301,7 @@ DEPENDENCIES
|
|||
activesupport (>= 6.1.7.5, != 7.1.0)
|
||||
cocoapods (>= 1.13, != 1.15.1, != 1.15.0)
|
||||
fastlane
|
||||
fastlane-plugin-browserstack
|
||||
rubyzip (= 2.3.2)
|
||||
|
||||
RUBY VERSION
|
||||
|
|
|
@ -264,6 +264,7 @@ class AmountInput extends Component {
|
|||
{amount !== BitcoinUnit.MAX ? (
|
||||
<TextInput
|
||||
{...this.props}
|
||||
caretHidden
|
||||
testID="BitcoinAmountInput"
|
||||
keyboardType="numeric"
|
||||
adjustsFontSizeToFit
|
||||
|
|
|
@ -3,8 +3,11 @@ import { Dimensions } from 'react-native';
|
|||
|
||||
import { isDesktop, isTablet } from '../../blue_modules/environment';
|
||||
|
||||
type ScreenSize = 'Handheld' | 'LargeScreen' | undefined;
|
||||
|
||||
interface ILargeScreenContext {
|
||||
isLargeScreen: boolean;
|
||||
setLargeScreenValue: (value: ScreenSize) => void;
|
||||
}
|
||||
|
||||
export const LargeScreenContext = createContext<ILargeScreenContext | undefined>(undefined);
|
||||
|
@ -15,7 +18,7 @@ interface LargeScreenProviderProps {
|
|||
|
||||
export const LargeScreenProvider: React.FC<LargeScreenProviderProps> = ({ children }) => {
|
||||
const [windowWidth, setWindowWidth] = useState<number>(Dimensions.get('window').width);
|
||||
const screenWidth: number = useMemo(() => Dimensions.get('screen').width, []);
|
||||
const [largeScreenValue, setLargeScreenValue] = useState<ScreenSize>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
const updateScreenUsage = (): void => {
|
||||
|
@ -30,13 +33,23 @@ export const LargeScreenProvider: React.FC<LargeScreenProviderProps> = ({ childr
|
|||
}, [windowWidth]);
|
||||
|
||||
const isLargeScreen: boolean = useMemo(() => {
|
||||
if (largeScreenValue === 'LargeScreen') {
|
||||
return true;
|
||||
} else if (largeScreenValue === 'Handheld') {
|
||||
return false;
|
||||
}
|
||||
const screenWidth: number = Dimensions.get('screen').width;
|
||||
const halfScreenWidth = windowWidth >= screenWidth / 2;
|
||||
const condition = (isTablet && halfScreenWidth) || isDesktop;
|
||||
console.debug(
|
||||
`LargeScreenProvider.isLargeScreen: width: ${windowWidth}, Screen width: ${screenWidth}, Is tablet: ${isTablet}, Is large screen: ${condition}, isDesktkop: ${isDesktop}`,
|
||||
);
|
||||
return condition;
|
||||
}, [windowWidth, screenWidth]);
|
||||
return (isTablet && halfScreenWidth) || isDesktop;
|
||||
}, [windowWidth, largeScreenValue]);
|
||||
|
||||
return <LargeScreenContext.Provider value={{ isLargeScreen }}>{children}</LargeScreenContext.Provider>;
|
||||
const contextValue = useMemo(
|
||||
() => ({
|
||||
isLargeScreen,
|
||||
setLargeScreenValue,
|
||||
}),
|
||||
[isLargeScreen, setLargeScreenValue],
|
||||
);
|
||||
|
||||
return <LargeScreenContext.Provider value={contextValue}>{children}</LargeScreenContext.Provider>;
|
||||
};
|
||||
|
|
187
components/DevMenu.tsx
Normal file
187
components/DevMenu.tsx
Normal file
|
@ -0,0 +1,187 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { DevSettings, Alert, Platform, AlertButton } from 'react-native';
|
||||
import { useStorage } from '../hooks/context/useStorage';
|
||||
import { HDSegwitBech32Wallet } from '../class';
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import { useIsLargeScreen } from '../hooks/useIsLargeScreen';
|
||||
import { TWallet } from '../class/wallets/types';
|
||||
|
||||
const getRandomLabelFromSecret = (secret: string): string => {
|
||||
const words = secret.split(' ');
|
||||
const firstWord = words[0];
|
||||
const lastWord = words[words.length - 1];
|
||||
return `[Developer] ${firstWord} ${lastWord}`;
|
||||
};
|
||||
|
||||
const showAlertWithWalletOptions = (
|
||||
wallets: TWallet[],
|
||||
title: string,
|
||||
message: string,
|
||||
onWalletSelected: (wallet: TWallet) => void,
|
||||
filterFn?: (wallet: TWallet) => boolean,
|
||||
) => {
|
||||
const filteredWallets = filterFn ? wallets.filter(filterFn) : wallets;
|
||||
|
||||
const showWallet = (index: number) => {
|
||||
if (index >= filteredWallets.length) return;
|
||||
const wallet = filteredWallets[index];
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
// Android: Use a limited number of buttons since the alert dialog has a limit
|
||||
Alert.alert(
|
||||
`${title}: ${wallet.getLabel()}`,
|
||||
`${message}\n\nSelected Wallet: ${wallet.getLabel()}\n\nWould you like to select this wallet or see the next one?`,
|
||||
[
|
||||
{
|
||||
text: 'Select This Wallet',
|
||||
onPress: () => onWalletSelected(wallet),
|
||||
},
|
||||
{
|
||||
text: 'Show Next Wallet',
|
||||
onPress: () => showWallet(index + 1),
|
||||
},
|
||||
{
|
||||
text: 'Cancel',
|
||||
style: 'cancel',
|
||||
},
|
||||
],
|
||||
{ cancelable: true },
|
||||
);
|
||||
} else {
|
||||
const options: AlertButton[] = filteredWallets.map(w => ({
|
||||
text: w.getLabel(),
|
||||
onPress: () => onWalletSelected(w),
|
||||
}));
|
||||
|
||||
options.push({
|
||||
text: 'Cancel',
|
||||
style: 'cancel',
|
||||
});
|
||||
|
||||
Alert.alert(title, message, options, { cancelable: true });
|
||||
}
|
||||
};
|
||||
|
||||
if (filteredWallets.length > 0) {
|
||||
showWallet(0);
|
||||
} else {
|
||||
Alert.alert('No wallets available');
|
||||
}
|
||||
};
|
||||
|
||||
const DevMenu: React.FC = () => {
|
||||
const { wallets, addWallet } = useStorage();
|
||||
const { setLargeScreenValue } = useIsLargeScreen();
|
||||
|
||||
useEffect(() => {
|
||||
if (__DEV__) {
|
||||
// Clear existing Dev Menu items to prevent duplication
|
||||
DevSettings.addMenuItem('Reset Dev Menu', () => {
|
||||
DevSettings.reload();
|
||||
});
|
||||
|
||||
DevSettings.addMenuItem('Add New Wallet', async () => {
|
||||
const wallet = new HDSegwitBech32Wallet();
|
||||
await wallet.generate();
|
||||
const label = getRandomLabelFromSecret(wallet.getSecret());
|
||||
wallet.setLabel(label);
|
||||
addWallet(wallet);
|
||||
|
||||
Clipboard.setString(wallet.getSecret());
|
||||
Alert.alert('New Wallet created!', `Wallet secret copied to clipboard.\nLabel: ${label}`);
|
||||
});
|
||||
|
||||
DevSettings.addMenuItem('Copy Wallet Secret', () => {
|
||||
if (wallets.length === 0) {
|
||||
Alert.alert('No wallets available');
|
||||
return;
|
||||
}
|
||||
|
||||
showAlertWithWalletOptions(wallets, 'Copy Wallet Secret', 'Select the wallet to copy the secret', wallet => {
|
||||
Clipboard.setString(wallet.getSecret());
|
||||
Alert.alert('Wallet Secret copied to clipboard!');
|
||||
});
|
||||
});
|
||||
|
||||
DevSettings.addMenuItem('Copy Wallet ID', () => {
|
||||
if (wallets.length === 0) {
|
||||
Alert.alert('No wallets available');
|
||||
return;
|
||||
}
|
||||
|
||||
showAlertWithWalletOptions(wallets, 'Copy Wallet ID', 'Select the wallet to copy the ID', wallet => {
|
||||
Clipboard.setString(wallet.getID());
|
||||
Alert.alert('Wallet ID copied to clipboard!');
|
||||
});
|
||||
});
|
||||
|
||||
DevSettings.addMenuItem('Copy Wallet Xpub', () => {
|
||||
if (wallets.length === 0) {
|
||||
Alert.alert('No wallets available');
|
||||
return;
|
||||
}
|
||||
|
||||
showAlertWithWalletOptions(
|
||||
wallets,
|
||||
'Copy Wallet Xpub',
|
||||
'Select the wallet to copy the Xpub',
|
||||
wallet => {
|
||||
const xpub = wallet.getXpub();
|
||||
if (xpub) {
|
||||
Clipboard.setString(xpub);
|
||||
Alert.alert('Wallet Xpub copied to clipboard!');
|
||||
} else {
|
||||
Alert.alert('This wallet does not have an Xpub.');
|
||||
}
|
||||
},
|
||||
wallet => typeof wallet.getXpub === 'function',
|
||||
);
|
||||
});
|
||||
|
||||
DevSettings.addMenuItem('Purge Wallet Transactions', () => {
|
||||
if (wallets.length === 0) {
|
||||
Alert.alert('No wallets available');
|
||||
return;
|
||||
}
|
||||
|
||||
showAlertWithWalletOptions(wallets, 'Purge Wallet Transactions', 'Select the wallet to purge transactions', wallet => {
|
||||
const msg = 'Transactions purged successfully!';
|
||||
|
||||
if (wallet.type === HDSegwitBech32Wallet.type) {
|
||||
wallet._txs_by_external_index = {};
|
||||
wallet._txs_by_internal_index = {};
|
||||
}
|
||||
|
||||
// @ts-ignore: Property '_hdWalletInstance' does not exist on type 'Wallet'. Pls help
|
||||
if (wallet._hdWalletInstance) {
|
||||
// @ts-ignore: Property '_hdWalletInstance' does not exist on type 'Wallet'. Pls help
|
||||
wallet._hdWalletInstance._txs_by_external_index = {};
|
||||
// @ts-ignore: Property '_hdWalletInstance' does not exist on type 'Wallet'. Pls help
|
||||
wallet._hdWalletInstance._txs_by_internal_index = {};
|
||||
}
|
||||
|
||||
Alert.alert(msg);
|
||||
});
|
||||
});
|
||||
|
||||
DevSettings.addMenuItem('Force Large Screen Interface', () => {
|
||||
setLargeScreenValue('LargeScreen');
|
||||
Alert.alert('Large Screen Interface forced.');
|
||||
});
|
||||
|
||||
DevSettings.addMenuItem('Force Handheld Interface', () => {
|
||||
setLargeScreenValue('Handheld');
|
||||
Alert.alert('Handheld Interface forced.');
|
||||
});
|
||||
|
||||
DevSettings.addMenuItem('Reset Screen Interface', () => {
|
||||
setLargeScreenValue(undefined);
|
||||
Alert.alert('Screen Interface reset to default.');
|
||||
});
|
||||
}
|
||||
}, [wallets, addWallet, setLargeScreenValue]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default DevMenu;
|
|
@ -45,11 +45,10 @@ const MenuElements = () => {
|
|||
if (reloadTransactionsMenuActionFunction && typeof reloadTransactionsMenuActionFunction === 'function') {
|
||||
reloadTransactionsMenuActionFunction();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
}, [reloadTransactionsMenuActionFunction]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('MenuElements: useEffect');
|
||||
console.debug('MenuElements: useEffect');
|
||||
if (walletsInitialized) {
|
||||
eventEmitter?.addListener('openSettings', openSettings);
|
||||
eventEmitter?.addListener('addWalletMenuAction', addWalletMenuAction);
|
||||
|
@ -62,8 +61,7 @@ const MenuElements = () => {
|
|||
eventEmitter?.removeAllListeners('importWalletMenuAction');
|
||||
eventEmitter?.removeAllListeners('reloadTransactionsMenuAction');
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [walletsInitialized]);
|
||||
}, [addWalletMenuAction, importWalletMenuAction, openSettings, reloadTransactionsMenuElementsFunction, walletsInitialized]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
|
|
@ -181,7 +181,7 @@ const TransactionsNavigationHeader: React.FC<TransactionsNavigationHeaderProps>
|
|||
style={styles.lineaderGradient}
|
||||
{...WalletGradient.linearGradientProps(wallet.type)}
|
||||
>
|
||||
<Image source={imageSource} defaultSource={imageSource} style={styles.chainIcon} />
|
||||
<Image source={imageSource} style={styles.chainIcon} />
|
||||
|
||||
<Text testID="WalletLabel" numberOfLines={1} style={styles.walletLabel} selectable>
|
||||
{wallet.getLabel()}
|
||||
|
@ -218,7 +218,7 @@ const TransactionsNavigationHeader: React.FC<TransactionsNavigationHeaderProps>
|
|||
<TouchableOpacity style={styles.walletPreferredUnitView} onPress={changeWalletBalanceUnit}>
|
||||
<Text style={styles.walletPreferredUnitText}>
|
||||
{wallet.getPreferredBalanceUnit() === BitcoinUnit.LOCAL_CURRENCY
|
||||
? preferredFiatCurrency?.endPointKey ?? FiatUnit.USD
|
||||
? (preferredFiatCurrency?.endPointKey ?? FiatUnit.USD)
|
||||
: wallet.getPreferredBalanceUnit()}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
|
|
@ -61,7 +61,7 @@ const NewWalletPanel: React.FC<NewWalletPanelProps> = ({ onPress }) => {
|
|||
const { colors } = useTheme();
|
||||
const { width } = useWindowDimensions();
|
||||
const itemWidth = width * 0.82 > 375 ? 375 : width * 0.82;
|
||||
const isLargeScreen = useIsLargeScreen();
|
||||
const { isLargeScreen } = useIsLargeScreen();
|
||||
const nStylesHooks = StyleSheet.create({
|
||||
container: isLargeScreen
|
||||
? {
|
||||
|
@ -192,7 +192,7 @@ export const WalletCarouselItem: React.FC<WalletCarouselItemProps> = React.memo(
|
|||
const { walletTransactionUpdateStatus } = useStorage();
|
||||
const { width } = useWindowDimensions();
|
||||
const itemWidth = width * 0.82 > 375 ? 375 : width * 0.82;
|
||||
const isLargeScreen = useIsLargeScreen();
|
||||
const { isLargeScreen } = useIsLargeScreen();
|
||||
|
||||
const onPressedIn = useCallback(() => {
|
||||
if (animationsEnabled) {
|
||||
|
@ -248,7 +248,7 @@ export const WalletCarouselItem: React.FC<WalletCarouselItemProps> = React.memo(
|
|||
return (
|
||||
<Animated.View
|
||||
style={[
|
||||
isLargeScreen || !horizontal ? [iStyles.rootLargeDevice, customStyle] : customStyle ?? { ...iStyles.root, width: itemWidth },
|
||||
isLargeScreen || !horizontal ? [iStyles.rootLargeDevice, customStyle] : (customStyle ?? { ...iStyles.root, width: itemWidth }),
|
||||
{ opacity, transform: [{ scale: scaleValue }] },
|
||||
]}
|
||||
>
|
||||
|
@ -264,7 +264,7 @@ export const WalletCarouselItem: React.FC<WalletCarouselItemProps> = React.memo(
|
|||
>
|
||||
<View style={[iStyles.shadowContainer, { backgroundColor: colors.background, shadowColor: colors.shadowColor }]}>
|
||||
<LinearGradient colors={WalletGradient.gradientsFor(item.type)} style={iStyles.grad}>
|
||||
<Image defaultSource={image} source={image} style={iStyles.image} />
|
||||
<Image source={image} style={iStyles.image} />
|
||||
<Text style={iStyles.br} />
|
||||
{!isPlaceHolder && (
|
||||
<>
|
||||
|
@ -374,31 +374,27 @@ const WalletsCarousel = forwardRef<FlatListRefType, WalletsCarouselProps>((props
|
|||
|
||||
const flatListRef = useRef<FlatList<any>>(null);
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
(): any => {
|
||||
return {
|
||||
scrollToEnd: (params: { animated?: boolean | null | undefined } | undefined) => flatListRef.current?.scrollToEnd(params),
|
||||
scrollToIndex: (params: {
|
||||
animated?: boolean | null | undefined;
|
||||
index: number;
|
||||
viewOffset?: number | undefined;
|
||||
viewPosition?: number | undefined;
|
||||
}) => flatListRef.current?.scrollToIndex(params),
|
||||
scrollToItem: (params: {
|
||||
animated?: boolean | null | undefined;
|
||||
item: any;
|
||||
viewOffset?: number | undefined;
|
||||
viewPosition?: number | undefined;
|
||||
}) => flatListRef.current?.scrollToItem(params),
|
||||
scrollToOffset: (params: { animated?: boolean | null | undefined; offset: number }) => flatListRef.current?.scrollToOffset(params),
|
||||
recordInteraction: () => flatListRef.current?.recordInteraction(),
|
||||
flashScrollIndicators: () => flatListRef.current?.flashScrollIndicators(),
|
||||
getNativeScrollRef: () => flatListRef.current?.getNativeScrollRef(),
|
||||
};
|
||||
},
|
||||
[],
|
||||
);
|
||||
useImperativeHandle(ref, (): any => {
|
||||
return {
|
||||
scrollToEnd: (params: { animated?: boolean | null | undefined } | undefined) => flatListRef.current?.scrollToEnd(params),
|
||||
scrollToIndex: (params: {
|
||||
animated?: boolean | null | undefined;
|
||||
index: number;
|
||||
viewOffset?: number | undefined;
|
||||
viewPosition?: number | undefined;
|
||||
}) => flatListRef.current?.scrollToIndex(params),
|
||||
scrollToItem: (params: {
|
||||
animated?: boolean | null | undefined;
|
||||
item: any;
|
||||
viewOffset?: number | undefined;
|
||||
viewPosition?: number | undefined;
|
||||
}) => flatListRef.current?.scrollToItem(params),
|
||||
scrollToOffset: (params: { animated?: boolean | null | undefined; offset: number }) => flatListRef.current?.scrollToOffset(params),
|
||||
recordInteraction: () => flatListRef.current?.recordInteraction(),
|
||||
flashScrollIndicators: () => flatListRef.current?.flashScrollIndicators(),
|
||||
getNativeScrollRef: () => flatListRef.current?.getNativeScrollRef(),
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onScrollToIndexFailed = (error: { averageItemLength: number; index: number }): void => {
|
||||
console.debug('onScrollToIndexFailed');
|
||||
|
|
|
@ -2,6 +2,144 @@ def app_identifiers
|
|||
["io.bluewallet.bluewallet", "io.bluewallet.bluewallet.watch", "io.bluewallet.bluewallet.watch.extension", "io.bluewallet.bluewallet.Stickers", "io.bluewallet.bluewallet.MarketWidget"]
|
||||
end
|
||||
|
||||
default_platform(:android)
|
||||
project_root = File.expand_path("..", __dir__)
|
||||
|
||||
platform :android do
|
||||
|
||||
desc "Prepare the keystore file"
|
||||
lane :prepare_keystore do
|
||||
Dir.chdir(project_root) do
|
||||
keystore_file_hex = ENV['KEYSTORE_FILE_HEX']
|
||||
UI.user_error!("KEYSTORE_FILE_HEX environment variable is missing") if keystore_file_hex.nil?
|
||||
|
||||
Dir.chdir("android") do
|
||||
UI.message("Creating keystore hex file...")
|
||||
|
||||
File.write("bluewallet-release-key.keystore.hex", keystore_file_hex)
|
||||
|
||||
sh("xxd -plain -revert bluewallet-release-key.keystore.hex > bluewallet-release-key.keystore") do |status|
|
||||
UI.user_error!("Error reverting hex to keystore") unless status.success?
|
||||
end
|
||||
UI.message("Keystore created successfully.")
|
||||
|
||||
File.delete("bluewallet-release-key.keystore.hex")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
desc "Update version code, build, and sign the APK"
|
||||
lane :update_version_build_and_sign_apk do
|
||||
Dir.chdir(project_root) do
|
||||
build_number = ENV['BUILD_NUMBER']
|
||||
UI.user_error!("BUILD_NUMBER environment variable is missing") if build_number.nil?
|
||||
|
||||
# Get the version name from build.gradle
|
||||
version_name = sh("grep versionName android/app/build.gradle | awk '{print $2}' | tr -d '\"'").strip
|
||||
|
||||
# Get the branch name
|
||||
branch_name = ENV['GITHUB_HEAD_REF'] || `git rev-parse --abbrev-ref HEAD`.strip.gsub('/', '-')
|
||||
|
||||
# Append branch name only if it's not 'master'
|
||||
if branch_name != 'master'
|
||||
signed_apk_name = "BlueWallet-#{version_name}-#{build_number}-#{branch_name}.apk"
|
||||
else
|
||||
signed_apk_name = "BlueWallet-#{version_name}-#{build_number}-.apk"
|
||||
end
|
||||
|
||||
Dir.chdir("android") do
|
||||
UI.message("Updating version code in build.gradle...")
|
||||
gradle(
|
||||
task: "assembleRelease",
|
||||
properties: { "versionCode" => build_number },
|
||||
project_dir: "android"
|
||||
)
|
||||
UI.message("Version code updated to #{build_number} and APK build completed.")
|
||||
|
||||
# Define the output paths
|
||||
unsigned_apk_path = "app/build/outputs/apk/release/app-release-unsigned.apk"
|
||||
signed_apk_path = "app/build/outputs/apk/release/#{signed_apk_name}"
|
||||
|
||||
# Rename the unsigned APK to include the version and build number
|
||||
if File.exist?(unsigned_apk_path)
|
||||
UI.message("Renaming APK to #{signed_apk_name}...")
|
||||
FileUtils.mv(unsigned_apk_path, signed_apk_path)
|
||||
ENV['APK_OUTPUT_PATH'] = File.expand_path(signed_apk_path)
|
||||
else
|
||||
UI.error("Unsigned APK not found at path: #{unsigned_apk_path}")
|
||||
next
|
||||
end
|
||||
|
||||
# Sign the APK using apksigner directly since we don't have an alias
|
||||
UI.message("Signing APK with apksigner...")
|
||||
apksigner_path = "#{ENV['ANDROID_HOME']}/build-tools/34.0.0/apksigner"
|
||||
sh("#{apksigner_path} sign --ks ./bluewallet-release-key.keystore --ks-pass=pass:#{ENV['KEYSTORE_PASSWORD']} #{signed_apk_path}")
|
||||
UI.message("APK signed successfully: #{signed_apk_path}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
desc "Upload APK to BrowserStack and post result as PR comment"
|
||||
lane :upload_to_browserstack_and_comment do
|
||||
Dir.chdir(project_root) do
|
||||
# Fetch the APK path from environment variables
|
||||
apk_path = ENV['APK_PATH']
|
||||
|
||||
# Attempt to find the APK if not provided
|
||||
if apk_path.nil? || apk_path.empty?
|
||||
UI.message("No APK path provided, attempting to find the artifact...")
|
||||
apk_path = `find ./ -name "*.apk"`.strip
|
||||
UI.user_error!("No APK file found") if apk_path.nil? || apk_path.empty?
|
||||
end
|
||||
|
||||
UI.message("Uploading APK to BrowserStack: #{apk_path}...")
|
||||
upload_to_browserstack_app_live(
|
||||
file_path: apk_path,
|
||||
browserstack_username: ENV['BROWSERSTACK_USERNAME'],
|
||||
browserstack_access_key: ENV['BROWSERSTACK_ACCESS_KEY']
|
||||
)
|
||||
|
||||
# Extract the BrowserStack URL from the output
|
||||
app_url = ENV['BROWSERSTACK_LIVE_APP_ID']
|
||||
UI.user_error!("BrowserStack upload failed, no app URL returned") if app_url.nil? || app_url.empty?
|
||||
|
||||
# Prepare necessary values for the PR comment
|
||||
apk_filename = File.basename(apk_path)
|
||||
browserstack_hashed_id = app_url.gsub('bs://', '')
|
||||
pr_number = ENV['GITHUB_PR_NUMBER']
|
||||
|
||||
comment = <<~COMMENT
|
||||
### APK Successfully Uploaded to BrowserStack
|
||||
|
||||
You can test it on the following devices:
|
||||
|
||||
- [Google Pixel 5 (Android 12.0)](https://app-live.browserstack.com/dashboard#os=android&os_version=12.0&device=Google+Pixel+5&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true)
|
||||
- [Google Pixel 7 (Android 13.0)](https://app-live.browserstack.com/dashboard#os=android&os_version=13.0&device=Google+Pixel+7&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true)
|
||||
- [Google Pixel 8 (Android 14.0)](https://app-live.browserstack.com/dashboard#os=android&os_version=14.0&device=Google+Pixel+8&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true)
|
||||
- [Samsung Galaxy Z Fold 5 (Android 13.0)](https://app-live.browserstack.com/dashboard#os=android&os_version=13.0&device=Samsung+Galaxy+Z+Fold+5&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true)
|
||||
- [Samsung Galaxy Z Fold 6 (Android 14.0)](https://app-live.browserstack.com/dashboard#os=android&os_version=14.0&device=Samsung+Galaxy+Z+Fold+6&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true)
|
||||
- [Samsung Galaxy Tab S9 (Android 13.0)](https://app-live.browserstack.com/dashboard#os=android&os_version=13.0&device=Samsung+Galaxy+Tab+S9&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true)
|
||||
|
||||
**Filename**: #{apk_filename}
|
||||
**BrowserStack App URL**: #{app_url}
|
||||
COMMENT
|
||||
|
||||
if pr_number
|
||||
begin
|
||||
sh("GH_TOKEN=#{ENV['GH_TOKEN']} gh pr comment #{pr_number} --body '#{comment}'")
|
||||
UI.success("Posted comment to PR ##{pr_number}")
|
||||
rescue => e
|
||||
UI.error("Failed to post comment to PR: #{e.message}")
|
||||
end
|
||||
else
|
||||
UI.important("No PR number found. Skipping PR comment.")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
platform :ios do
|
||||
|
||||
before_all do |lane, options|
|
||||
|
@ -136,7 +274,7 @@ platform :ios do
|
|||
|
||||
# Set the new build number
|
||||
increment_build_number(
|
||||
xcodeproj: "BlueWallet.xcodeproj",
|
||||
xcodeproj: "ios/BlueWallet.xcodeproj",
|
||||
build_number: ENV["NEW_BUILD_NUMBER"]
|
||||
)
|
||||
|
||||
|
@ -146,7 +284,7 @@ platform :ios do
|
|||
desc "Install CocoaPods dependencies"
|
||||
lane :install_pods do
|
||||
UI.message("Installing CocoaPods dependencies...")
|
||||
cocoapods
|
||||
cocoapods(podfile: "ios/Podfile")
|
||||
end
|
||||
|
||||
desc "Build the application"
|
||||
|
@ -154,7 +292,7 @@ platform :ios do
|
|||
UI.message("Building the application...")
|
||||
build_app(
|
||||
scheme: "BlueWallet",
|
||||
workspace: "BlueWallet.xcworkspace",
|
||||
workspace: "ios/BlueWallet.xcworkspace",
|
||||
export_method: "app-store",
|
||||
include_bitcode: false,
|
||||
configuration: "Release",
|
||||
|
@ -309,7 +447,7 @@ lane :update_release_notes do |options|
|
|||
'it' => release_notes_text, # Italian
|
||||
'ja' => release_notes_text, # Japanese
|
||||
'ms' => release_notes_text, # Malay
|
||||
'nb-NO' => release_notes_text, # Norwegian
|
||||
'nb' => release_notes_text, # Norwegian
|
||||
'pl' => release_notes_text, # Polish
|
||||
'pt-BR' => release_notes_text, # Portuguese (Brazil)
|
||||
'pt-PT' => release_notes_text, # Portuguese (Portugal)
|
||||
|
@ -346,4 +484,4 @@ lane :update_release_notes do |options|
|
|||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
5
fastlane/Pluginfile
Normal file
5
fastlane/Pluginfile
Normal file
|
@ -0,0 +1,5 @@
|
|||
# Autogenerated by fastlane
|
||||
#
|
||||
# Ensure this file is checked in to source control!
|
||||
|
||||
gem 'fastlane-plugin-browserstack'
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue