feat(intial auth): intial auth setup for the scanner
Some checks failed
Build and Push LST Docker Image / docker (push) Has been cancelled

This commit is contained in:
2026-05-05 19:48:36 -05:00
parent 4e0cf8c54c
commit cd13360cfb
46 changed files with 7847 additions and 206 deletions

View File

@@ -15,7 +15,7 @@
"foregroundImage": "./assets/adaptive-icon-white.png",
"backgroundColor": "#ffffff"
},
"versionCode": 21,
"versionCode": 23,
"minSupportedVersionCode": 21,
"predictiveBackGestureEnabled": false,
"package": "net.alpla.lst.mobile"
@@ -26,7 +26,7 @@
"bundler": "metro"
},
"plugins": [
"./plugins/withZebraScanner",
"./plugins/withZebraDataWedge",
"expo-router",
[
"expo-splash-screen",

View File

@@ -9,7 +9,7 @@
"ios": "expo run:ios",
"web": "expo start --web",
"lint": "expo lint",
"build:apk:clean": "expo prebuild --clean && cd android && gradlew.bat assembleRelease && npm run copy:apk",
"build:apk:clean": "expo prebuild --clean && cd android && gradlew.bat clean && gradlew.bat assembleRelease && npm run copy:apk",
"build:apk": "expo prebuild && cd android && gradlew.bat assembleRelease && npm run copy:apk",
"build:mobile": "cd scripts && node runBuild.ts",
"build:mobile:bump": "cd scripts && node runBuild.ts --bump",

View File

@@ -145,34 +145,32 @@ class ZebraScannerModule(
Thread.sleep(500)
val barcodeConfig = Bundle().apply {
putString("PLUGIN_NAME", "BARCODE")
putString("RESET_CONFIG", "true")
val barcodeConfig = Bundle().apply {
putString("PLUGIN_NAME", "BARCODE")
putString("RESET_CONFIG", "true")
val isLegacyTc8000 =
android.os.Build.MODEL.contains("TC8000", ignoreCase = true)
val props = Bundle().apply {
putString("scanner_input_enabled", "true")
val props = Bundle().apply {
putString("scanner_input_enabled", "true")
// Baseline that should be safe on old and new Zebra devices
putString("scanner_selection", "auto")
if (!isLegacyTc8000) {
// Newer Zebra devices
// Auto-select internal scanner
putString("scanner_selection", "auto")
putString("scanner_selection_by_identifier", "AUTO")
// Hardware trigger behavior
putString("hardware_trigger_enabled", "true")
putString("trigger_mode", "2") // HARD trigger
putString("trigger_mode", "2") // 2 = HARD trigger
// Disable Zebra's loud initial decode feedback
putString("decode_audio_feedback_uri", "")
putString("decode_haptic_feedback", "false")
putString("decode_led_feedback", "false")
}
}
putBundle("PARAM_LIST", props)
}
// add in wake on trigger
putString("trigger_wakeup_scan", "true");
}
putBundle("PARAM_LIST", props)
}
val intentConfig = Bundle().apply {
putString("PLUGIN_NAME", "INTENT")

View File

@@ -1,13 +1,26 @@
import React from 'react'
import { Text, View } from 'react-native'
import React from "react";
import { Text, View } from "react-native";
import { Button } from "../../components/ui/button";
export default function Logs() {
return (
<View style={{
const getInfo = async () => {
const info = "ho";
console.log(info);
};
return (
<View
style={{
flex: 1,
//justifyContent: "center",
alignItems: "center",
marginTop: 50,
}}><Text>Logs</Text></View>
)
}}
>
<Text>Logs</Text>
<Button onPress={getInfo}>
<Text>Check info</Text>
</Button>
</View>
);
}

View File

@@ -1,11 +1,9 @@
import React from "react";
import { View } from "react-native";
import { useAppStore } from "../../hooks/useAppStore";
import ProdScanner from "../../components/ProdScanner";
import { View } from "react-native";
import LSTScanner from "../../components/LSTScanner";
import ProdScanner from "../../components/ProdScanner";
import { useAppStore } from "../../hooks/useAppStore";
export default function scanner() {
export default function Scanner() {
const serverPort = useAppStore((s) => s.serverPort);
return (
<View
@@ -16,7 +14,11 @@ export default function scanner() {
marginTop: 50,
}}
>
{parseInt(serverPort || "0", 10) >= 50000 ? <ProdScanner /> : <LSTScanner />}
{parseInt(serverPort || "0", 10) >= 50000 ? (
<ProdScanner />
) : (
<LSTScanner />
)}
</View>
);
}

View File

@@ -4,6 +4,7 @@ import { Redirect, useRouter } from "expo-router";
import { useEffect, useState } from "react";
import { ActivityIndicator, Text, View } from "react-native";
import { useAppStore } from "../hooks/useAppStore";
import { useMobileAuthStore } from "../hooks/useMobileAuth";
import { useServerStore } from "../hooks/useServerCheck";
import { devDelay } from "../lib/devMode";
@@ -12,6 +13,7 @@ export default function Index() {
const [message, setMessage] = useState(<Text>Starting app...</Text>);
const [ready, setReady] = useState(false);
const setServerVersion = useServerStore((s) => s.setServerVersion);
//const { isUnlocked } = useMobileAuthStore();
const hasHydrated = useAppStore((s) => s.hasHydrated);
const serverPort = useAppStore((s) => s.serverPort);
@@ -86,7 +88,7 @@ export default function Index() {
// TODO if theres an update go to update screen message :D
setMessage(<Text>Opening LST scan app</Text>);
await devDelay(3250);
//router.replace("/scanner");
setReady(true);
} catch (error) {
console.log("Startup error", error);
@@ -104,6 +106,9 @@ export default function Index() {
setServerVersion,
]);
// if (ready && !isUnlocked) {
// return <Redirect href={"/login"} />;
// }
if (ready) {
return <Redirect href="/(tabs)/scanner" />;
}

View File

@@ -0,0 +1,52 @@
import axios from "axios";
import Constants from "expo-constants";
import { useRouter } from "expo-router";
import { useState } from "react";
import { Alert, Button, Text, View } from "react-native";
import { Input } from "../components/ui/input";
import { useAppStore } from "../hooks/useAppStore";
import { useMobileAuthStore } from "../hooks/useMobileAuth";
export default function Login() {
const { setUser } = useMobileAuthStore();
const serverPort = useAppStore((s) => s.serverPort);
const serverIp = useAppStore((s) => s.serverIp);
const onLogin = async () => {
try {
const res = await axios.get(
`http://${serverIp}:${parseInt(serverPort || "0", 10) >= 50000 ? "3000" : serverPort}/lst/api/mobile/version`,
{
timeout: 5000,
},
);
console.log(res.data);
} catch (error) {}
};
return (
<View
style={{
flex: 1,
//justifyContent: "center",
alignItems: "center",
marginTop: 50,
}}
>
<View className="flex items-center m-5">
<Text style={{ fontSize: 20, fontWeight: "600" }}>
LST Scanner Login
</Text>
<View className="w-64 p-4">
<Input
className="w-fit"
keyboardType="number-pad"
textContentType="oneTimeCode"
placeholder="Pin number"
/>
</View>
</View>
<Button title="Login" onPress={onLogin} />
</View>
);
}

View File

@@ -154,10 +154,10 @@ export default function Setup() {
marginBottom: 12,
}}
>
<Text className="text-[12] color-#666">
<Text className="text-sm color-[#312f2f]">
App v{version}-{build}
</Text>
<Text className="text-[12] color-#666">
<Text className="text-sm color-[#312f2f]">
Server version - v{server?.versionName}-{server?.versionCode}
</Text>
</View>

View File

@@ -1,24 +1,51 @@
import React from 'react'
import { View, Text } from 'react-native'
import { useCallback, useEffect } from "react";
import { Text, View } from "react-native";
import { zebraScanner } from "../lib/ZebraScanner";
export default function LSTScanner() {
return (
<View><View style={{ alignItems: "center", margin: 10 }}>
<Text style={{ fontSize: 20, fontWeight: "600" }}>LST Scanner</Text>
</View>
<View
style={{
marginTop: 50,
alignItems: "center",
}}
>
<Text>Relocate</Text>
<Text>0 / 4</Text>
</View>
{/* <View>
const handleScan = useCallback(async (scan: any) => {
console.log(scan);
}, []);
const clearScans = () => {
// add in
};
//console.log(lastScan);
useEffect(() => {
zebraScanner.ensureProfile();
zebraScanner.startListening();
const sub = zebraScanner.addScanListener((scan) => {
//console.log("SCAN:", scan);
handleScan(scan);
});
return () => {
sub.remove();
zebraScanner.stopListening();
};
}, [handleScan]);
return (
<View>
<View style={{ alignItems: "center", margin: 10 }}>
<Text style={{ fontSize: 20, fontWeight: "600" }}>LST Scanner</Text>
</View>
<View
style={{
marginTop: 50,
alignItems: "center",
}}
>
<Text>Relocate</Text>
<Text>0 / 4</Text>
</View>
{/* <View>
<Text>List of recent scanned pallets TBA</Text>
</View> */}
</View>
)
</View>
);
}

View File

@@ -0,0 +1,29 @@
import { cn } from '@/lib/utils';
import { Platform, TextInput } from 'react-native';
function Input({ className, ...props }: React.ComponentProps<typeof TextInput> & React.RefAttributes<TextInput>) {
return (
<TextInput
className={cn(
'dark:bg-input/30 border-input bg-background text-foreground flex h-10 w-full min-w-0 flex-row items-center rounded-md border px-3 py-1 text-base leading-5 shadow-sm shadow-black/5 sm:h-9',
props.editable === false &&
cn(
'opacity-50',
Platform.select({ web: 'disabled:pointer-events-none disabled:cursor-not-allowed' })
),
Platform.select({
web: cn(
'placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground outline-none transition-[color,box-shadow] md:text-sm',
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive'
),
native: 'placeholder:text-muted-foreground/50',
}),
className
)}
{...props}
/>
);
}
export { Input };

View File

@@ -0,0 +1,28 @@
import { create } from "zustand";
type MobileUser = {
id: string;
name: string;
role: "user" | "lead" | "manager" | "admin";
excludedCommand: string[];
};
type AuthState = {
user: MobileUser | null;
isUnlocked: boolean;
setUser: (user: MobileUser) => void;
lock: () => void;
logout: () => void;
};
export const useMobileAuthStore = create<AuthState>((set) => ({
user: null,
isUnlocked: false,
setUser: (user) => set({ user, isUnlocked: true }),
lock: () => set({ isUnlocked: false }),
logout: () => set({ user: null, isUnlocked: false }),
}));

View File

@@ -28,9 +28,9 @@ export const zebraScanner = {
ZebraScanner.triggerScan();
},
ensureProfile() {
ZebraScanner.ensureProfile();
},
ensureProfile() {
ZebraScanner.ensureProfile();
},
addScanListener(
callback: (scan: ZebraScanResult) => void,

View File

@@ -0,0 +1,13 @@
const roleRank = {
user: 1,
lead: 2,
manager: 3,
admin: 4,
} as const;
export function hasMobileRole(
userRole: keyof typeof roleRank,
requiredRole: keyof typeof roleRank,
) {
return roleRank[userRole] >= roleRank[requiredRole];
}