Skip to content

Repository files navigation

FormEasy

Make your files upload-ready.

FormEasy is a small, offline-first React Native app that solves one specific, everyday annoyance: government, job, exam, and KYC portals demand photos and documents in very exact formats — "JPG, 200×230px, 20–50 KB" — and getting there with generic phone tools is trial and error. FormEasy takes a photo, a portal's requirements, and produces a file that's guaranteed to match, entirely on your device.

The problem

Upload portals reject files for narrow, mechanical reasons: wrong format, wrong pixel dimensions, file too big or too small. Most people don't know what "compress to 50 KB" means in practice, and generic online converters require uploading personal photos, signatures, and ID documents to an unknown third-party server. FormEasy solves this locally — no upload, no account, no backend.

Demo

FormEasy demo

35-second walkthrough of the golden path: Home dashboard → pick a photo → Passport/Signature requirement presets → Result → Recent Files → Settings. Captured on the iOS Simulator; an MP4 version is also available if you'd rather download it.

Screenshots

Home dashboardSelect FileRequirements (Passport preset)
Home dashboard with stats and last-prepared fileSelect File screen with a picked photoRequirements screen with Passport Photo preset
Requirements (Signature preset)ResultRecent FilesSettings
Requirements screen with signature presetResult screen, ready to uploadRecent Files screen with a prepared fileSettings screen with theme and privacy options

When a request can't be met exactly, the Result screen shows Best Effort with a plain-language explanation instead of silently claiming success — see the compression pipeline section below for how that's decided.

Features

  • Prepare Photo — resize, compress, and convert any photo against dimension/size/format requirements you type in
  • Prepare Signature — a smart preset (140×60px, 20 KB, JPG) as a starting point; still editable, since portals vary
  • Custom Size — type in exactly what a portal asks for, with no hardcoded per-portal presets to go stale
  • Before/After comparison with a pass/fail checklist and an honest Ready to Upload / Best Effort / Could Not Meet Requirements status — a quality-loss warning appears before you commit to an unreasonably tight size budget
  • Save to Photos and Share the prepared file, output-named original_formeasy.ext
  • Fully offline — every step (crop, resize, compress, format conversion) runs on-device

Privacy

Your files are processed on your device and are never uploaded to a server. This app makes no network calls with file data — there is no backend. Permissions (camera, photo library, save-to-Photos) are requested only at the moment they're needed, with plain-language descriptions of why.

Tech stack

Bare React Native CLI (not Expo) + TypeScript, chosen because the app's core value is native image processing where Expo's managed-workflow constraints wouldn't have added anything material for this specific set of native modules.

ConcernLibraryWhy
Navigation@react-navigation/native + native-stackNative transitions, typed routes
Image pickerreact-native-image-pickerGallery + camera in one API, returns metadata directly
Crop@react-native-community/image-editorNative crop-to-rectangle, avoids distorting the subject
Resize/compress/convert@bam.tech/react-native-image-resizerJPEG quality control, PNG support, exact-dimension "stretch" mode
File rename/copy@dr.pogodin/react-native-fsFriendly output filenames instead of native UUID temp names
Save to Photos@react-native-camera-roll/camera-rollThe maintained successor to RN core's removed CameraRoll API
ShareReact Native core ShareNo extra dependency needed for a plain share sheet
Iconsreact-native-svgA small, curated set of ~12 hand-authored icons rather than a 5000-icon font library
StateReact useState / local component stateEverything here is screen-local or passed via route params — no app-wide state library earns its keep at this scale
TestingJest + @testing-library/react-nativeUnit tests for the compression/crop/validation logic, component tests for the Requirements form

Architecture

Feature-based, not layered — no repository pattern or DI container for a project this size.

src/
navigation/ Typed React Navigation stack + route params
features/
home/ Landing screen
image-picker/ Gallery/camera selection + its native wrapper
requirements/ The dimensions/size/format form + its pure validation logic
result/ Before/after, checklist, Save/Share
recent/ Recent files list, backed by AsyncStorage
settings/ Privacy statement
services/
imageProcessing/ Pure crop-math and quality-search algorithms, plus the
orchestrator that calls the native crop/resize modules
shared/
components/ Button, Card, Chip, TextField
permissions/ Android runtime permission requests (camera, save-to-Photos)
theme/ Design tokens (colors, spacing) + useTheme()
types/ ImageMetadata, ProcessingRequirements, ValidationResult
utils/ formatBytes, fileNaming

