From 0a4f5a8b6e5aef2f6ccf856001a8e907c907a2ba Mon Sep 17 00:00:00 2001 From: Arkaprabha Chakraborty Date: Sat, 14 Mar 2026 18:20:31 +0530 Subject: [PATCH] fix: input fields don't open keyboard if cursor is already present --- README.md | 28 ++++ android/app/build.gradle | 2 +- app.json | 9 +- ios/Expensso.xcodeproj/project.pbxproj | 4 +- package.json | 5 +- scripts/version.js | 186 +++++++++++++++++++++++++ src/components/CustomBottomSheet.tsx | 16 ++- version.config.json | 4 + 8 files changed, 246 insertions(+), 8 deletions(-) create mode 100644 scripts/version.js create mode 100644 version.config.json diff --git a/README.md b/README.md index 3e2c3f8..495fcfd 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,34 @@ You've successfully run and modified your React Native App. :partying_face: If you're having issues getting the above steps to work, see the [Troubleshooting](https://reactnative.dev/docs/troubleshooting) page. +# Versioning (Single Source of Truth) + +App versioning is centralized in [version.config.json](version.config.json). + +## Commands + +```sh +# Sync all targets from version.config.json +npm run version:sync + +# Set exact version (and optional build number) +npm run version:set -- 1.2.0 +npm run version:set -- 1.2.0 42 + +# Bump and auto-increment build number +npm run version:bump -- patch +npm run version:bump -- minor +npm run version:bump -- major +npm run version:bump -- build +``` + +## Synced targets + +- `package.json` → `version` +- `app.json` → `version`, `android.versionCode`, `ios.buildNumber` +- `android/app/build.gradle` → `versionName`, `versionCode` +- `ios/Expensso.xcodeproj/project.pbxproj` → `MARKETING_VERSION`, `CURRENT_PROJECT_VERSION` + # Learn More To learn more about React Native, take a look at the following resources: diff --git a/android/app/build.gradle b/android/app/build.gradle index 9ad45b9..8b3b76b 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -92,7 +92,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 1 - versionName "1.0" + versionName "0.1.2" } signingConfigs { debug { diff --git a/app.json b/app.json index 71978bb..6123caa 100644 --- a/app.json +++ b/app.json @@ -1,4 +1,11 @@ { "name": "Expensso", - "displayName": "Expensso" + "displayName": "Expensso", + "version": "0.1.2", + "android": { + "versionCode": 1 + }, + "ios": { + "buildNumber": "1" + } } diff --git a/ios/Expensso.xcodeproj/project.pbxproj b/ios/Expensso.xcodeproj/project.pbxproj index 6548735..2d4505c 100644 --- a/ios/Expensso.xcodeproj/project.pbxproj +++ b/ios/Expensso.xcodeproj/project.pbxproj @@ -265,7 +265,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 0.1.2; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -294,7 +294,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 0.1.2; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", diff --git a/package.json b/package.json index 208c208..79e1dd4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "expensso", - "version": "0.1.1-alpha", + "version": "0.1.2", "private": true, "scripts": { "android": "react-native run-android", @@ -8,6 +8,9 @@ "lint": "eslint .", "start": "react-native start", "test": "jest", + "version:sync": "node scripts/version.js sync", + "version:set": "node scripts/version.js set", + "version:bump": "node scripts/version.js bump", "build:release": "cd android && ./gradlew assembleRelease --no-daemon", "build:release:aab": "cd android && ./gradlew bundleRelease --no-daemon" }, diff --git a/scripts/version.js b/scripts/version.js new file mode 100644 index 0000000..f7c0cdd --- /dev/null +++ b/scripts/version.js @@ -0,0 +1,186 @@ +#!/usr/bin/env node + +const fs = require('node:fs'); +const path = require('node:path'); + +const rootDir = path.resolve(__dirname, '..'); +const configPath = path.join(rootDir, 'version.config.json'); +const packagePath = path.join(rootDir, 'package.json'); +const appJsonPath = path.join(rootDir, 'app.json'); +const androidGradlePath = path.join(rootDir, 'android', 'app', 'build.gradle'); +const iosPbxprojPath = path.join(rootDir, 'ios', 'Expensso.xcodeproj', 'project.pbxproj'); + +function readJson(filePath) { + return JSON.parse(fs.readFileSync(filePath, 'utf8')); +} + +function writeJson(filePath, data) { + fs.writeFileSync(filePath, `${JSON.stringify(data, null, 2)}\n`, 'utf8'); +} + +function readVersionConfig() { + const config = readJson(configPath); + + if (!isValidVersion(config.version)) { + throw new Error(`Invalid version in version.config.json: ${config.version}`); + } + + if (!Number.isInteger(config.buildNumber) || config.buildNumber < 1) { + throw new Error(`Invalid buildNumber in version.config.json: ${config.buildNumber}`); + } + + return config; +} + +function isValidVersion(version) { + return /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(version); +} + +function bumpVersion(version, type) { + const base = version.split('-')[0].split('+')[0]; + const [major, minor, patch] = base.split('.').map(Number); + + if (![major, minor, patch].every(Number.isInteger)) { + throw new Error(`Cannot bump invalid version: ${version}`); + } + + if (type === 'major') { + return `${major + 1}.0.0`; + } + + if (type === 'minor') { + return `${major}.${minor + 1}.0`; + } + + if (type === 'patch') { + return `${major}.${minor}.${patch + 1}`; + } + + throw new Error(`Unsupported bump type: ${type}`); +} + +function replaceOrThrow(content, regex, replacement, targetName) { + if (!regex.test(content)) { + throw new Error(`Could not update ${targetName}. Expected pattern not found.`); + } + + const updated = content.replace(regex, replacement); + + return updated; +} + +function syncToTargets({version, buildNumber}) { + const packageJson = readJson(packagePath); + packageJson.version = version; + writeJson(packagePath, packageJson); + + const appJson = readJson(appJsonPath); + appJson.version = version; + appJson.android = {...(appJson.android || {}), versionCode: buildNumber}; + appJson.ios = {...(appJson.ios || {}), buildNumber: String(buildNumber)}; + writeJson(appJsonPath, appJson); + + let gradle = fs.readFileSync(androidGradlePath, 'utf8'); + gradle = replaceOrThrow( + gradle, + /versionCode\s+\d+/, + `versionCode ${buildNumber}`, + 'android versionCode', + ); + gradle = replaceOrThrow( + gradle, + /versionName\s+"[^"]+"/, + `versionName "${version}"`, + 'android versionName', + ); + fs.writeFileSync(androidGradlePath, gradle, 'utf8'); + + let pbxproj = fs.readFileSync(iosPbxprojPath, 'utf8'); + pbxproj = replaceOrThrow( + pbxproj, + /MARKETING_VERSION = [^;]+;/g, + `MARKETING_VERSION = ${version};`, + 'iOS MARKETING_VERSION', + ); + pbxproj = replaceOrThrow( + pbxproj, + /CURRENT_PROJECT_VERSION = [^;]+;/g, + `CURRENT_PROJECT_VERSION = ${buildNumber};`, + 'iOS CURRENT_PROJECT_VERSION', + ); + fs.writeFileSync(iosPbxprojPath, pbxproj, 'utf8'); +} + +function saveConfig(config) { + writeJson(configPath, config); +} + +function printUsage() { + console.log('Usage:'); + console.log(' node scripts/version.js sync'); + console.log(' node scripts/version.js set [buildNumber]'); + console.log(' node scripts/version.js bump '); +} + +function main() { + const [, , command, arg1, arg2] = process.argv; + + if (!command) { + printUsage(); + process.exitCode = 1; + return; + } + + if (command === 'sync') { + const config = readVersionConfig(); + syncToTargets(config); + console.log(`Synced version ${config.version} (${config.buildNumber})`); + return; + } + + if (command === 'set') { + if (!arg1 || !isValidVersion(arg1)) { + throw new Error('set requires a valid semver, e.g. 1.2.3'); + } + + const config = readVersionConfig(); + const nextBuildNumber = arg2 ? Number(arg2) : config.buildNumber; + + if (!Number.isInteger(nextBuildNumber) || nextBuildNumber < 1) { + throw new Error('buildNumber must be a positive integer'); + } + + const next = {version: arg1, buildNumber: nextBuildNumber}; + saveConfig(next); + syncToTargets(next); + console.log(`Set version to ${next.version} (${next.buildNumber})`); + return; + } + + if (command === 'bump') { + if (!arg1 || !['major', 'minor', 'patch', 'build'].includes(arg1)) { + throw new Error('bump requires one of: major, minor, patch, build'); + } + + const config = readVersionConfig(); + const next = { + version: arg1 === 'build' ? config.version : bumpVersion(config.version, arg1), + buildNumber: config.buildNumber + 1, + }; + + saveConfig(next); + syncToTargets(next); + console.log(`Bumped to ${next.version} (${next.buildNumber})`); + return; + } + + printUsage(); + process.exitCode = 1; +} + +try { + main(); +} catch (error) { + console.error(error instanceof Error ? error.message : String(error)); + process.exitCode = 1; +} diff --git a/src/components/CustomBottomSheet.tsx b/src/components/CustomBottomSheet.tsx index 87387cf..142cc1a 100644 --- a/src/components/CustomBottomSheet.tsx +++ b/src/components/CustomBottomSheet.tsx @@ -27,10 +27,12 @@ import { StyleSheet, TouchableOpacity, Pressable, + Platform, } from 'react-native'; import BottomSheet, { BottomSheetBackdrop, BottomSheetScrollView, + BottomSheetTextInput, BottomSheetView, type BottomSheetBackdropProps, } from '@gorhom/bottom-sheet'; @@ -271,8 +273,6 @@ export interface BottomSheetInputProps { autoFocus?: boolean; } -import {TextInput} from 'react-native'; - export const BottomSheetInput: React.FC = ({ label, value, @@ -287,6 +287,13 @@ export const BottomSheetInput: React.FC = ({ const theme = useTheme(); const {colors, typography, shape, spacing} = theme; const [focused, setFocused] = React.useState(false); + const inputRef = React.useRef>(null); + + const handlePressIn = React.useCallback(() => { + if (Platform.OS === 'android' && focused) { + inputRef.current?.focus(); + } + }, [focused]); const borderColor = error ? colors.error @@ -325,7 +332,8 @@ export const BottomSheetInput: React.FC = ({ {prefix} )} - = ({ keyboardType={keyboardType} multiline={multiline} autoFocus={autoFocus} + showSoftInputOnFocus + onPressIn={handlePressIn} onFocus={() => setFocused(true)} onBlur={() => setFocused(false)} /> diff --git a/version.config.json b/version.config.json new file mode 100644 index 0000000..a74135f --- /dev/null +++ b/version.config.json @@ -0,0 +1,4 @@ +{ + "version": "0.1.2", + "buildNumber": 1 +}