Merge branch 'master' into inputacc

This commit is contained in:
Marcos Rodriguez Velez 2024-09-08 12:19:37 -04:00
commit 7b32f5b1d7
281 changed files with 759 additions and 331 deletions

View file

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

View file

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

View file

@ -6,3 +6,5 @@ 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"
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
eval_gemfile(plugins_path) if File.exist?(plugins_path)

View file

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

View file

@ -264,6 +264,7 @@ class AmountInput extends Component {
{amount !== BitcoinUnit.MAX ? (
<TextInput
{...this.props}
caretHidden
testID="BitcoinAmountInput"
keyboardType="numeric"
adjustsFontSizeToFit

View file

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

View file

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

View file

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

View file

@ -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,9 +374,7 @@ const WalletsCarousel = forwardRef<FlatListRefType, WalletsCarouselProps>((props
const flatListRef = useRef<FlatList<any>>(null);
useImperativeHandle(
ref,
(): any => {
useImperativeHandle(ref, (): any => {
return {
scrollToEnd: (params: { animated?: boolean | null | undefined } | undefined) => flatListRef.current?.scrollToEnd(params),
scrollToIndex: (params: {
@ -396,9 +394,7 @@ const WalletsCarousel = forwardRef<FlatListRefType, WalletsCarouselProps>((props
flashScrollIndicators: () => flatListRef.current?.flashScrollIndicators(),
getNativeScrollRef: () => flatListRef.current?.getNativeScrollRef(),
};
},
[],
);
}, []);
const onScrollToIndexFailed = (error: { averageItemLength: number; index: number }): void => {
console.debug('onScrollToIndexFailed');

View file

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

5
fastlane/Pluginfile Normal file
View file

@ -0,0 +1,5 @@
# Autogenerated by fastlane
#
# Ensure this file is checked in to source control!
gem 'fastlane-plugin-browserstack'

View file

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