services/imageProcessing is deliberately UI-free: the crop-rectangle math and the JPEG quality binary search are plain functions that take data and return data, callable from Jest without a device or simulator. Screens call the orchestrator (pipeline.ts) and render whatever it returns — they don't know or care how compression works.

Image processing pipeline

flowchart LR
A[Selected image] --> B{Dimensions requested?}
B -- no --> E[Resize/compress at original size]
B -- yes, ratio locked --> C[Center-crop to match target ratio]
B -- yes, ratio unlocked --> D[Stretch to exact dimensions]
C --> E
D --> E
E --> F{Format}
F -- JPEG --> G[Binary-search quality<br/>for best fit under max size]
F -- PNG --> H[Single encode<br/>lossless, limited size control]
G --> I[Validate against requirements]
H --> I
I --> J[Before/After + checklist]
Loading

The compression step never blindly claims success: if no quality setting lands inside the requested size range without heavy degradation, the Result screen shows Best Effort with a plain-language explanation instead of a false pass.

Setup

Prerequisites: Node ≥ 22.11, Xcode (for iOS), CocoaPods, Ruby. Full React Native environment setup: https://reactnative.dev/docs/set-up-your-environment

npm install
cd ios && bundle install && bundle exec pod install &&cd ..
npm start # Metro, in its own terminal
npm run ios # in a second terminal

Android

Android's toolchain (JDK + Android SDK) is not installed by default in this project's history — set it up once:

brew install --cask temurin17
brew install --cask android-commandlinetools
# Add to ~/.zshrc, then restart your terminal:export ANDROID_HOME="$HOME/Library/Android/sdk"export PATH="$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator"
yes | sdkmanager --licenses
sdkmanager "platform-tools""platforms;android-35""build-tools;35.0.0""emulator""system-images;android-35;google_apis;arm64-v8a"
avdmanager create avd -n FormEasy_Pixel -k "system-images;android-35;google_apis;arm64-v8a" -d pixel_7
emulator -avd FormEasy_Pixel
npm run android

Or install Android Studio and let its own SDK Manager set up ANDROID_HOME instead of the command-line tools above.

Testing

npx tsc --noEmit # typecheck
npm run lint # ESLint
npm test# Jest: unit + component tests

Coverage focuses on the logic most likely to have a subtle bug: the crop-rectangle math, the JPEG quality binary search, requirement validation, and the Requirements form's mode-based presets — all pure functions or component-render assertions, no native code required to run them. A GitHub Actions workflow (.github/workflows/ci.yml) runs typecheck, lint, and tests on every push/PR.

There is no automated end-to-end test that drives the real Simulator/device UI (no Detox/Maestro setup) — the one critical flow (Home → pick a photo → set requirements → Result → Save/Share) should be walked manually before a release; see docs/manual-e2e-checklist.md.

Known limitations

  • PDF/document support is not implemented. Reliable cross-platform PDF compression without paid SDKs or a backend was judged too complex for this project's scope; photos and signatures cover the core use case.
  • PNG size control is limited — PNG is lossless, so unlike JPEG there's no quality dial to hit a tight size budget. The Result screen surfaces this as a warning rather than silently failing.
  • No interactive crop UI. Cropping is automatic (centered, matched to the target aspect ratio) rather than a drag-to-adjust gesture — a deliberate scope cut to avoid pulling in gesture-handling dependencies for a small app.
  • Android is unverified on a real device/emulator — every screen, service, and native dependency (image picker, cropper, resizer, camera roll, async storage) is shared, cross-platform code with an Android implementation, and Android-only concerns (runtime CAMERA/storage permission requests, the Photo Picker backport dependency for API < 30) are wired in — see src/shared/permissions/androidPermissions.ts. It just hasn't been built and clicked through on Android, since the toolchain (JDK + Android SDK) isn't installed in this project's history.

Future enhancements (explicitly not built yet)

AI/ML cleanup, OCR, automatic portal detection, accounts, cloud sync, a backend, community presets, batch processing, and a document scanner are all out of scope for this project by design — see the plan's own "DO NOT BUILD YET" list. PDF support and Android verification are the two items likely to come first if this project continues.

About

ormEasy is a small app for Offline-first React Native app that resizes, crops, and compresses photos & signatures to match exact upload-portal requirements (dimensions, size, format) — entirely on-device. No uploads, no accounts, no backend.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages