Update camera screens, configuration, and build setup

This commit is contained in:
2026-05-30 19:13:04 +02:00
parent d8e397eac1
commit 305d8fd449
14 changed files with 144 additions and 373 deletions
+49 -31
View File
@@ -1,6 +1,6 @@
import ipp from 'ipp-encoder';
import { Buffer } from 'buffer';
import * as FileSystem from 'expo-file-system/legacy';
import { File } from 'expo-file-system';
export interface PrintOptions {
ipAddress: string;
@@ -23,9 +23,8 @@ export async function printImageLocal(imageUri: string, options: PrintOptions):
}
// 1. Read the image file from local storage as Base64 and convert to binary Buffer
const base64Data = await FileSystem.readAsStringAsync(imageUri, {
encoding: FileSystem.EncodingType.Base64,
});
const file = new File(imageUri);
const base64Data = await file.base64();
const imageBuffer = Buffer.from(base64Data, 'base64');
// 2. Build the IPP Print-Job request structure
@@ -57,36 +56,55 @@ export async function printImageLocal(imageUri: string, options: PrintOptions):
// 5. Send the POST request to the printer via standard HTTP on port 631
const printerUrl = `http://${ipAddress}:631/ipp/print`;
console.log(`Sending silent print job to ${printerUrl}...`);
const response = await fetch(printerUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/ipp',
},
// Pass as Uint8Array to ensure React Native fetch treats it as raw binary
body: new Uint8Array(finalPayload),
});
const maxRetries = 3;
let delay = 1000;
let lastError: any = null;
if (!response.ok) {
throw new Error(`Drucker antwortete mit HTTP-Status ${response.status}: ${response.statusText}`);
}
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
console.log(`Sending silent print job to ${printerUrl} (Attempt ${attempt}/${maxRetries})...`);
const response = await fetch(printerUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/ipp',
},
// Pass as Uint8Array to ensure React Native fetch treats it as raw binary
body: new Uint8Array(finalPayload),
});
// 6. Read and decode the response from the printer to verify success
const responseArrayBuffer = await response.arrayBuffer();
const responseBuffer = Buffer.from(responseArrayBuffer);
if (!response.ok) {
throw new Error(`Drucker antwortete mit HTTP-Status ${response.status}: ${response.statusText}`);
}
try {
const decodedResponse = ipp.response.decode(responseBuffer);
const statusCode = decodedResponse.statusCode;
// IPP success status is 0x0000 (successful-ok)
if (statusCode !== 0x0000) {
throw new Error(`Drucker lieferte IPP-Fehlercode: 0x${statusCode.toString(16)}`);
// 6. Read and decode the response from the printer to verify success
const responseArrayBuffer = await response.arrayBuffer();
const responseBuffer = Buffer.from(responseArrayBuffer);
try {
const decodedResponse = ipp.response.decode(responseBuffer);
const statusCode = decodedResponse.statusCode;
// IPP success status is 0x0000 (successful-ok)
if (statusCode !== 0x0000) {
throw new Error(`Drucker lieferte IPP-Fehlercode: 0x${statusCode.toString(16)}`);
}
console.log('Silent print job accepted successfully!');
} catch (e: any) {
// If decoding fails, we still assume success since the network request completed successfully
console.warn('Failed to parse IPP response, but network request succeeded:', e.message);
}
return; // Success, exit retry loop
} catch (error: any) {
console.warn(`Print attempt ${attempt} failed: ${error.message}`);
lastError = error;
if (attempt < maxRetries) {
await new Promise((resolve) => setTimeout(resolve, delay));
delay *= 2; // exponential backoff
}
}
console.log('Silent print job accepted successfully!');
} catch (e: any) {
// If decoding fails, we still assume success since the network request completed successfully
console.warn('Failed to parse IPP response, but network request succeeded:', e.message);
}
throw new Error(`Druckauftrag nach ${maxRetries} Versuchen fehlgeschlagen. Letzter Fehler: ${lastError?.message}`);
}
+5 -6
View File
@@ -1,4 +1,4 @@
import * as FileSystem from 'expo-file-system/legacy';
import { File, Paths } from 'expo-file-system';
export interface AppSettings {
countdownDuration: number; // in seconds
@@ -7,7 +7,7 @@ export interface AppSettings {
kioskModeEnabled: boolean;
}
const SETTINGS_FILE = `${FileSystem.documentDirectory}settings.json`;
const settingsFile = new File(Paths.document, 'settings.json');
const DEFAULT_SETTINGS: AppSettings = {
countdownDuration: 3,
@@ -21,9 +21,8 @@ const DEFAULT_SETTINGS: AppSettings = {
*/
export async function loadSettings(): Promise<AppSettings> {
try {
const fileInfo = await FileSystem.getInfoAsync(SETTINGS_FILE);
if (fileInfo.exists) {
const content = await FileSystem.readAsStringAsync(SETTINGS_FILE);
if (settingsFile.exists) {
const content = await settingsFile.text();
const parsed = JSON.parse(content);
return { ...DEFAULT_SETTINGS, ...parsed };
}
@@ -39,7 +38,7 @@ export async function loadSettings(): Promise<AppSettings> {
export async function saveSettings(settings: AppSettings): Promise<void> {
try {
const content = JSON.stringify(settings, null, 2);
await FileSystem.writeAsStringAsync(SETTINGS_FILE, content);
settingsFile.write(content);
console.log('Settings saved successfully:', content);
} catch (e) {
console.error('Failed to save settings:', e);
+14 -10
View File
@@ -1,5 +1,4 @@
import * as MediaLibrary from 'expo-media-library/legacy';
import { Platform } from 'react-native';
import { Asset, Album, getPermissionsAsync, requestPermissionsAsync } from 'expo-media-library';
const ALBUM_NAME = 'Schnappix';
@@ -8,13 +7,13 @@ const ALBUM_NAME = 'Schnappix';
* @returns Promise<boolean> True if permissions are granted.
*/
export async function requestStoragePermission(): Promise<boolean> {
const { status, canAskAgain } = await MediaLibrary.getPermissionsAsync();
const { status, canAskAgain } = await getPermissionsAsync();
if (status === 'granted') {
return true;
}
if (canAskAgain) {
const { status: newStatus } = await MediaLibrary.requestPermissionsAsync();
const { status: newStatus } = await requestPermissionsAsync();
return newStatus === 'granted';
}
@@ -34,25 +33,30 @@ export async function saveToGallery(localUri: string): Promise<string> {
}
// 1. Create a media asset from the local file
const asset = await MediaLibrary.createAssetAsync(localUri);
const asset = await Asset.create(localUri);
try {
// 2. Check if the "Schnappix" album already exists
let album = await MediaLibrary.getAlbumAsync(ALBUM_NAME);
const album = await Album.get(ALBUM_NAME);
if (!album) {
// 3. If it doesn't exist, create it with our asset
await MediaLibrary.createAlbumAsync(ALBUM_NAME, asset, false);
await Album.create(ALBUM_NAME, [asset], false);
console.log(`Created new album "${ALBUM_NAME}" and saved photo.`);
} else {
// 4. If it exists, add our asset to it
await MediaLibrary.addAssetsToAlbumAsync([asset], album, false);
await album.add(asset);
console.log(`Saved photo to existing album "${ALBUM_NAME}".`);
}
return asset.uri;
const assetUri = await asset.getUri();
return assetUri;
} catch (e: any) {
console.error('Error saving asset to album, returning fallback asset URI:', e);
return asset.uri;
try {
return await asset.getUri();
} catch {
return asset.id;
}
}
}