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:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,2 @@
|
||||
#Mon May 25 16:29:05 CEST 2026
|
||||
gradle.version=8.9
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user