Working demo app mrz scan screen (#1232)

* Add camera permission to demo manifest

* save wip

* save buidling wip

* enable camera

* working mrz scan

* cr feedback
This commit is contained in:
Justin Hernandez
2025-10-07 18:45:20 -07:00
committed by GitHub
parent f7c5ef0e74
commit fecfc6b1b2
19 changed files with 153 additions and 40 deletions

View File

@@ -0,0 +1,8 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="5dp" />
<solid android:color="@android:color/transparent" />
<stroke
android:width="1dp"
android:color="@color/grey_2" />
</shape>

View File

@@ -0,0 +1,20 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true">
<shape>
<corners
android:topLeftRadius="5dp"
android:bottomLeftRadius="5dp" />
<solid android:color="@color/blue_light"></solid>
</shape>
</item>
<item android:state_pressed="true">
<shape>
<solid android:color="@color/white"></solid>
</shape>
</item>
<item>
<shape>
<solid android:color="@android:color/transparent"></solid>
</shape>
</item>
</selector>

View File

@@ -0,0 +1,20 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true">
<shape>
<corners
android:topRightRadius="5dp"
android:bottomRightRadius="5dp" />
<solid android:color="@color/blue_light"></solid>
</shape>
</item>
<item android:state_pressed="true">
<shape>
<solid android:color="@color/white"></solid>
</shape>
</item>
<item>
<shape>
<solid android:color="@android:color/transparent"></solid>
</shape>
</item>
</selector>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<font
android:fontStyle="normal"
android:fontWeight="700"
android:font="@font/roboto_bold"
app:fontStyle="normal"
app:fontWeight="700"
app:font="@font/roboto_bold" />
<font
android:fontStyle="italic"
android:fontWeight="700"
android:font="@font/roboto_bold_italic"
app:fontStyle="italic"
app:fontWeight="700"
app:font="@font/roboto_bold_italic" />
</font-family>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<font
android:fontStyle="normal"
android:fontWeight="500"
android:font="@font/roboto_medium"
app:fontStyle="normal"
app:fontWeight="500"
app:font="@font/roboto_medium" />
<font
android:fontStyle="italic"
android:fontWeight="500"
android:font="@font/roboto_medium_italic"
app:fontStyle="italic"
app:fontWeight="500"
app:font="@font/roboto_medium_italic" />
</font-family>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<font
android:fontStyle="normal"
android:fontWeight="400"
android:font="@font/roboto_regular"
app:fontStyle="normal"
app:fontWeight="400"
app:font="@font/roboto_regular" />
<font
android:fontStyle="italic"
android:fontWeight="400"
android:font="@font/roboto_italic"
app:fontStyle="italic"
app:fontWeight="400"
app:font="@font/roboto_italic" />
</font-family>

View File

@@ -14,7 +14,11 @@ module.exports = {
ios: {
podspecPath: path.join(packageRoot, 'mobile-sdk-alpha.podspec'),
},
android: null,
android: {
sourceDir: path.join(packageRoot, 'android'),
packageImportPath: 'import com.selfxyz.selfSDK.RNSelfPassportReaderPackage;',
packageInstance: 'new RNSelfPassportReaderPackage()',
},
},
},
};

View File

