Initialize Schnappix Photo Booth application with custom UVC camera, silent Wi-Fi IPP printing, local gallery storage, and kiosk screen pinning support

This commit is contained in:
2026-05-25 16:44:15 +02:00
parent 5958319772
commit 872e1ffc86
51 changed files with 8931 additions and 1 deletions
@@ -0,0 +1,2 @@
#Mon May 25 16:29:05 CEST 2026
gradle.version=8.9
+21
View File
@@ -0,0 +1,21 @@
apply plugin: 'com.android.library'
apply plugin: 'expo-module-gradle-plugin'
apply plugin: 'org.jetbrains.kotlin.android'
android {
namespace "expo.modules.kioskmode"
compileSdkVersion findProperty("expo.compileSdkVersion") ?: 34
defaultConfig {
minSdkVersion findProperty("expo.minSdkVersion") ?: 23
targetSdkVersion findProperty("expo.targetSdkVersion") ?: 34
}
buildFeatures {
buildConfig true
}
}
dependencies {
implementation 'com.facebook.react:react-android'
}
@@ -0,0 +1,25 @@
package expo.modules.kioskmode
import android.app.admin.DeviceAdminReceiver
import android.content.Context
import android.content.Intent
import android.widget.Toast
import android.content.ComponentName
class AdminReceiver : DeviceAdminReceiver() {
override fun onEnabled(context: Context, intent: Intent) {
super.onEnabled(context, intent)
Toast.makeText(context, "Schnappix Admin Enabled", Toast.LENGTH_SHORT).show()
}
override fun onDisabled(context: Context, intent: Intent) {
super.onDisabled(context, intent)
Toast.makeText(context, "Schnappix Admin Disabled", Toast.LENGTH_SHORT).show()
}
companion object {
fun getComponentName(context: Context): ComponentName {
return ComponentName(context.applicationContext, AdminReceiver::class.java)
}
}
}
@@ -0,0 +1,63 @@
package expo.modules.kioskmode
import android.app.Activity
import android.app.ActivityManager
import android.app.admin.DevicePolicyManager
import android.content.Context
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
class KioskModeModule : Module() {
override fun definition() = ModuleDefinition {
Name("KioskMode")
Function("startKiosk") {
val activity = appContext.currentActivity ?: return@Function false
val dpm = activity.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val packageName = activity.packageName
val adminComponent = AdminReceiver.getComponentName(activity)
activity.runOnUiThread {
try {
if (dpm.isDeviceOwnerApp(packageName)) {
// Whitelist package to enable true Kiosk Mode
dpm.setLockTaskPackages(adminComponent, arrayOf(packageName))
}
activity.startLockTask()
} catch (e: Exception) {
e.printStackTrace()
}
}
return@Function true
}
Function("stopKiosk") {
val activity = appContext.currentActivity ?: return@Function false
activity.runOnUiThread {
try {
activity.stopLockTask()
} catch (e: Exception) {
e.printStackTrace()
}
}
return@Function true
}
Function("isKioskActive") {
val activity = appContext.currentActivity ?: return@Function false
val am = activity.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
return@Function if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
am.lockTaskModeState != ActivityManager.LOCK_TASK_MODE_NONE
} else {
@Suppress("DEPRECATION")
am.isInLockTaskMode
}
}
Function("isDeviceOwner") {
val activity = appContext.currentActivity ?: return@Function false
val dpm = activity.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
return@Function dpm.isDeviceOwnerApp(activity.packageName)
}
}
}
@@ -0,0 +1,8 @@
{
"platforms": ["android"],
"android": {
"modules": [
"expo.modules.kioskmode.KioskModeModule"
]
}
}
+35
View File
@@ -0,0 +1,35 @@
import { requireNativeModule } from 'expo-modules-core';
import { Platform } from 'react-native';
let KioskMode: any = null;
if (Platform.OS === 'android') {
try {
KioskMode = requireNativeModule('KioskMode');
} catch (e) {
console.warn('KioskMode native module not found');
}
}
export interface KioskModeInterface {
startKiosk(): boolean;
stopKiosk(): boolean;
isKioskActive(): boolean;
isDeviceOwner(): boolean;
}
const KioskModeProxy: KioskModeInterface = {
startKiosk() {
return KioskMode ? KioskMode.startKiosk() : false;
},
stopKiosk() {
return KioskMode ? KioskMode.stopKiosk() : false;
},
isKioskActive() {
return KioskMode ? KioskMode.isKioskActive() : false;
},
isDeviceOwner() {
return KioskMode ? KioskMode.isDeviceOwner() : false;
}
};
export default KioskModeProxy;
+23
View File
@@ -0,0 +1,23 @@
apply plugin: 'com.android.library'
apply plugin: 'expo-module-gradle-plugin'
apply plugin: 'org.jetbrains.kotlin.android'
android {
namespace "expo.modules.usbcamera"
compileSdkVersion findProperty("expo.compileSdkVersion") ?: 34
defaultConfig {
minSdkVersion 23 // libausbc requires min SDK 23
targetSdkVersion findProperty("expo.targetSdkVersion") ?: 34
}
buildFeatures {
buildConfig true
}
}
dependencies {
implementation 'com.facebook.react:react-android'
// AndroidUSBCamera library for USB/UVC Webcams
implementation 'com.github.jiangdongguo.AndroidUSBCamera:libausbc:3.3.3'
}
@@ -0,0 +1,27 @@
package expo.modules.usbcamera
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
import expo.modules.kotlin.Promise
class UsbCameraModule : Module() {
override fun definition() = ModuleDefinition {
Name("UsbCamera")
View(UsbCameraView::class) {
Events("onCameraReady")
AsyncFunction("takePicture") { view: UsbCameraView, savePath: String, promise: Promise ->
view.takePicture(savePath, promise)
}
AsyncFunction("isCameraConnected") { view: UsbCameraView, promise: Promise ->
promise.resolve(view.isCameraConnected())
}
OnViewDestroys { view ->
view.onDestroy()
}
}
}
}
@@ -0,0 +1,87 @@
package expo.modules.usbcamera
import android.content.Context
import android.widget.FrameLayout
import android.util.AttributeSet
import com.jiangdg.ausbc.CameraClient
import com.jiangdg.ausbc.widget.AspectRatioTextureView
import com.jiangdg.ausbc.camera.bean.CameraRequest
import com.jiangdg.ausbc.camera.CameraUvcStrategy
import com.jiangdg.ausbc.callback.ICaptureCallBack
import expo.modules.kotlin.views.ExpoView
import expo.modules.kotlin.AppContext
import expo.modules.kotlin.Promise
class UsbCameraView(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
private val textureView: AspectRatioTextureView
private var cameraClient: CameraClient? = null
init {
textureView = AspectRatioTextureView(context)
addView(textureView, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
cameraClient = CameraClient.newBuilder(context)
.setCameraStrategy(CameraUvcStrategy(context))
.setEnableGLES(true)
.build()
post {
startPreview()
}
}
private fun startPreview() {
try {
val cameraRequest = CameraRequest.CameraRequestBuilder()
.setPreviewWidth(1280)
.setPreviewHeight(720)
.create()
cameraClient?.openCamera(textureView, cameraRequest)
} catch (e: Exception) {
e.printStackTrace()
}
}
fun takePicture(savePath: String, promise: Promise) {
val client = cameraClient
if (client == null) {
promise.reject("ERR_CAMERA_NOT_READY", "Camera client is not initialized", null)
return
}
try {
client.captureImage(object : ICaptureCallBack {
override fun onBegin() {
// Do nothing
}
override fun onError(error: String?) {
promise.reject("ERR_CAPTURE_FAILED", error ?: "Unknown error", null)
}
override fun onComplete(path: String?) {
if (path != null) {
promise.resolve(path)
} else {
promise.reject("ERR_CAPTURE_FAILED", "File path was null", null)
}
}
}, savePath)
} catch (e: Exception) {
promise.reject("ERR_CAPTURE_EXCEPTION", e.message ?: "Capture threw an exception", e)
}
}
fun isCameraConnected(): Boolean {
return cameraClient?.isCameraOpened ?: false
}
fun onDestroy() {
try {
cameraClient?.closeCamera()
} catch (e: Exception) {
e.printStackTrace()
}
cameraClient = null
}
}
@@ -0,0 +1,8 @@
{
"platforms": ["android"],
"android": {
"modules": [
"expo.modules.usbcamera.UsbCameraModule"
]
}
}
+20
View File
@@ -0,0 +1,20 @@
import { requireNativeViewManager } from 'expo-modules-core';
import * as React from 'react';
import { ViewProps, Platform } from 'react-native';
const viewName = 'UsbCamera';
export interface UsbCameraViewProps extends ViewProps {
// Add props here if we decide to pass them from JS
}
export interface UsbCameraRef {
takePicture(savePath: string): Promise<string>;
isCameraConnected(): Promise<boolean>;
}
// Export the native view manager component
export const UsbCameraView: React.ComponentType<UsbCameraViewProps & { ref?: React.RefObject<any> }> =
Platform.OS === 'android'
? requireNativeViewManager(viewName)
: (() => null) as any;