Add CameraPreviewManager and CameraPreviewView for camera functionality
This commit is contained in:
111
App.tsx
111
App.tsx
@@ -1,3 +1,4 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
StatusBar,
|
StatusBar,
|
||||||
useColorScheme,
|
useColorScheme,
|
||||||
@@ -11,7 +12,6 @@ import {
|
|||||||
ScrollView,
|
ScrollView,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||||
import { useState, useEffect } from 'react';
|
|
||||||
|
|
||||||
import Login from './src/components/Login';
|
import Login from './src/components/Login';
|
||||||
import Register from './src/components/Register';
|
import Register from './src/components/Register';
|
||||||
@@ -21,9 +21,10 @@ import { ScannerScreen } from './src/screens/ScannerScreen';
|
|||||||
|
|
||||||
type Screen = 'login' | 'register' | 'home' | 'scanner';
|
type Screen = 'login' | 'register' | 'home' | 'scanner';
|
||||||
|
|
||||||
const { MyNativeModule } = NativeModules;
|
// ✅ Correct native module
|
||||||
|
const { ICameraSDK } = NativeModules;
|
||||||
|
|
||||||
function App() {
|
function App(): JSX.Element {
|
||||||
const isDarkMode = useColorScheme() === 'dark';
|
const isDarkMode = useColorScheme() === 'dark';
|
||||||
|
|
||||||
const [currentScreen, setCurrentScreen] = useState<Screen>('login');
|
const [currentScreen, setCurrentScreen] = useState<Screen>('login');
|
||||||
@@ -38,10 +39,10 @@ function App() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initializeApp();
|
initializeApp();
|
||||||
|
|
||||||
console.log('MyNativeModule:', MyNativeModule);
|
console.log('ICameraSDK:', ICameraSDK);
|
||||||
MyNativeModule?.greet?.('John').then((msg: any) => {
|
ICameraSDK?.greet?.('John')
|
||||||
console.log(msg);
|
.then((msg: string) => console.log(msg))
|
||||||
});
|
.catch(console.error);
|
||||||
|
|
||||||
const unsubscribe = networkService.addListener(networkState => {
|
const unsubscribe = networkService.addListener(networkState => {
|
||||||
setIsOnline(networkState.isConnected);
|
setIsOnline(networkState.isConnected);
|
||||||
@@ -53,15 +54,14 @@ function App() {
|
|||||||
const initializeApp = async () => {
|
const initializeApp = async () => {
|
||||||
try {
|
try {
|
||||||
await authAPI.initialize();
|
await authAPI.initialize();
|
||||||
|
const loggedIn = await authAPI.isLoggedIn();
|
||||||
const isLoggedIn = await authAPI.isLoggedIn();
|
if (loggedIn) {
|
||||||
if (isLoggedIn) {
|
|
||||||
const user = await authAPI.getCurrentUser();
|
const user = await authAPI.getCurrentUser();
|
||||||
setCurrentUser(user);
|
setCurrentUser(user);
|
||||||
setCurrentScreen('home');
|
setCurrentScreen('home');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (e) {
|
||||||
console.error('Initialization error:', error);
|
console.error(e);
|
||||||
} finally {
|
} finally {
|
||||||
setIsInitialized(true);
|
setIsInitialized(true);
|
||||||
}
|
}
|
||||||
@@ -93,7 +93,6 @@ function App() {
|
|||||||
const handleLogout = async () => {
|
const handleLogout = async () => {
|
||||||
try {
|
try {
|
||||||
await authAPI.logout();
|
await authAPI.logout();
|
||||||
setQrCode(null);
|
|
||||||
navigateToLogin();
|
navigateToLogin();
|
||||||
Alert.alert('Success', 'Logged out successfully');
|
Alert.alert('Success', 'Logged out successfully');
|
||||||
} catch {
|
} catch {
|
||||||
@@ -101,35 +100,20 @@ function App() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const showAppStatus = async () => {
|
|
||||||
try {
|
|
||||||
const status = await authAPI.getAppStatus();
|
|
||||||
Alert.alert(
|
|
||||||
'App Status',
|
|
||||||
`Network: ${status.network.isOnline ? 'Online' : 'Offline'}
|
|
||||||
Users: ${status.storage.totalUsers}
|
|
||||||
Current User: ${status.authentication.currentUser || 'None'}
|
|
||||||
Logged In: ${status.authentication.isLoggedIn ? 'Yes' : 'No'}`
|
|
||||||
);
|
|
||||||
} catch {
|
|
||||||
Alert.alert('Error', 'Failed to get app status');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const generateQRCode = async () => {
|
const generateQRCode = async () => {
|
||||||
if (!currentUser?.email) {
|
if (!currentUser?.email || !ICameraSDK) {
|
||||||
Alert.alert('Error', 'No user email available');
|
Alert.alert('Error', 'Native QR module not available');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsGeneratingQR(true);
|
setIsGeneratingQR(true);
|
||||||
try {
|
try {
|
||||||
const qrData = `User: ${currentUser.fullName}\nEmail: ${currentUser.email}`;
|
const qrData = `User: ${currentUser.fullName}\nEmail: ${currentUser.email}`;
|
||||||
const base64Image = await MyNativeModule.generateQRCode(qrData, 300, 300);
|
const base64 = await ICameraSDK.generateQRCode(qrData, 300, 300);
|
||||||
setQrCode(base64Image);
|
setQrCode(base64);
|
||||||
} catch (error) {
|
} catch (e) {
|
||||||
console.error(error);
|
console.error(e);
|
||||||
Alert.alert('Error', 'Failed to generate QR code');
|
Alert.alert('Error', 'QR generation failed');
|
||||||
} finally {
|
} finally {
|
||||||
setIsGeneratingQR(false);
|
setIsGeneratingQR(false);
|
||||||
}
|
}
|
||||||
@@ -146,7 +130,7 @@ Logged In: ${status.authentication.isLoggedIn ? 'Yes' : 'No'}`
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
'Scanned Successfully',
|
'Scanned',
|
||||||
`Code: ${result.code}\nFormat: ${result.format}`,
|
`Code: ${result.code}\nFormat: ${result.format}`,
|
||||||
[{ text: 'OK', onPress: () => setCurrentScreen('home') }]
|
[{ text: 'OK', onPress: () => setCurrentScreen('home') }]
|
||||||
);
|
);
|
||||||
@@ -181,50 +165,46 @@ Logged In: ${status.authentication.isLoggedIn ? 'Yes' : 'No'}`
|
|||||||
|
|
||||||
case 'home':
|
case 'home':
|
||||||
return (
|
return (
|
||||||
<ScrollView style={styles.homeContainer} contentContainerStyle={styles.scrollContent}>
|
<ScrollView
|
||||||
|
style={styles.homeContainer}
|
||||||
|
contentContainerStyle={styles.scrollContent}
|
||||||
|
>
|
||||||
<Text style={styles.welcomeText}>
|
<Text style={styles.welcomeText}>
|
||||||
Welcome, {currentUser?.fullName || 'User'}
|
Welcome, {currentUser?.fullName || 'User'}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
{qrCode && (
|
{qrCode && <Image source={{ uri: qrCode }} style={styles.qrImage} />}
|
||||||
<Image source={{ uri: qrCode }} style={styles.qrImage} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
<TouchableOpacity style={styles.scannerButton} onPress={openScanner}>
|
<TouchableOpacity style={styles.button} onPress={openScanner}>
|
||||||
<Text style={styles.buttonText}>Scan QR / Barcode</Text>
|
<Text style={styles.buttonText}>Scan QR / Barcode</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={styles.qrButton}
|
style={[styles.button, styles.qrButton]}
|
||||||
onPress={generateQRCode}
|
onPress={generateQRCode}
|
||||||
disabled={isGeneratingQR}
|
disabled={isGeneratingQR}
|
||||||
>
|
>
|
||||||
<Text style={styles.buttonText}>
|
<Text style={styles.buttonText}>
|
||||||
{isGeneratingQR ? 'Generating...' : 'Generate QR Code'}
|
{isGeneratingQR ? 'Generating...' : 'Generate QR'}
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
<TouchableOpacity style={styles.statusButton} onPress={showAppStatus}>
|
<TouchableOpacity
|
||||||
<Text style={styles.buttonText}>App Status</Text>
|
style={[styles.button, styles.logoutButton]}
|
||||||
</TouchableOpacity>
|
onPress={handleLogout}
|
||||||
|
>
|
||||||
<TouchableOpacity style={styles.logoutButton} onPress={handleLogout}>
|
|
||||||
<Text style={styles.buttonText}>Logout</Text>
|
<Text style={styles.buttonText}>Logout</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/* -------------------- LOADING -------------------- */
|
|
||||||
if (!isInitialized) {
|
if (!isInitialized) {
|
||||||
return (
|
return (
|
||||||
<SafeAreaProvider>
|
<SafeAreaProvider>
|
||||||
<View style={styles.loadingContainer}>
|
<View style={styles.loading}>
|
||||||
<Text style={styles.loadingText}>Initializing...</Text>
|
<Text>Initializing...</Text>
|
||||||
</View>
|
</View>
|
||||||
</SafeAreaProvider>
|
</SafeAreaProvider>
|
||||||
);
|
);
|
||||||
@@ -238,18 +218,25 @@ Logged In: ${status.authentication.isLoggedIn ? 'Yes' : 'No'}`
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
|
|
||||||
|
/* -------------------- STYLES -------------------- */
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
loadingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
loading: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
||||||
loadingText: { fontSize: 18, fontWeight: '600' },
|
|
||||||
homeContainer: { flex: 1 },
|
homeContainer: { flex: 1 },
|
||||||
scrollContent: { alignItems: 'center', padding: 20 },
|
scrollContent: { alignItems: 'center', padding: 20 },
|
||||||
welcomeText: { fontSize: 24, fontWeight: '700', marginBottom: 20 },
|
welcomeText: { fontSize: 24, fontWeight: '700', marginBottom: 20 },
|
||||||
qrImage: { width: 300, height: 300, marginBottom: 20 },
|
qrImage: { width: 300, height: 300, marginBottom: 20 },
|
||||||
scannerButton: { backgroundColor: '#9333ea', padding: 14, borderRadius: 20, marginBottom: 10 },
|
button: {
|
||||||
qrButton: { backgroundColor: '#10b981', padding: 14, borderRadius: 20, marginBottom: 10 },
|
backgroundColor: '#6366f1',
|
||||||
statusButton: { backgroundColor: '#3bb6d8', padding: 14, borderRadius: 20, marginBottom: 10 },
|
padding: 14,
|
||||||
logoutButton: { backgroundColor: '#ff6b6b', padding: 14, borderRadius: 20 },
|
borderRadius: 18,
|
||||||
|
marginBottom: 12,
|
||||||
|
minWidth: 220,
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
qrButton: { backgroundColor: '#10b981' },
|
||||||
|
logoutButton: { backgroundColor: '#ef4444' },
|
||||||
buttonText: { color: '#fff', fontWeight: '600' },
|
buttonText: { color: '#fff', fontWeight: '600' },
|
||||||
});
|
});
|
||||||
|
|
||||||
export default App;
|
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package com.lynkeduppro.camera
|
||||||
|
|
||||||
|
import com.facebook.react.uimanager.SimpleViewManager
|
||||||
|
import com.facebook.react.uimanager.ThemedReactContext
|
||||||
|
import com.facebook.react.uimanager.annotations.ReactProp
|
||||||
|
import com.facebook.react.bridge.LifecycleEventListener
|
||||||
|
|
||||||
|
class CameraPreviewManager :
|
||||||
|
SimpleViewManager<CameraPreviewView>(),
|
||||||
|
LifecycleEventListener {
|
||||||
|
|
||||||
|
private var previewView: CameraPreviewView? = null
|
||||||
|
|
||||||
|
override fun getName(): String = "CameraPreviewView"
|
||||||
|
|
||||||
|
override fun createViewInstance(reactContext: ThemedReactContext): CameraPreviewView {
|
||||||
|
previewView = CameraPreviewView(reactContext)
|
||||||
|
reactContext.addLifecycleEventListener(this)
|
||||||
|
return previewView!!
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "active")
|
||||||
|
fun setActive(view: CameraPreviewView, active: Boolean) {
|
||||||
|
if (active && reactContext is androidx.lifecycle.LifecycleOwner) {
|
||||||
|
view.startCamera(reactContext as androidx.lifecycle.LifecycleOwner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onHostResume() {
|
||||||
|
previewView?.let {
|
||||||
|
if (reactContext is androidx.lifecycle.LifecycleOwner) {
|
||||||
|
it.startCamera(reactContext as androidx.lifecycle.LifecycleOwner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onHostPause() {}
|
||||||
|
override fun onHostDestroy() {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.lynkeduppro.camera
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.camera.core.CameraSelector
|
||||||
|
import androidx.camera.core.Preview
|
||||||
|
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||||
|
import androidx.camera.view.PreviewView
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
|
||||||
|
class CameraPreviewView(context: Context) : PreviewView(context) {
|
||||||
|
|
||||||
|
fun startCamera(lifecycleOwner: LifecycleOwner) {
|
||||||
|
val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
|
||||||
|
|
||||||
|
cameraProviderFuture.addListener({
|
||||||
|
val cameraProvider = cameraProviderFuture.get()
|
||||||
|
|
||||||
|
val preview = Preview.Builder().build().also {
|
||||||
|
it.setSurfaceProvider(surfaceProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
|
||||||
|
|
||||||
|
cameraProvider.unbindAll()
|
||||||
|
cameraProvider.bindToLifecycle(
|
||||||
|
lifecycleOwner,
|
||||||
|
cameraSelector,
|
||||||
|
preview
|
||||||
|
)
|
||||||
|
}, ContextCompat.getMainExecutor(context))
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user