@@ -125,7 +125,7 @@ export const MRZScannerView: React.FC<MRZScannerViewProps> = ({
<View style={containerStyle}>
<RCTFragment
RCTFragmentViewManager={NativeMRZScannerView as any}
fragmentComponentName="PassportOCRViewManager"
fragmentComponentName="SelfOCRViewManager"
isMounted={true}
style={{
height: PixelRatio.getPixelSizeForLayoutSize(800),

View File

@@ -42,6 +42,12 @@ android {
buildFeatures {
buildConfig true
}
packaging {
resources {
excludes += ['META-INF/versions/9/OSGI-INF/MANIFEST.MF']
}
}
}
apply from: file("../../node_modules/react-native-vector-icons/fonts.gradle")
@@ -52,4 +58,8 @@ dependencies {
if (project.hasProperty('newArchEnabled') ? newArchEnabled.toBoolean() : false) {
implementation("com.facebook.react:react-android-codegen:0.76.9")
}
// Manually add mobile-sdk-alpha dependency
implementation project(':mobile-sdk-alpha')
implementation 'com.google.android.material:material:1.12.0'
}

View File

@@ -1,6 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<application
android:name=".MainApplication"

View File

@@ -9,14 +9,16 @@ import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
import com.facebook.react.defaults.DefaultReactNativeHost
import com.facebook.soloader.SoLoader
import com.facebook.react.soloader.OpenSourceMergedSoMapping
import com.selfxyz.selfSDK.RNSelfPassportReaderPackage
class MainApplication : Application(), ReactApplication {
private val mReactNativeHost: ReactNativeHost = object : DefaultReactNativeHost(this) {
override fun getPackages(): List<ReactPackage> {
// Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
return PackageList(this).packages
val packages = PackageList(this).packages.toMutableList()
// Manually add the SelfSDK package
packages.add(RNSelfPassportReaderPackage())
return packages
}
override fun getJSMainModuleName(): String {

View File

@@ -5,4 +5,6 @@ extensions.configure(com.facebook.react.ReactSettingsExtension){ ex ->
}
rootProject.name = 'SelfDemoApp'
include ':app'
include ':mobile-sdk-alpha'
project(':mobile-sdk-alpha').projectDir = new File(rootProject.projectDir, '../../mobile-sdk-alpha/android')
includeBuild('../../../node_modules/@react-native/gradle-plugin')

View File

@@ -76,23 +76,19 @@ export default function DocumentCamera({ onBack }: Props) {
onBack();
}}
contentStyle={styles.screenContent}
rightAction={
<TouchableOpacity accessibilityRole="button" onPress={handleSaveDocument}>
<Text style={styles.headerAction}>Save Document</Text>
</TouchableOpacity>
}
>
{permissionStatus === 'loading' && renderLoading()}
{permissionStatus === 'denied' && renderPermissionDenied()}
{permissionStatus === 'granted' && (
<View style={styles.contentWrapper}>
<View style={styles.instructionsContainer}>
<Text style={styles.instructionsTitle}>Position your document</Text>
<Text style={styles.instructionsText}>{instructionsText}</Text>
</View>
<View style={styles.cameraWrapper}>
<MRZScannerView style={styles.scanner} onMRZDetected={handleMRZDetected} onError={handleScannerError} />
<View style={styles.overlay} accessibilityLiveRegion="polite" pointerEvents="none">
<Text style={styles.overlayTitle}>Position your document</Text>
<Text style={styles.overlayText}>{instructionsText}</Text>
</View>
</View>
<View style={styles.statusContainer}>
@@ -110,9 +106,7 @@ export default function DocumentCamera({ onBack }: Props) {
{scanState === 'error' && error && <Text style={[styles.statusText, styles.errorText]}>{error}</Text>}
</View>
{mrzResult && <DocumentScanResultCard result={mrzResult} />}
<View style={styles.actions}>
<View style={[styles.actions, mrzResult && styles.actionsWithResult]}>
<TouchableOpacity accessibilityRole="button" onPress={handleScanAgain} style={styles.secondaryButton}>
<Text style={styles.secondaryButtonText}>Scan Again</Text>
</TouchableOpacity>
@@ -121,6 +115,8 @@ export default function DocumentCamera({ onBack }: Props) {
<Text style={styles.primaryButtonText}>Save Document</Text>
</TouchableOpacity>
</View>
{mrzResult && <DocumentScanResultCard result={mrzResult} />}
</View>
)}
</ScreenLayout>
@@ -134,6 +130,20 @@ const styles = StyleSheet.create({
contentWrapper: {
flex: 1,
},
instructionsContainer: {
marginBottom: 16,
},
instructionsTitle: {
color: '#0f172a',
fontWeight: '600',
fontSize: 16,
marginBottom: 4,
},
instructionsText: {
color: '#475569',
fontSize: 14,
lineHeight: 20,
},
cameraWrapper: {
backgroundColor: '#0f172a',
borderRadius: 16,
@@ -147,31 +157,14 @@ const styles = StyleSheet.create({
height: '100%',
backgroundColor: '#0f172a',
},
overlay: {
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
backgroundColor: 'rgba(15, 23, 42, 0.75)',
paddingHorizontal: 16,
paddingVertical: 12,
},
overlayTitle: {
color: '#f8fafc',
fontWeight: '600',
fontSize: 16,
marginBottom: 4,
},
overlayText: {
color: '#e2e8f0',
fontSize: 14,
},
statusContainer: {
marginBottom: 16,
alignItems: 'center',
},
statusRow: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
gap: 8,
},
statusText: {
@@ -191,6 +184,9 @@ const styles = StyleSheet.create({
flexDirection: 'row',
gap: 12,
},
actionsWithResult: {
marginBottom: 16,
},
primaryButton: {
flex: 1,
backgroundColor: '#0f172a',
@@ -215,10 +211,6 @@ const styles = StyleSheet.create({
fontSize: 15,
fontWeight: '600',
},
headerAction: {
color: '#2563eb',
fontWeight: '600',
},
centeredState: {
flex: 1,
alignItems: 'center',