Code cleanup and remove vague dependencies (#3)

* cleanup

* gemfile dep fix
This commit is contained in:
2026-04-18 23:45:19 +05:30
committed by GitHub
parent b53d853de7
commit 57c4cda970
17 changed files with 5 additions and 1604 deletions

View File

@@ -1,47 +0,0 @@
/** @type {Detox.DetoxConfig} */
module.exports = {
testRunner: {
args: {
$0: 'jest',
config: 'e2e/jest.config.js',
},
jest: {
setupTimeout: 120000,
},
},
apps: {
'android.debug': {
type: 'android.apk',
binaryPath: 'android/app/build/outputs/apk/debug/app-debug.apk',
testBinaryPath:
'android/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk',
build:
'cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug --no-daemon',
reversePorts: [8081],
},
'android.release': {
type: 'android.apk',
binaryPath: 'android/app/build/outputs/apk/release/app-release.apk',
build:
'cd android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release --no-daemon',
},
},
devices: {
emulator: {
type: 'android.emulator',
// avdName is set by reactivecircus/android-emulator-runner in CI.
// For local runs, create an AVD named below or override via DETOX_AVD_NAME.
device: {avdName: process.env.DETOX_AVD_NAME ?? 'Pixel_6_API_34'},
},
},
configurations: {
'android.emu.debug': {
device: 'emulator',
app: 'android.debug',
},
'android.emu.release': {
device: 'emulator',
app: 'android.release',
},
},
};

View File

@@ -1,120 +0,0 @@
name: E2E Android (Detox)
on:
push:
branches: [main, master]
pull_request:
branches: [main, master]
workflow_dispatch:
jobs:
e2e:
name: Detox E2E (debug)
runs-on: ubuntu-latest
env:
ANDROID_HOME: /home/runner/android-sdk
ANDROID_SDK_ROOT: /home/runner/android-sdk
steps:
- uses: actions/checkout@v6
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 17
- uses: actions/setup-node@v6
with:
node-version: 20
cache: npm
- name: Install dependencies
run: npm ci
- name: Cache Android SDK
id: cache-sdk
uses: actions/cache@v5
with:
path: /home/runner/android-sdk
key: android-sdk-${{ hashFiles('android/build.gradle') }}
- name: Install Android SDK
if: steps.cache-sdk.outputs.cache-hit != 'true'
run: |
mkdir -p $ANDROID_HOME/cmdline-tools
curl -sSL https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip -o cmdline-tools.zip
unzip -q cmdline-tools.zip -d $ANDROID_HOME/cmdline-tools
mv $ANDROID_HOME/cmdline-tools/cmdline-tools $ANDROID_HOME/cmdline-tools/latest
yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --sdk_root=$ANDROID_HOME \
"platforms;android-35" "build-tools;35.0.0" "platform-tools"
- name: Cache Gradle
uses: actions/cache@v5
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
android/.gradle
key: gradle-${{ hashFiles('android/gradle/wrapper/gradle-wrapper.properties', 'android/**/*.gradle') }}
restore-keys: gradle-
- name: chmod gradlew
run: chmod +x android/gradlew
# debuggableVariants=[] in build.gradle means JS is bundled into debug APK too,
# so no Metro server is needed at runtime.
- name: Build APKs
working-directory: android
run: ./gradlew assembleDebug assembleAndroidTest --no-daemon
- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
# Cache the AVD snapshot to speed up subsequent runs
- name: Cache AVD snapshot
uses: actions/cache@v5
id: avd-cache
with:
path: |
~/.android/avd/*
~/.android/adb*
key: avd-34-x86_64-google_apis
- name: Create AVD snapshot
if: steps.avd-cache.outputs.cache-hit != 'true'
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 34
target: google_apis
arch: x86_64
profile: pixel_6
avd-name: Pixel_6_API_34
force-avd-creation: false
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: false
script: echo "AVD snapshot ready"
- name: Run Detox E2E
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 34
target: google_apis
arch: x86_64
profile: pixel_6
avd-name: Pixel_6_API_34
force-avd-creation: false
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true
script: npx detox test --configuration android.emu.debug --headless --record-logs failing
- name: Upload Detox logs
if: failure()
uses: actions/upload-artifact@v7
with:
name: detox-logs-${{ github.run_number }}
path: artifacts/
retention-days: 7

20
App.tsx
View File

@@ -1,4 +1,4 @@
import React, {useState, useCallback, useEffect} from 'react'; import React, {useState, useCallback} from 'react';
import {Alert} from 'react-native'; import {Alert} from 'react-native';
import HomeScreen from './src/screens/HomeScreen'; import HomeScreen from './src/screens/HomeScreen';
import ScanScreen from './src/screens/ScanScreen'; import ScanScreen from './src/screens/ScanScreen';
@@ -14,24 +14,6 @@ export default function App() {
useDocumentStore(); useDocumentStore();
const [screen, setScreen] = useState<Screen>('home'); const [screen, setScreen] = useState<Screen>('home');
// Seed a fake document when launched with detoxSeedDocument=1 (E2E only)
useEffect(() => {
if (__DEV__ || process.env.DETOX_SEED) {
const args = (global as any).__detox_launch_args ?? {};
if (args.detoxSeedDocument === '1') {
createDocument([
{
id: 'seed-page-1',
uri: 'https://via.placeholder.com/600x800.jpg',
width: 600,
height: 800,
},
]);
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const [pendingPages, setPendingPages] = useState<ScannedPage[]>([]); const [pendingPages, setPendingPages] = useState<ScannedPage[]>([]);
const [viewingDoc, setViewingDoc] = useState<ScannedDocument | null>(null); const [viewingDoc, setViewingDoc] = useState<ScannedDocument | null>(null);

View File

@@ -1,7 +1,7 @@
source 'https://rubygems.org' source 'https://rubygems.org'
# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
ruby ">= 2.6.10" ruby ">= 3.1.0"
gem 'cocoapods', '~> 1.13' gem 'cocoapods', '~> 1.13'
gem 'activesupport', '>= 6.1.7.3', '< 7.1.0' gem 'activesupport', '>= 7.2.3.1'

View File

@@ -57,15 +57,6 @@ Unit tests (Jest + React Native Testing Library):
npm test npm test
``` ```
E2E (Detox, Android emulator):
```
npm run test:e2e:build
npm run test:e2e
```
The E2E build currently targets `android.emu.debug`. There's a launch-arg seed path (`detoxSeedDocument=1`) in `App.tsx` for preloading a fake document, but the bridge that wires it through to the native side isn't hooked up yet — so the viewer E2E is disabled for now.
## Builds ## Builds
Android release APKs are split per-ABI (armeabi-v7a, arm64-v8a, x86_64) — see `android/app/build.gradle`. Per-arch APKs are smaller than a universal one. Android release APKs are split per-ABI (armeabi-v7a, arm64-v8a, x86_64) — see `android/app/build.gradle`. Per-arch APKs are smaller than a universal one.
@@ -78,11 +69,10 @@ CI builds live in `.github/workflows/`.
App.tsx # screen switcher + top-level state App.tsx # screen switcher + top-level state
src/ src/
screens/ # Home, Scan, Review, Viewer screens/ # Home, Scan, Review, Viewer
components/ # DocumentCard, EdgeOverlay, FilterPicker components/ # DocumentCard, FilterPicker
hooks/ # useDocumentStore hooks/ # useDocumentStore
utils/ # generatePdf, imageProcessing utils/ # generatePdf, imageProcessing
types/ # shared types types/ # shared types
e2e/ # Detox specs
patches/ # patch-package patches applied postinstall patches/ # patch-package patches applied postinstall
``` ```

View File

@@ -17,12 +17,6 @@ react {
// The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js // The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js
// cliFile = file("../node_modules/react-native/cli.js") // cliFile = file("../node_modules/react-native/cli.js")
/* Variants */
// Empty list means the JS bundle is embedded in ALL variants including debug.
// This is required for E2E tests in CI where no Metro server is running.
// For local dev, Metro still takes precedence if the packager is running.
debuggableVariants = []
/* Bundling */ /* Bundling */
// A list containing the node command and its flags. Default is just 'node'. // A list containing the node command and its flags. Default is just 'node'.
// nodeExecutableAndArgs = ["node"] // nodeExecutableAndArgs = ["node"]
@@ -106,8 +100,6 @@ android {
splits { splits {
abi { abi {
reset() reset()
// Only split release builds — debug keeps a single universal APK
// so Detox/E2E can find the expected app-debug.apk path.
enable gradle.startParameter.taskNames.any { it.toLowerCase().contains("release") } enable gradle.startParameter.taskNames.any { it.toLowerCase().contains("release") }
universalApk false universalApk false
include (*project.property("reactNativeArchitectures").split(",")) include (*project.property("reactNativeArchitectures").split(","))
@@ -131,10 +123,6 @@ dependencies {
// The version of react-native is set by the React Native Gradle Plugin // The version of react-native is set by the React Native Gradle Plugin
implementation("com.facebook.react:react-android") implementation("com.facebook.react:react-android")
implementation("com.facebook.react:flipper-integration") implementation("com.facebook.react:flipper-integration")
androidTestImplementation("androidx.test:runner:1.6.2")
androidTestImplementation("androidx.test:rules:1.6.1")
androidTestImplementation("androidx.test.ext:junit:1.2.1")
androidTestImplementation("com.wix:detox:20.50.1") { transitive = true }
if (hermesEnabled.toBoolean()) { if (hermesEnabled.toBoolean()) {
implementation("com.facebook.react:hermes-android") implementation("com.facebook.react:hermes-android")

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-sdk tools:overrideLibrary="com.wix.detox" />
</manifest>

View File

@@ -1,30 +0,0 @@
package com.lensapp;
import com.wix.detox.Detox;
import com.wix.detox.config.DetoxConfig;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;
@RunWith(AndroidJUnit4.class)
@LargeTest
public class DetoxTest {
@Rule
public ActivityTestRule<MainActivity> mActivityRule =
new ActivityTestRule<>(MainActivity.class, false, false);
@Test
public void runDetoxTests() {
DetoxConfig detoxConfig = new DetoxConfig();
detoxConfig.idlePolicyConfig.masterTimeoutSec = 90;
detoxConfig.idlePolicyConfig.idleResourceTimeoutSec = 60;
detoxConfig.rnContextLoadTimeoutSec = 180;
Detox.runTests(mActivityRule, detoxConfig);
}
}

View File

@@ -19,21 +19,3 @@ buildscript {
} }
apply plugin: "com.facebook.react.rootproject" apply plugin: "com.facebook.react.rootproject"
allprojects {
repositories {
// Route com.wix.detox lookups to Detox's bundled local Maven repo so
// they bypass JitPack (which RN's Gradle plugin adds as a fallback and
// which returns an HTML error page instead of a valid POM).
exclusiveContent {
forRepository {
maven {
url "$rootDir/../node_modules/detox/Detox-android"
}
}
filter {
includeModule "com.wix", "detox"
}
}
}
}

View File

@@ -1,28 +0,0 @@
import {device, element, by, expect as detoxExpect} from 'detox';
describe('Home Screen', () => {
beforeAll(async () => {
await device.launchApp({newInstance: true});
});
beforeEach(async () => {
await device.reloadReactNative();
});
it('shows the app title', async () => {
await detoxExpect(element(by.text('Lens'))).toBeVisible();
});
it('shows empty state when no documents exist', async () => {
await detoxExpect(element(by.id('empty-state'))).toBeVisible();
await detoxExpect(element(by.text('No scans yet'))).toBeVisible();
});
it('shows the scan FAB', async () => {
await detoxExpect(element(by.id('scan-fab'))).toBeVisible();
});
it('scan FAB label reads Scan', async () => {
await detoxExpect(element(by.text('Scan'))).toBeVisible();
});
});

View File

@@ -1,12 +0,0 @@
/** @type {import('@jest/types').Config.InitialOptions} */
module.exports = {
rootDir: '..',
testMatch: ['<rootDir>/e2e/**/*.e2e.ts'],
testTimeout: 120000,
maxWorkers: 1,
globalSetup: 'detox/runners/jest/globalSetup',
globalTeardown: 'detox/runners/jest/globalTeardown',
reporters: ['detox/runners/jest/reporter'],
testEnvironment: 'detox/runners/jest/testEnvironment',
verbose: true,
};

View File

@@ -1,8 +0,0 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"types": ["detox", "jest"]
},
"include": ["**/*.e2e.ts", "jest.config.js"],
"exclude": []
}

1169
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,8 +8,6 @@
"lint": "eslint .", "lint": "eslint .",
"start": "react-native start", "start": "react-native start",
"test": "jest", "test": "jest",
"test:e2e:build": "detox build --configuration android.emu.debug",
"test:e2e": "detox test --configuration android.emu.debug --headless",
"postinstall": "patch-package" "postinstall": "patch-package"
}, },
"dependencies": { "dependencies": {
@@ -18,7 +16,6 @@
"react-native-document-scanner-plugin": "^2.0.4", "react-native-document-scanner-plugin": "^2.0.4",
"react-native-fs": "^2.20.0", "react-native-fs": "^2.20.0",
"react-native-html-to-pdf": "^1.3.0", "react-native-html-to-pdf": "^1.3.0",
"react-native-image-crop-picker": "^0.51.1",
"react-native-share": "^12.2.6" "react-native-share": "^12.2.6"
}, },
"devDependencies": { "devDependencies": {
@@ -35,7 +32,6 @@
"@types/react": "^18.2.6", "@types/react": "^18.2.6",
"@types/react-test-renderer": "^18.0.0", "@types/react-test-renderer": "^18.0.0",
"babel-jest": "^29.6.3", "babel-jest": "^29.6.3",
"detox": "^20.50.1",
"eslint": "^8.19.0", "eslint": "^8.19.0",
"jest": "^29.6.3", "jest": "^29.6.3",
"jest-circus": "^30.3.0", "jest-circus": "^30.3.0",

View File

@@ -1,92 +0,0 @@
import React, {useRef} from 'react';
import {View, StyleSheet, PanResponder, Animated} from 'react-native';
import {Corners, Point} from '../types';
interface Props {
corners: Corners;
imageWidth: number;
imageHeight: number;
containerWidth: number;
containerHeight: number;
onChange: (corners: Corners) => void;
}
type CornerKey = keyof Corners;
const HANDLE_SIZE = 24;
export default function EdgeOverlay({
corners,
imageWidth,
imageHeight,
containerWidth,
containerHeight,
onChange,
}: Props) {
const scaleX = containerWidth / imageWidth;
const scaleY = containerHeight / imageHeight;
const toDisplay = (p: Point) => ({x: p.x * scaleX, y: p.y * scaleY});
const toImage = (p: Point) => ({x: p.x / scaleX, y: p.y / scaleY});
const makeHandle = (key: CornerKey) => {
const pos = toDisplay(corners[key]);
const pan = useRef(new Animated.ValueXY({x: pos.x, y: pos.y})).current;
const responder = PanResponder.create({
onStartShouldSetPanResponder: () => true,
onPanResponderMove: (_, gs) => {
const nx = Math.max(0, Math.min(containerWidth, pos.x + gs.dx));
const ny = Math.max(0, Math.min(containerHeight, pos.y + gs.dy));
pan.setValue({x: nx, y: ny});
},
onPanResponderRelease: (_, gs) => {
const nx = Math.max(0, Math.min(containerWidth, pos.x + gs.dx));
const ny = Math.max(0, Math.min(containerHeight, pos.y + gs.dy));
onChange({...corners, [key]: toImage({x: nx, y: ny})});
},
});
return (
<Animated.View
key={key}
style={[
styles.handle,
{transform: [{translateX: Animated.subtract(pan.x, HANDLE_SIZE / 2)}, {translateY: Animated.subtract(pan.y, HANDLE_SIZE / 2)}]},
]}
{...responder.panHandlers}
/>
);
};
const pts = (Object.keys(corners) as CornerKey[]).map(k => toDisplay(corners[k]));
const polygon = pts.map(p => `${p.x},${p.y}`).join(' ');
return (
<View style={[styles.overlay, {width: containerWidth, height: containerHeight}]} pointerEvents="box-none">
{/* SVG-style quad outline using absolute positioned thin lines via borders */}
{(Object.keys(corners) as CornerKey[]).map(k => makeHandle(k))}
</View>
);
}
const styles = StyleSheet.create({
overlay: {
position: 'absolute',
top: 0,
left: 0,
},
handle: {
position: 'absolute',
width: HANDLE_SIZE,
height: HANDLE_SIZE,
borderRadius: HANDLE_SIZE / 2,
backgroundColor: 'rgba(0, 122, 255, 0.9)',
borderWidth: 2,
borderColor: '#fff',
shadowColor: '#000',
shadowOpacity: 0.4,
shadowRadius: 4,
elevation: 6,
},
});

View File

@@ -8,22 +8,8 @@ export interface ScannedDocument {
export interface ScannedPage { export interface ScannedPage {
id: string; id: string;
uri: string; uri: string;
corners?: Corners;
width: number; width: number;
height: number; height: number;
} }
export interface Corners {
topLeft: Point;
topRight: Point;
bottomLeft: Point;
bottomRight: Point;
}
export interface Point {
x: number;
y: number;
}
export type ScanMode = 'auto' | 'manual';
export type FilterMode = 'original' | 'grayscale' | 'blackwhite' | 'enhanced'; export type FilterMode = 'original' | 'grayscale' | 'blackwhite' | 'enhanced';

View File

@@ -1,4 +1,4 @@
import {FilterMode, Corners} from '../types'; import {FilterMode} from '../types';
export function getFilterStyle(mode: FilterMode): object { export function getFilterStyle(mode: FilterMode): object {
switch (mode) { switch (mode) {
@@ -13,17 +13,6 @@ export function getFilterStyle(mode: FilterMode): object {
} }
} }
// Builds a CSS-style matrix string for perspective transform preview
export function cornersToTransform(corners: Corners, width: number, height: number): string {
const {topLeft, topRight, bottomLeft, bottomRight} = corners;
// Normalized 0-1 coordinates for display
const tl = `${(topLeft.x / width) * 100}% ${(topLeft.y / height) * 100}%`;
const tr = `${(topRight.x / width) * 100}% ${(topRight.y / height) * 100}%`;
const br = `${(bottomRight.x / width) * 100}% ${(bottomRight.y / height) * 100}%`;
const bl = `${(bottomLeft.x / width) * 100}% ${(bottomLeft.y / height) * 100}%`;
return `polygon(${tl}, ${tr}, ${br}, ${bl})`;
}
export function formatDate(date: Date): string { export function formatDate(date: Date): string {
return date.toLocaleDateString(undefined, { return date.toLocaleDateString(undefined, {
year: 'numeric', year: 'numeric',