App polish for 2.9 rd1 (#1359)

* add useSafeBottomPadding

* add bottom padding to dev screen

* use safe bottom padding

* skip uploading if building android bundle locally

* fix tests

* cache fix script

* clean up country picker, fix font color

* sort package jsons, add watcher for mobile sdk

* formatting

* only bump versions for successfull builds

* move all css

* cleaner script

* kill watchers before starting new one
This commit is contained in:
Justin Hernandez
2025-11-07 10:26:08 -08:00
committed by GitHub
parent 394668e5df
commit ccb9e148be
24 changed files with 240 additions and 158 deletions

View File

@@ -52,41 +52,40 @@ secrets-ignore:
- "RSA Private Key" # For mock RSA keys - "RSA Private Key" # For mock RSA keys
- "EC Private Key" # For mock EC keys - "EC Private Key" # For mock EC keys
secret: secret:
ignored_matches: ignored_matches:
- match: 2036b4e50ad3042969b290e354d9864465107a14de6f5a36d49f81ea8290def8 - match: 2036b4e50ad3042969b290e354d9864465107a14de6f5a36d49f81ea8290def8
name: prebuilt-ios-arm64-apple-ios.private.swiftinterface name: prebuilt-ios-arm64-apple-ios.private.swiftinterface
ignored_paths: ignored_paths:
- '**/*.swiftinterface' - "**/*.swiftinterface"
- '**/*.xcframework/**' - "**/*.xcframework/**"
- '**/packages/mobile-sdk-alpha/ios/Frameworks/**' - "**/packages/mobile-sdk-alpha/ios/Frameworks/**"
- '**/OpenSSL.xcframework/**' - "**/OpenSSL.xcframework/**"
- '**/demo-app/**/mock/**' - "**/demo-app/**/mock/**"
- common/src/mock_certificates/aadhaar/mockAadhaarCert.ts - common/src/mock_certificates/aadhaar/mockAadhaarCert.ts
- '**/NFCPassportReader.xcframework/**' - "**/NFCPassportReader.xcframework/**"
- common/src/utils/passports/genMockIdDoc.ts - common/src/utils/passports/genMockIdDoc.ts
- '**/tests/**/*.crt' - "**/tests/**/*.crt"
- '**/mock_certificates/**/*.crt' - "**/mock_certificates/**/*.crt"
- '**/mock_certificates/**/*.key' - "**/mock_certificates/**/*.key"
- '**/demo-app/**/test-data/**' - "**/demo-app/**/test-data/**"
- '**/generated/**/*.key' - "**/generated/**/*.key"
- '**/SelfSDK.xcframework/**' - "**/SelfSDK.xcframework/**"
- '**/mock/**/*.crt' - "**/mock/**/*.crt"
- '**/generated/**/*.crt' - "**/generated/**/*.crt"
- '**/test/**/*.key' - "**/test/**/*.key"
- '**/mock/**/*.key' - "**/mock/**/*.key"
- '**/test/**/*.crt' - "**/test/**/*.crt"
- '**/test/**/*.pem' - "**/test/**/*.pem"
- '**/constants/mockCertificates.ts' - "**/constants/mockCertificates.ts"
- '**/mock/**/*.pem' - "**/mock/**/*.pem"
- '**/mock_certificates/**/*.pem' - "**/mock_certificates/**/*.pem"
- '**/mock-data/**' - "**/mock-data/**"
- '**/packages/mobile-sdk-alpha/ios/SelfSDK/**' - "**/packages/mobile-sdk-alpha/ios/SelfSDK/**"
- '**/tests/**/*.key' - "**/tests/**/*.key"
- '**/generated/**/*.pem' - "**/generated/**/*.pem"
- '**/tests/**/*.pem' - "**/tests/**/*.pem"
- '**/test-data/**' - "**/test-data/**"
- common/src/mock_certificates/** - common/src/mock_certificates/**
- '**/*.xcframework' - "**/*.xcframework"
version: 2 version: 2

View File

@@ -76,7 +76,7 @@ jobs:
with: with:
cache-version: ${{ env.GH_CACHE_VERSION }}-${{ env.NODE_VERSION_SANITIZED }} cache-version: ${{ env.GH_CACHE_VERSION }}-${{ env.NODE_VERSION_SANITIZED }}
- name: Build dependencies (cache miss) - name: Build dependencies (cache miss)
# Temporarily disabled due to `yarn types` failures when cache is used. # Temporarily disabled due to `yarn types` failures when cache is used.
# if: steps.built-deps.outputs.cache-hit != 'true' # if: steps.built-deps.outputs.cache-hit != 'true'
run: | run: |
echo "Cache miss for built dependencies. Building now..." echo "Cache miss for built dependencies. Building now..."

View File

@@ -1276,17 +1276,21 @@ jobs:
const version = JSON.parse(fs.readFileSync('version.json', 'utf8')); const version = JSON.parse(fs.readFileSync('version.json', 'utf8'));
const timestamp = new Date().toISOString(); const timestamp = new Date().toISOString();
version.ios.build = $IOS_BUILD; // Only bump build numbers for successful builds
version.android.build = $ANDROID_BUILD;
// Update lastDeployed timestamp for successful builds
if ('$IOS_SUCCESS' === 'success') { if ('$IOS_SUCCESS' === 'success') {
version.ios.build = $IOS_BUILD;
version.ios.lastDeployed = timestamp; version.ios.lastDeployed = timestamp;
console.log('✅ Updated iOS lastDeployed timestamp'); console.log('✅ Updated iOS build number to $IOS_BUILD and lastDeployed timestamp');
} else {
console.log('⏭️ Skipped iOS build number bump (build did not succeed)');
} }
if ('$ANDROID_SUCCESS' === 'success') { if ('$ANDROID_SUCCESS' === 'success') {
version.android.build = $ANDROID_BUILD;
version.android.lastDeployed = timestamp; version.android.lastDeployed = timestamp;
console.log('✅ Updated Android lastDeployed timestamp'); console.log('✅ Updated Android build number to $ANDROID_BUILD and lastDeployed timestamp');
} else {
console.log('⏭️ Skipped Android build number bump (build did not succeed)');
} }
fs.writeFileSync('version.json', JSON.stringify(version, null, 2) + '\n'); fs.writeFileSync('version.json', JSON.stringify(version, null, 2) + '\n');

View File

@@ -319,7 +319,8 @@ jobs:
echo "::add-mask::${SELFXYZ_INTERNAL_REPO_PAT}" echo "::add-mask::${SELFXYZ_INTERNAL_REPO_PAT}"
fi fi
cd packages/mobile-sdk-demo/ios cd packages/mobile-sdk-demo/ios
pod install echo "📦 Installing pods via cache-fix script…"
bash scripts/pod-install-with-cache-fix.sh
env: env:
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }} SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
GIT_TERMINAL_PROMPT: 0 GIT_TERMINAL_PROMPT: 0

View File

@@ -8,7 +8,7 @@ on:
- "sdk/core/package.json" - "sdk/core/package.json"
- "sdk/qrcode/package.json" - "sdk/qrcode/package.json"
- "common/package.json" - "common/package.json"
- 'packages/mobile-sdk-alpha/package.json' - "packages/mobile-sdk-alpha/package.json"
- "sdk/qrcode-angular/package.json" - "sdk/qrcode-angular/package.json"
- "contracts/package.json" - "contracts/package.json"
workflow_dispatch: workflow_dispatch:

View File

@@ -25,7 +25,7 @@ GEM
artifactory (3.0.17) artifactory (3.0.17)
atomos (0.1.3) atomos (0.1.3)
aws-eventstream (1.4.0) aws-eventstream (1.4.0)
aws-partitions (1.1179.0) aws-partitions (1.1181.0)
aws-sdk-core (3.236.0) aws-sdk-core (3.236.0)
aws-eventstream (~> 1, >= 1.3.0) aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0) aws-partitions (~> 1, >= 1.992.0)
@@ -37,7 +37,7 @@ GEM
aws-sdk-kms (1.116.0) aws-sdk-kms (1.116.0)
aws-sdk-core (~> 3, >= 3.234.0) aws-sdk-core (~> 3, >= 3.234.0)
aws-sigv4 (~> 1.5) aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.202.0) aws-sdk-s3 (1.203.0)
aws-sdk-core (~> 3, >= 3.234.0) aws-sdk-core (~> 3, >= 3.234.0)
aws-sdk-kms (~> 1) aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5) aws-sigv4 (~> 1.5)

View File

@@ -306,6 +306,14 @@ platform :android do
skip_upload = options[:skip_upload] == true || options[:skip_upload] == "true" skip_upload = options[:skip_upload] == true || options[:skip_upload] == "true"
version_bump = options[:version_bump] || "build" version_bump = options[:version_bump] || "build"
# Automatically skip uploads in local development (no local permissions for Play Store)
# Uploads must be done by CI/CD machines with proper authentication
if local_development && !skip_upload
skip_upload = true
UI.important("🏠 LOCAL DEVELOPMENT: Automatically skipping Play Store upload")
UI.important(" Uploads require CI/CD machine permissions and will be handled automatically")
end
if local_development if local_development
if ENV["ANDROID_KEYSTORE_PATH"].nil? if ENV["ANDROID_KEYSTORE_PATH"].nil?
ENV["ANDROID_KEYSTORE_PATH"] = Fastlane::Helpers.android_create_keystore(android_keystore_path) ENV["ANDROID_KEYSTORE_PATH"] = Fastlane::Helpers.android_create_keystore(android_keystore_path)

View File

@@ -30,6 +30,8 @@ module.exports = {
'<rootDir>/../packages/mobile-sdk-alpha/dist/cjs/index.cjs', '<rootDir>/../packages/mobile-sdk-alpha/dist/cjs/index.cjs',
'^@selfxyz/mobile-sdk-alpha/components$': '^@selfxyz/mobile-sdk-alpha/components$':
'<rootDir>/../packages/mobile-sdk-alpha/dist/cjs/components/index.cjs', '<rootDir>/../packages/mobile-sdk-alpha/dist/cjs/components/index.cjs',
'^@selfxyz/mobile-sdk-alpha/hooks$':
'<rootDir>/../packages/mobile-sdk-alpha/dist/cjs/hooks/index.cjs',
'^@selfxyz/mobile-sdk-alpha/onboarding/(.*)$': '^@selfxyz/mobile-sdk-alpha/onboarding/(.*)$':
'<rootDir>/../packages/mobile-sdk-alpha/dist/cjs/flows/onboarding/$1.cjs', '<rootDir>/../packages/mobile-sdk-alpha/dist/cjs/flows/onboarding/$1.cjs',
'^@selfxyz/mobile-sdk-alpha/disclosing/(.*)$': '^@selfxyz/mobile-sdk-alpha/disclosing/(.*)$':

View File

@@ -50,8 +50,8 @@
"release:patch": "./scripts/release.sh patch", "release:patch": "./scripts/release.sh patch",
"setup": "yarn clean:build && yarn install && yarn build:deps && yarn setup:android-deps && cd ios && bundle install && bundle exec pod install --repo-update && cd .. && yarn clean:xcode-env-local", "setup": "yarn clean:build && yarn install && yarn build:deps && yarn setup:android-deps && cd ios && bundle install && bundle exec pod install --repo-update && cd .. && yarn clean:xcode-env-local",
"setup:android-deps": "node scripts/setup-private-modules.cjs", "setup:android-deps": "node scripts/setup-private-modules.cjs",
"start": "watchman watch-del-all && react-native start", "start": "watchman watch-del-all && yarn watch:sdk & react-native start",
"start:clean": "watchman watch-del-all && cd android && ./gradlew clean && cd .. && react-native start --reset-cache", "start:clean": "watchman watch-del-all && cd android && ./gradlew clean && cd .. && yarn watch:sdk & react-native start --reset-cache",
"sync-versions": "bundle exec fastlane ios sync_version && bundle exec fastlane android sync_version", "sync-versions": "bundle exec fastlane ios sync_version && bundle exec fastlane android sync_version",
"tag:release": "node scripts/tag.cjs release", "tag:release": "node scripts/tag.cjs release",
"tag:remove": "node scripts/tag.cjs remove", "tag:remove": "node scripts/tag.cjs remove",
@@ -66,6 +66,7 @@
"test:tree-shaking": "node ./scripts/test-tree-shaking.cjs", "test:tree-shaking": "node ./scripts/test-tree-shaking.cjs",
"test:web-build": "jest tests/web-build-render.test.ts --testTimeout=180000", "test:web-build": "jest tests/web-build-render.test.ts --testTimeout=180000",
"types": "tsc --noEmit", "types": "tsc --noEmit",
"watch:sdk": "yarn workspace @selfxyz/mobile-sdk-alpha watch",
"web": "vite", "web": "vite",
"web:build": "yarn build:deps && vite build", "web:build": "yarn build:deps && vite build",
"web:preview": "vite preview" "web:preview": "vite preview"

View File

@@ -17,6 +17,8 @@ import { useNavigation } from '@react-navigation/native';
import type { NativeStackScreenProps } from '@react-navigation/native-stack'; import type { NativeStackScreenProps } from '@react-navigation/native-stack';
import { Check, ChevronDown, ChevronRight } from '@tamagui/lucide-icons'; import { Check, ChevronDown, ChevronRight } from '@tamagui/lucide-icons';
import { useSafeBottomPadding } from '@selfxyz/mobile-sdk-alpha/hooks';
import BugIcon from '@/images/icons/bug_icon.svg'; import BugIcon from '@/images/icons/bug_icon.svg';
import IdIcon from '@/images/icons/id_icon.svg'; import IdIcon from '@/images/icons/id_icon.svg';
import WarningIcon from '@/images/icons/warning.svg'; import WarningIcon from '@/images/icons/warning.svg';
@@ -295,6 +297,7 @@ const DevSettingsScreen: React.FC<DevSettingsScreenProps> = ({}) => {
const subscribedTopics = useSettingStore(state => state.subscribedTopics); const subscribedTopics = useSettingStore(state => state.subscribedTopics);
const [hasNotificationPermission, setHasNotificationPermission] = const [hasNotificationPermission, setHasNotificationPermission] =
useState(false); useState(false);
const paddingBottom = useSafeBottomPadding(20);
// Check notification permissions on mount // Check notification permissions on mount
useEffect(() => { useEffect(() => {
@@ -466,6 +469,7 @@ const DevSettingsScreen: React.FC<DevSettingsScreenProps> = ({}) => {
flex={1} flex={1}
paddingHorizontal="$4" paddingHorizontal="$4"
paddingTop="$4" paddingTop="$4"
paddingBottom={paddingBottom}
> >
<ParameterSection <ParameterSection
icon={<IdIcon />} icon={<IdIcon />}

View File

@@ -14,10 +14,10 @@ import {
SecondaryButton, SecondaryButton,
} from '@selfxyz/mobile-sdk-alpha/components'; } from '@selfxyz/mobile-sdk-alpha/components';
import { AadhaarEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics'; import { AadhaarEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics';
import { useSafeBottomPadding } from '@selfxyz/mobile-sdk-alpha/hooks';
import { getErrorMessages } from '@selfxyz/mobile-sdk-alpha/onboarding/import-aadhaar'; import { getErrorMessages } from '@selfxyz/mobile-sdk-alpha/onboarding/import-aadhaar';
import WarningIcon from '@/images/warning.svg'; import WarningIcon from '@/images/warning.svg';
import { useSafeAreaInsets } from '@/mocks/react-native-safe-area-context';
import { black, slate100, slate200, slate500, white } from '@/utils/colors'; import { black, slate100, slate200, slate500, white } from '@/utils/colors';
import { extraYPadding } from '@/utils/constants'; import { extraYPadding } from '@/utils/constants';
@@ -31,7 +31,7 @@ type AadhaarUploadErrorRoute = RouteProp<
>; >;
const AadhaarUploadErrorScreen: React.FC = () => { const AadhaarUploadErrorScreen: React.FC = () => {
const { bottom } = useSafeAreaInsets(); const paddingBottom = useSafeBottomPadding(extraYPadding + 35);
const navigation = useNavigation(); const navigation = useNavigation();
const route = useRoute<AadhaarUploadErrorRoute>(); const route = useRoute<AadhaarUploadErrorRoute>();
const { trackEvent } = useSelfClient(); const { trackEvent } = useSelfClient();
@@ -78,7 +78,7 @@ const AadhaarUploadErrorScreen: React.FC = () => {
<YStack <YStack
paddingHorizontal={25} paddingHorizontal={25}
backgroundColor={white} backgroundColor={white}
paddingBottom={bottom + extraYPadding + 35} paddingBottom={paddingBottom}
paddingTop={25} paddingTop={25}
> >
<XStack gap="$3" alignItems="stretch"> <XStack gap="$3" alignItems="stretch">

View File

@@ -12,11 +12,11 @@ import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { useSelfClient } from '@selfxyz/mobile-sdk-alpha'; import { useSelfClient } from '@selfxyz/mobile-sdk-alpha';
import { BodyText, PrimaryButton } from '@selfxyz/mobile-sdk-alpha/components'; import { BodyText, PrimaryButton } from '@selfxyz/mobile-sdk-alpha/components';
import { AadhaarEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics'; import { AadhaarEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics';
import { useSafeBottomPadding } from '@selfxyz/mobile-sdk-alpha/hooks';
import { useAadhaar } from '@selfxyz/mobile-sdk-alpha/onboarding/import-aadhaar'; import { useAadhaar } from '@selfxyz/mobile-sdk-alpha/onboarding/import-aadhaar';
import { useModal } from '@/hooks/useModal'; import { useModal } from '@/hooks/useModal';
import AadhaarImage from '@/images/512w.png'; import AadhaarImage from '@/images/512w.png';
import { useSafeAreaInsets } from '@/mocks/react-native-safe-area-context';
import type { RootStackParamList } from '@/navigation'; import type { RootStackParamList } from '@/navigation';
import { slate100, slate200, slate400, slate500, white } from '@/utils/colors'; import { slate100, slate200, slate400, slate500, white } from '@/utils/colors';
import { extraYPadding } from '@/utils/constants'; import { extraYPadding } from '@/utils/constants';
@@ -26,7 +26,7 @@ import {
} from '@/utils/qrScanner'; } from '@/utils/qrScanner';
const AadhaarUploadScreen: React.FC = () => { const AadhaarUploadScreen: React.FC = () => {
const { bottom } = useSafeAreaInsets(); const paddingBottom = useSafeBottomPadding(extraYPadding + 50);
const navigation = const navigation =
useNavigation<NativeStackNavigationProp<RootStackParamList>>(); useNavigation<NativeStackNavigationProp<RootStackParamList>>();
@@ -140,11 +140,7 @@ const AadhaarUploadScreen: React.FC = () => {
]); ]);
return ( return (
<YStack <YStack flex={1} backgroundColor={slate100} paddingBottom={paddingBottom}>
flex={1}
backgroundColor={slate100}
paddingBottom={bottom + extraYPadding + 50}
>
<YStack flex={1} paddingHorizontal={20} paddingTop={20}> <YStack flex={1} paddingHorizontal={20} paddingTop={20}>
<YStack <YStack
flex={1} flex={1}

View File

@@ -10,15 +10,15 @@ import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { useSelfClient } from '@selfxyz/mobile-sdk-alpha'; import { useSelfClient } from '@selfxyz/mobile-sdk-alpha';
import { BodyText, PrimaryButton } from '@selfxyz/mobile-sdk-alpha/components'; import { BodyText, PrimaryButton } from '@selfxyz/mobile-sdk-alpha/components';
import { AadhaarEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics'; import { AadhaarEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics';
import { useSafeBottomPadding } from '@selfxyz/mobile-sdk-alpha/hooks';
import BlueCheckIcon from '@/images/blue_check.svg'; import BlueCheckIcon from '@/images/blue_check.svg';
import { useSafeAreaInsets } from '@/mocks/react-native-safe-area-context';
import type { RootStackParamList } from '@/navigation'; import type { RootStackParamList } from '@/navigation';
import { black, slate100, slate200, slate500, white } from '@/utils/colors'; import { black, slate100, slate200, slate500, white } from '@/utils/colors';
import { extraYPadding } from '@/utils/constants'; import { extraYPadding } from '@/utils/constants';
const AadhaarUploadedSuccessScreen: React.FC = () => { const AadhaarUploadedSuccessScreen: React.FC = () => {
const { bottom } = useSafeAreaInsets(); const paddingBottom = useSafeBottomPadding(extraYPadding + 35);
const navigation = const navigation =
useNavigation<NativeStackNavigationProp<RootStackParamList>>(); useNavigation<NativeStackNavigationProp<RootStackParamList>>();
const { trackEvent } = useSelfClient(); const { trackEvent } = useSelfClient();
@@ -62,7 +62,7 @@ const AadhaarUploadedSuccessScreen: React.FC = () => {
<YStack <YStack
paddingHorizontal={25} paddingHorizontal={25}
backgroundColor={white} backgroundColor={white}
paddingBottom={bottom + extraYPadding + 35} paddingBottom={paddingBottom}
paddingTop={25} paddingTop={25}
> >
<PrimaryButton <PrimaryButton

View File

@@ -110,10 +110,10 @@
], ],
"scripts": { "scripts": {
"build": "yarn build:android && yarn build:ios && tsup && yarn postbuild", "build": "yarn build:android && yarn build:ios && tsup && yarn postbuild",
"build:ts-only": "tsup && yarn postbuild", "postbuild": "node ./scripts/postBuild.mjs",
"build:android": "sh ./scripts/build-android.sh", "build:android": "sh ./scripts/build-android.sh",
"build:ios": "sh ./scripts/build-ios.sh", "build:ios": "sh ./scripts/build-ios.sh",
"postbuild": "node ./scripts/postBuild.mjs", "build:ts-only": "tsup && yarn postbuild",
"fmt": "prettier --check .", "fmt": "prettier --check .",
"fmt:fix": "prettier --write .", "fmt:fix": "prettier --write .",
"format": "sh -c 'if [ -z \"$SKIP_BUILD_DEPS\" ]; then yarn nice; else yarn fmt:fix; fi'", "format": "sh -c 'if [ -z \"$SKIP_BUILD_DEPS\" ]; then yarn nice; else yarn fmt:fix; fi'",
@@ -127,7 +127,8 @@
"typecheck": "tsc -p tsconfig.json --noEmit", "typecheck": "tsc -p tsconfig.json --noEmit",
"types": "tsc -p tsconfig.json --noEmit", "types": "tsc -p tsconfig.json --noEmit",
"validate:exports": "node ./scripts/validate-exports.mjs", "validate:exports": "node ./scripts/validate-exports.mjs",
"validate:pkg": "node ./scripts/verify-conditions.mjs" "validate:pkg": "node ./scripts/verify-conditions.mjs",
"watch": "pkill -f 'tsup.*--watch' 2>/dev/null || true && tsup && yarn postbuild && tsup --watch"
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.28.3", "@babel/runtime": "^7.28.3",

View File

@@ -45,9 +45,9 @@ export function useCountries() {
if (result.status === 'success') { if (result.status === 'success') {
setCountryData(result.data); setCountryData(result.data);
if (__DEV__) { // if (__DEV__) {
console.log('Set country data:', result.data); // console.log('Set country data:', result.data);
} // }
} else { } else {
console.error('API returned non-success status:', result.status); console.error('API returned non-success status:', result.status);
} }

View File

@@ -2,8 +2,8 @@
// SPDX-License-Identifier: BUSL-1.1 // SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. // NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
import { useCallback, useEffect, useMemo } from 'react'; import { useCallback, useEffect } from 'react';
import { Dimensions, StyleSheet } from 'react-native'; import { StyleSheet } from 'react-native';
import type { DocumentCategory } from '@selfxyz/common/utils/types'; import type { DocumentCategory } from '@selfxyz/common/utils/types';
@@ -17,6 +17,7 @@ import { black, white } from '../../constants/colors';
import { useSelfClient } from '../../context'; import { useSelfClient } from '../../context';
import { loadSelectedDocument } from '../../documents/utils'; import { loadSelectedDocument } from '../../documents/utils';
import { notificationSuccess } from '../../haptic'; import { notificationSuccess } from '../../haptic';
import { useSafeBottomPadding } from '../../hooks/useSafeBottomPadding';
import { ExpandableBottomLayout } from '../../layouts/ExpandableBottomLayout'; import { ExpandableBottomLayout } from '../../layouts/ExpandableBottomLayout';
import { SdkEvents } from '../../types/events'; import { SdkEvents } from '../../types/events';
import type { SelfClient } from '../../types/public'; import type { SelfClient } from '../../types/public';
@@ -38,17 +39,7 @@ export const ConfirmIdentificationScreen = ({ onBeforeConfirm }: { onBeforeConfi
await onConfirm(selfClient); await onConfirm(selfClient);
}, [onBeforeConfirm, selfClient]); }, [onBeforeConfirm, selfClient]);
// Calculate total bottom padding: base padding + fallback for smaller screens const paddingBottom = useSafeBottomPadding(20);
const paddingBottom = useMemo(() => {
const basePadding = 20;
// Estimate for smaller screens to account for safe areas
const windowHeight = Dimensions.get('window').height;
const isSmallScreen = windowHeight < 900;
const fallbackPadding = isSmallScreen ? 50 : 0;
return basePadding + fallbackPadding;
}, []);
return ( return (
<ExpandableBottomLayout.Layout backgroundColor={black}> <ExpandableBottomLayout.Layout backgroundColor={black}>

View File

@@ -3,7 +3,7 @@
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. // NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { ActivityIndicator, FlatList, TouchableOpacity, View } from 'react-native'; import { ActivityIndicator, FlatList, StyleSheet, TouchableOpacity, View } from 'react-native';
import { commonNames } from '@selfxyz/common/constants/countries'; import { commonNames } from '@selfxyz/common/constants/countries';
@@ -32,15 +32,10 @@ const CountryItem = memo<{
if (!countryName) return null; if (!countryName) return null;
return ( return (
<TouchableOpacity <TouchableOpacity onPress={() => onSelect(countryCode)} style={styles.countryItemContainer}>
onPress={() => onSelect(countryCode)} <XStack style={styles.countryItemContent}>
style={{
paddingVertical: 13,
}}
>
<XStack alignItems="center" gap={16}>
<RoundFlag countryCode={countryCode} size={FLAG_SIZE} /> <RoundFlag countryCode={countryCode} size={FLAG_SIZE} />
<BodyText style={{ fontSize: 16, color: black, flex: 1 }}>{countryName}</BodyText> <BodyText style={styles.countryItemText}>{countryName}</BodyText>
</XStack> </XStack>
</TouchableOpacity> </TouchableOpacity>
); );
@@ -49,7 +44,7 @@ const CountryItem = memo<{
CountryItem.displayName = 'CountryItem'; CountryItem.displayName = 'CountryItem';
const Loading = () => ( const Loading = () => (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> <View style={styles.loadingContainer}>
<ActivityIndicator size="small" /> <ActivityIndicator size="small" />
</View> </View>
); );
@@ -63,11 +58,11 @@ const CountryPickerScreen: React.FC = () => {
const onPressCountry = useCallback( const onPressCountry = useCallback(
(countryCode: string) => { (countryCode: string) => {
buttonTap(); buttonTap();
if (__DEV__) { // if (__DEV__) {
console.log('Selected country code:', countryCode); // console.log('Selected country code:', countryCode);
console.log('Current countryData:', countryData); // console.log('Current countryData:', countryData);
console.log('Available country codes:', Object.keys(countryData)); // console.log('Available country codes:', Object.keys(countryData));
} // }
const documentTypes = countryData[countryCode]; const documentTypes = countryData[countryCode];
if (__DEV__) { if (__DEV__) {
console.log('documentTypes for', countryCode, ':', documentTypes); console.log('documentTypes for', countryCode, ':', documentTypes);
@@ -111,8 +106,8 @@ const CountryPickerScreen: React.FC = () => {
return ( return (
<YStack flex={1} paddingTop="$4" paddingHorizontal="$4" backgroundColor={slate100}> <YStack flex={1} paddingTop="$4" paddingHorizontal="$4" backgroundColor={slate100}>
<YStack marginTop="$4" marginBottom="$6"> <YStack marginTop="$4" marginBottom="$6">
<BodyText style={{ fontSize: 29, fontFamily: advercase }}>Select the country that issued your ID</BodyText> <BodyText style={styles.titleText}>Select the country that issued your ID</BodyText>
<BodyText style={{ fontSize: 16, color: slate500, marginTop: 20 }}> <BodyText style={styles.subtitleText}>
Self has support for over 300 ID types. You can select the type of ID in the next step Self has support for over 300 ID types. You can select the type of ID in the next step
</BodyText> </BodyText>
</YStack> </YStack>
@@ -122,32 +117,12 @@ const CountryPickerScreen: React.FC = () => {
<YStack flex={1}> <YStack flex={1}>
{showSuggestion && ( {showSuggestion && (
<YStack marginBottom="$2"> <YStack marginBottom="$2">
<BodyText <BodyText style={styles.sectionLabel}>SUGGESTION</BodyText>
style={{
fontSize: 16,
color: black,
fontFamily: dinot,
letterSpacing: 0.8,
marginBottom: 8,
}}
>
SUGGESTION
</BodyText>
<CountryItem <CountryItem
countryCode={userCountryCode as string /*safe due to showSuggestion*/} countryCode={userCountryCode as string /*safe due to showSuggestion*/}
onSelect={onPressCountry} onSelect={onPressCountry}
/> />
<BodyText <BodyText style={styles.sectionLabelBottom}>SELECT AN ISSUING COUNTRY</BodyText>
style={{
fontSize: 16,
color: black,
fontFamily: dinot,
letterSpacing: 0.8,
marginTop: 20,
}}
>
SELECT AN ISSUING COUNTRY
</BodyText>
</YStack> </YStack>
)} )}
<FlatList <FlatList
@@ -169,4 +144,48 @@ const CountryPickerScreen: React.FC = () => {
}; };
CountryPickerScreen.displayName = 'CountryPickerScreen'; CountryPickerScreen.displayName = 'CountryPickerScreen';
const styles = StyleSheet.create({
countryItemContainer: {
paddingVertical: 13,
},
countryItemContent: {
alignItems: 'center',
gap: 16,
},
countryItemText: {
fontSize: 16,
color: black,
flex: 1,
},
loadingContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
titleText: {
fontSize: 29,
fontFamily: advercase,
color: black,
},
subtitleText: {
fontSize: 16,
color: slate500,
marginTop: 20,
},
sectionLabel: {
fontSize: 16,
color: black,
fontFamily: dinot,
letterSpacing: 0.8,
marginBottom: 8,
},
sectionLabelBottom: {
fontSize: 16,
color: black,
fontFamily: dinot,
letterSpacing: 0.8,
marginTop: 20,
},
});
export default CountryPickerScreen; export default CountryPickerScreen;

View File

@@ -3,6 +3,7 @@
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. // NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
import type React from 'react'; import type React from 'react';
import { StyleSheet } from 'react-native';
import AadhaarLogo from '../../../svgs/icons/aadhaar.svg'; import AadhaarLogo from '../../../svgs/icons/aadhaar.svg';
import EPassportLogoRounded from '../../../svgs/icons/epassport_rounded.svg'; import EPassportLogoRounded from '../../../svgs/icons/epassport_rounded.svg';
@@ -105,16 +106,7 @@ const IDSelectionScreen: React.FC<IDSelectionScreenProps> = props => {
<SelfLogo width={24} height={24} /> <SelfLogo width={24} height={24} />
</YStack> </YStack>
</XStack> </XStack>
<BodyText <BodyText style={styles.titleText}>Select an ID type</BodyText>
style={{
marginTop: 48,
fontSize: 29,
fontFamily: advercase,
textAlign: 'center',
}}
>
Select an ID type
</BodyText>
</YStack> </YStack>
<YStack gap="$3"> <YStack gap="$3">
{documentTypes.map((docType: string) => ( {documentTypes.map((docType: string) => (
@@ -135,35 +127,42 @@ const IDSelectionScreen: React.FC<IDSelectionScreenProps> = props => {
<XStack alignItems="center" gap={'$3'} flex={1}> <XStack alignItems="center" gap={'$3'} flex={1}>
{getDocumentLogo(docType)} {getDocumentLogo(docType)}
<YStack gap={'$1'}> <YStack gap={'$1'}>
<BodyText style={{ fontSize: 24, fontFamily: dinot, color: black }}> <BodyText style={styles.documentNameText}>{getDocumentName(docType)}</BodyText>
{getDocumentName(docType)} <BodyText style={styles.documentDescriptionText}>{getDocumentDescription(docType)}</BodyText>
</BodyText>
<BodyText
style={{
fontSize: 14,
fontFamily: dinot,
color: slate400,
}}
>
{getDocumentDescription(docType)}
</BodyText>
</YStack> </YStack>
</XStack> </XStack>
</XStack> </XStack>
))} ))}
<BodyText <BodyText style={styles.footerText}>Be sure your document is ready to scan</BodyText>
style={{
fontSize: 18,
fontFamily: dinot,
color: slate400,
textAlign: 'center',
}}
>
Be sure your document is ready to scan
</BodyText>
</YStack> </YStack>
</YStack> </YStack>
); );
}; };
const styles = StyleSheet.create({
titleText: {
marginTop: 48,
fontSize: 29,
fontFamily: advercase,
textAlign: 'center',
color: black,
},
documentNameText: {
fontSize: 24,
fontFamily: dinot,
color: black,
},
documentDescriptionText: {
fontSize: 14,
fontFamily: dinot,
color: slate400,
},
footerText: {
fontSize: 18,
fontFamily: dinot,
color: slate400,
textAlign: 'center',
},
});
export default IDSelectionScreen; export default IDSelectionScreen;

View File

@@ -0,0 +1,5 @@
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
export { useSafeBottomPadding } from './useSafeBottomPadding';

View File

@@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
import { useMemo } from 'react';
import { Dimensions } from 'react-native';
/**
* Custom hook to calculate bottom padding that prevents UI elements from bleeding
* into the system navigation area on smaller screens.
*
* This hook uses screen height detection to add extra padding for smaller screens
* (< 900px height) to account for system navigation bars and safe areas.
*
* @param basePadding - Base padding to add (default: 20)
* @returns Total bottom padding value
*
* @example
* ```tsx
* // For use with ExpandableBottomLayout.BottomSection
* const bottomPadding = useSafeBottomPadding(20);
* <ExpandableBottomLayout.BottomSection paddingBottom={bottomPadding} />
* ```
*/
export const useSafeBottomPadding = (basePadding: number = 20): number => {
const { height: windowHeight } = Dimensions.get('window');
return useMemo(() => {
const isSmallScreen = windowHeight < 900;
const fallbackPadding = isSmallScreen ? 50 : 0;
return basePadding + fallbackPadding;
}, [windowHeight, basePadding]);
};

View File

@@ -40,6 +40,8 @@ const entry = {
'constants/analytics': 'src/constants/analytics.ts', 'constants/analytics': 'src/constants/analytics.ts',
'constants/colors': 'src/constants/colors.ts', 'constants/colors': 'src/constants/colors.ts',
'components/index': 'src/components/index.ts', 'components/index': 'src/components/index.ts',
'hooks/index': 'src/hooks/index.ts',
'hooks/useSafeBottomPadding': 'src/hooks/useSafeBottomPadding.ts',
stores: 'src/stores/index.ts', stores: 'src/stores/index.ts',
...flowEntries, ...flowEntries,
}; };

View File

@@ -0,0 +1,22 @@
#!/bin/bash
# Pod install with hermes-engine cache fix for React Native upgrades
# This script handles CocoaPods cache mismatches that occur after React Native version upgrades
set -e # Exit on any error
echo "🧹 Clearing CocoaPods cache to prevent hermes-engine version conflicts..."
pod cache clean --all > /dev/null 2>&1 || true
rm -rf ~/Library/Caches/CocoaPods > /dev/null 2>&1 || true
echo "📦 Attempting pod install..."
if pod install; then
echo "✅ Pods installed successfully"
else
echo "⚠️ Pod install failed, likely due to hermes-engine cache mismatch after React Native upgrade"
echo "🔧 Running targeted fix: pod update hermes-engine..."
pod update hermes-engine --no-repo-update
echo "🔄 Retrying pod install..."
pod install
echo "✅ Pods installed successfully after cache fix"
fi

View File

@@ -23,9 +23,9 @@
"reinstall": "yarn clean && yarn install && yarn prebuild && cd ios && pod install && cd ..", "reinstall": "yarn clean && yarn install && yarn prebuild && cd ios && pod install && cd ..",
"start": "react-native start", "start": "react-native start",
"test": "vitest run", "test": "vitest run",
"test:e2e:android": "bash scripts/e2e-android-ci.sh",
"test:watch": "vitest", "test:watch": "vitest",
"types": "yarn prebuild && tsc --noEmit", "types": "yarn prebuild && tsc --noEmit"
"test:e2e:android": "bash scripts/e2e-android-ci.sh"
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.28.3", "@babel/runtime": "^7.28.3",

View File

@@ -11,10 +11,5 @@
"output": "animations" "output": "animations"
} }
], ],
"allowedNonPeerDependencies": [ "allowedNonPeerDependencies": ["angularx-qrcode", "lottie-web", "socket.io-client", "uuid"]
"angularx-qrcode",
"lottie-web",
"socket.io-client",
"uuid"
]
} }