diff --git a/android/app/build.gradle b/android/app/build.gradle
index e5c4585..50aaf4c 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -93,7 +93,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
- versionName "1.4.3"
+ versionName "1.5.3"
}
signingConfigs {
debug {
@@ -154,7 +154,11 @@ dependencies {
def isGifEnabled = (findProperty('expo.gif.enabled') ?: "") == "true";
def isWebpEnabled = (findProperty('expo.webp.enabled') ?: "") == "true";
def isWebpAnimatedEnabled = (findProperty('expo.webp.animated') ?: "") == "true";
+
implementation project(':react-native-tcp-socket')
+ implementation project(':react-native-keyevent')
+ implementation project(':react-native-bluetooth-classic')
+
if (isGifEnabled) {
// For animated gif support
implementation("com.facebook.fresco:animated-gif:${expoLibs.versions.fresco.get()}")
diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml
index 3ec2507..f3b51e8 100644
--- a/android/app/src/debug/AndroidManifest.xml
+++ b/android/app/src/debug/AndroidManifest.xml
@@ -1,7 +1,16 @@
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 403ab21..45f79d2 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -6,7 +6,10 @@
-
+
+
+
+
diff --git a/android/app/src/main/java/com/anonymous/Assemblr/MainActivity.kt b/android/app/src/main/java/com/anonymous/Assemblr/MainActivity.kt
index fefb5d8..d6aa57a 100644
--- a/android/app/src/main/java/com/anonymous/Assemblr/MainActivity.kt
+++ b/android/app/src/main/java/com/anonymous/Assemblr/MainActivity.kt
@@ -1,65 +1,88 @@
package com.anonymous.Assemblr
+
import expo.modules.splashscreen.SplashScreenManager
import android.os.Build
import android.os.Bundle
+import android.view.KeyEvent
import com.facebook.react.ReactActivity
import com.facebook.react.ReactActivityDelegate
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
import com.facebook.react.defaults.DefaultReactActivityDelegate
+import com.github.kevinejohn.keyevent.KeyEventModule
import expo.modules.ReactActivityDelegateWrapper
class MainActivity : ReactActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- // Set the theme to AppTheme BEFORE onCreate to support
- // coloring the background, status bar, and navigation bar.
- // This is required for expo-splash-screen.
- // setTheme(R.style.AppTheme);
- // @generated begin expo-splashscreen - expo prebuild (DO NOT MODIFY) sync-f3ff59a738c56c9a6119210cb55f0b613eb8b6af
- SplashScreenManager.registerOnActivity(this)
- // @generated end expo-splashscreen
- super.onCreate(null)
- }
+ override fun onCreate(savedInstanceState: Bundle?) {
+ // Set the theme to AppTheme BEFORE onCreate to support
+ // coloring the background, status bar, and navigation bar.
+ // This is required for expo-splash-screen.
+ // setTheme(R.style.AppTheme);
+ // @generated begin expo-splashscreen - expo prebuild (DO NOT MODIFY) sync-f3ff59a738c56c9a6119210cb55f0b613eb8b6af
+ SplashScreenManager.registerOnActivity(this)
+ // @generated end expo-splashscreen
+ super.onCreate(null)
+ }
- /**
- * Returns the name of the main component registered from JavaScript. This is used to schedule
- * rendering of the component.
- */
- override fun getMainComponentName(): String = "main"
+ /**
+ * Returns the name of the main component registered from JavaScript. This is used to schedule
+ * rendering of the component.
+ */
+ override fun getMainComponentName(): String = "main"
- /**
- * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate]
- * which allows you to enable New Architecture with a single boolean flags [fabricEnabled]
- */
- override fun createReactActivityDelegate(): ReactActivityDelegate {
- return ReactActivityDelegateWrapper(
- this,
- BuildConfig.IS_NEW_ARCHITECTURE_ENABLED,
- object : DefaultReactActivityDelegate(
- this,
- mainComponentName,
- fabricEnabled
- ){})
- }
+ /**
+ * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate]
+ * which allows you to enable New Architecture with a single boolean flags [fabricEnabled]
+ */
+ override fun createReactActivityDelegate(): ReactActivityDelegate {
+ return ReactActivityDelegateWrapper(
+ this,
+ BuildConfig.IS_NEW_ARCHITECTURE_ENABLED,
+ object : DefaultReactActivityDelegate(
+ this,
+ mainComponentName,
+ fabricEnabled
+ ) {})
+ }
- /**
- * Align the back button behavior with Android S
- * where moving root activities to background instead of finishing activities.
- * @see onBackPressed
- */
- override fun invokeDefaultOnBackPressed() {
- if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
- if (!moveTaskToBack(false)) {
- // For non-root activities, use the default implementation to finish them.
- super.invokeDefaultOnBackPressed()
- }
- return
- }
+ /**
+ * Align the back button behavior with Android S
+ * where moving root activities to background instead of finishing activities.
+ * @see onBackPressed
+ */
+ override fun invokeDefaultOnBackPressed() {
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
+ if (!moveTaskToBack(false)) {
+ // For non-root activities, use the default implementation to finish them.
+ super.invokeDefaultOnBackPressed()
+ }
+ return
+ }
- // Use the default back button implementation on Android S
- // because it's doing more than [Activity.moveTaskToBack] in fact.
- super.invokeDefaultOnBackPressed()
- }
+ // Use the default back button implementation on Android S
+ // because it's doing more than [Activity.moveTaskToBack] in fact.
+ super.invokeDefaultOnBackPressed()
+ }
+
+ override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
+ KeyEventModule.getInstance().onKeyDownEvent(keyCode, event)
+// super.onKeyDown(keyCode, event)
+ return true
+ }
+
+ override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
+ KeyEventModule.getInstance().onKeyUpEvent(keyCode, event);
+// super.onKeyUp(keyCode, event)
+ return true;
+ }
+
+
+
+ override fun onKeyMultiple(keyCode: Int, repeatCount: Int, event: KeyEvent): Boolean {
+ KeyEventModule.getInstance().onKeyMultipleEvent(keyCode, repeatCount, event)
+ return true;
+// return super.onKeyMultiple(keyCode, repeatCount, event)
+ }
}
diff --git a/android/app/src/main/java/com/anonymous/Assemblr/MainApplication.kt b/android/app/src/main/java/com/anonymous/Assemblr/MainApplication.kt
index 53604c8..9883a26 100644
--- a/android/app/src/main/java/com/anonymous/Assemblr/MainApplication.kt
+++ b/android/app/src/main/java/com/anonymous/Assemblr/MainApplication.kt
@@ -12,10 +12,12 @@ import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
import com.facebook.react.defaults.DefaultReactNativeHost
import com.facebook.react.soloader.OpenSourceMergedSoMapping
import com.facebook.soloader.SoLoader
-
+import com.github.kevinejohn.keyevent.KeyEventPackage
import expo.modules.ApplicationLifecycleDispatcher
import expo.modules.ReactNativeHostWrapper
import com.asterinet.react.tcpsocket.TcpSocketPackage;
+import kjd.reactnative.bluetooth.RNBluetoothClassicPackage;
+
class MainApplication : Application(), ReactApplication {
override val reactNativeHost: ReactNativeHost = ReactNativeHostWrapper(
@@ -26,6 +28,8 @@ class MainApplication : Application(), ReactApplication {
packages.add(PdfToBitmapPackage())
packages.add(TcpSocketPackage())
packages.add(MyTcpSocketPackage())
+ packages.add(KeyEventPackage())
+ packages.add(RNBluetoothClassicPackage())
return packages
}
@@ -55,4 +59,5 @@ class MainApplication : Application(), ReactApplication {
super.onConfigurationChanged(newConfig)
ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig)
}
+
}
diff --git a/android/settings.gradle b/android/settings.gradle
index 1c15b7d..ce268e8 100644
--- a/android/settings.gradle
+++ b/android/settings.gradle
@@ -37,5 +37,12 @@ expoAutolinking.useExpoVersionCatalog()
include ':app'
includeBuild(expoAutolinking.reactNativeGradlePlugin)
+
include ':react-native-tcp-socket'
project(':react-native-tcp-socket').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-tcp-socket/android')
+
+include ':react-native-keyevent'
+project(':react-native-keyevent').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-keyevent/android')
+
+include ':react-native-bluetooth-classic'
+project(':react-native-bluetooth-classic').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-bluetooth-classic/android')
\ No newline at end of file
diff --git a/app.json b/app.json
index 2cf7eb1..748147a 100644
--- a/app.json
+++ b/app.json
@@ -12,7 +12,7 @@
],
"name": "Assemblr",
"slug": "Assemblr",
- "version": "1.4.3",
+ "version": "1.5.3",
"orientation": "portrait",
"icon": "./src/assets/icon.png",
"userInterfaceStyle": "light",
diff --git a/package.json b/package.json
index 5f2a7ec..25d0e25 100644
--- a/package.json
+++ b/package.json
@@ -46,9 +46,12 @@
"react-hook-form": "^7.57.0",
"react-native": "0.79.2",
"react-native-animatable": "^1.4.0",
+ "react-native-bluetooth-classic": "^1.73.0-rc.13",
+ "react-native-bluetooth-serial": "lucassouza16/react-native-bluetooth-serial",
"react-native-gesture-handler": "~2.24.0",
"react-native-image-pan-zoom": "^2.1.12",
"react-native-image-zoom-viewer": "^3.0.1",
+ "react-native-keyevent": "^0.3.2",
"react-native-modal": "^13.0.1",
"react-native-paper": "^5.12.5",
"react-native-picker-select": "^9.3.1",
@@ -60,6 +63,7 @@
"react-native-responsive-fontsize": "^0.5.1",
"react-native-safe-area-context": "5.4.0",
"react-native-screens": "~4.10.0",
+ "react-native-serialport-bluetooth": "^0.4.11",
"react-native-svg": "15.11.2",
"react-native-tcp-socket": "^6.3.0",
"react-native-toast-message": "^2.2.1",
diff --git a/src/App.tsx b/src/App.tsx
index 35666fc..65cf771 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,15 +1,18 @@
import {StyleSheet} from 'react-native';
import {Provider} from "react-redux";
import {store} from "./redux/store";
-import React from "react";
+import React, {useEffect} from "react";
import CommonPage from "./screens/CommonPage/CommonPage";
import {BottomSheetModalProvider} from "@gorhom/bottom-sheet";
import {GestureHandlerRootView} from "react-native-gesture-handler";
+import KeyEvent from "react-native-keyevent";
export default function App() {
return (
+ <>
+ >
diff --git a/src/assets/icons/scanner.png b/src/assets/icons/scanner.png
new file mode 100644
index 0000000..58a8828
Binary files /dev/null and b/src/assets/icons/scanner.png differ
diff --git a/src/components/SearchBar/ScanModal.tsx b/src/components/SearchBar/ScanModal.tsx
index b8be305..fbf3680 100644
--- a/src/components/SearchBar/ScanModal.tsx
+++ b/src/components/SearchBar/ScanModal.tsx
@@ -1,5 +1,5 @@
import {FC, useEffect, useRef} from "react";
-import {GestureResponderEvent, StyleSheet, Text, TextInput, View} from "react-native";
+import {StyleSheet, TextInput, View} from "react-native";
import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions";
import {RFPercentage} from "react-native-responsive-fontsize";
import Modal from "react-native-modal";
@@ -11,12 +11,14 @@ import {closeScanModal, setScannedData} from "../../features/scanModal/scanModal
const ScanModal: FC = () => {
const inputRef = useRef(null);
+ const scannerState = useSelector((state: RootState) => state.scanner);
const state = useSelector((state: RootState) => state.scanModal);
const getDefaultLabel = () => "Наведите сканер на штрихкод товара или заказа";
const dispatch = useDispatch();
useEffect(() => {
if (state.isVisible) inputRef.current?.focus();
}, [state.isVisible]);
+
return (
diff --git a/src/css/colors.tsx b/src/css/colors.tsx
index dcd1e47..ad80b20 100644
--- a/src/css/colors.tsx
+++ b/src/css/colors.tsx
@@ -1,3 +1,5 @@
export const background = "#F5F5F5";
export const blue = "#2478F8";
-export const gray = '#A5A5A5';
\ No newline at end of file
+export const gray = '#A5A5A5';
+export const red = "#e03131"
+export const blueButton = '#2478F8'
\ No newline at end of file
diff --git a/src/features/scannerState/scannerState.ts b/src/features/scannerState/scannerState.ts
new file mode 100644
index 0000000..6c27061
--- /dev/null
+++ b/src/features/scannerState/scannerState.ts
@@ -0,0 +1,30 @@
+import {BluetoothDevice} from "react-native-bluetooth-classic";
+import {createSlice, PayloadAction} from "@reduxjs/toolkit";
+
+export interface ScannerState {
+ isConnected: boolean;
+ deviceAddress?: string;
+}
+
+const initialState: ScannerState = {
+ isConnected: false,
+ deviceAddress: undefined
+}
+
+export const scannerSlice = createSlice({
+ name: "scannerState",
+ initialState,
+ reducers: {
+ setDevice(state, action: PayloadAction<{ deviceAddress: string }>) {
+ state.deviceAddress = action.payload.deviceAddress;
+ },
+ setIsConnected(state, action: PayloadAction<{ isConnected: boolean }>) {
+ state.isConnected = action.payload.isConnected
+ }
+
+
+ }
+})
+
+export const {setDevice, setIsConnected} = scannerSlice.actions
+export default scannerSlice.reducer
\ No newline at end of file
diff --git a/src/redux/store.ts b/src/redux/store.ts
index 5acf8c5..be25fe3 100644
--- a/src/redux/store.ts
+++ b/src/redux/store.ts
@@ -15,6 +15,7 @@ import cancelAssemblyModal from 'features/cancelAssemblyModal/cancelAssemblyModa
import balanceReducer from 'features/balance/balanceSlice';
import animationsReducer from 'features/animations/animationsSlice';
import filterReducer from 'features/filterSlice/filterSlice';
+import scannerReducer from 'features/scannerState/scannerState';
import {useDispatch} from "react-redux";
export const store = configureStore({
@@ -33,7 +34,8 @@ export const store = configureStore({
cancelAssemblyModal: cancelAssemblyModal,
balance: balanceReducer,
animations: animationsReducer,
- filter:filterReducer
+ filter: filterReducer,
+ scanner: scannerReducer
},
});
diff --git a/src/screens/CommonPage/CommonPage.tsx b/src/screens/CommonPage/CommonPage.tsx
index f57e09c..5a5731e 100644
--- a/src/screens/CommonPage/CommonPage.tsx
+++ b/src/screens/CommonPage/CommonPage.tsx
@@ -66,6 +66,7 @@ function CommonPage() {
})
}
const checkUpdates = async () => {
+ return
const currentVersion = Application.nativeApplicationVersion;
applicationApi.getVersion('assemblr').then(({latest_version}) => {
diff --git a/src/screens/LoginScreen/LoginScreen.tsx b/src/screens/LoginScreen/LoginScreen.tsx
index 771a4f7..bbf867b 100644
--- a/src/screens/LoginScreen/LoginScreen.tsx
+++ b/src/screens/LoginScreen/LoginScreen.tsx
@@ -11,7 +11,6 @@ import {useDispatch} from "react-redux";
import {login} from "../../features/auth/authSlice";
import Toast from "react-native-toast-message";
import * as SecureStore from 'expo-secure-store';
-import KeyEvent from "react-native-keyevent";
function LoginScreen() {
const dispatch = useAppDispatch();
diff --git a/src/screens/MainScreen/MainScreen.tsx b/src/screens/MainScreen/MainScreen.tsx
index aaf3e96..bb8794a 100644
--- a/src/screens/MainScreen/MainScreen.tsx
+++ b/src/screens/MainScreen/MainScreen.tsx
@@ -70,7 +70,7 @@ function MainScreen() {
{
name: "Money",
component: moneyScreen,
- icon: require('assets/icons/money.png'),
+ icon: require('assets/icons/scanner.png'),
hidden: false
},
{
diff --git a/src/screens/MoneyScreen/MoneyScreen.tsx b/src/screens/MoneyScreen/MoneyScreen.tsx
index e8ca5c1..6da1255 100644
--- a/src/screens/MoneyScreen/MoneyScreen.tsx
+++ b/src/screens/MoneyScreen/MoneyScreen.tsx
@@ -1,65 +1,157 @@
-import {Text, View} from "react-native";
+import {PermissionsAndroid, Platform, Text, TouchableOpacity, View} from "react-native";
+import {useEffect, useState} from "react";
+import {Permission} from "react-native/Libraries/PermissionsAndroid/PermissionsAndroid";
+import RNBluetoothClassic, {BluetoothDevice} from "react-native-bluetooth-classic";
+import {RFPercentage} from "react-native-responsive-fontsize";
+import {responsiveWidth} from "react-native-responsive-dimensions";
+import {blue, blueButton, red} from "../../css/colors";
import BasicButton from "../../components/BasicButton/BasicButton";
-import {useScanningContext} from "../../providers/ScanProvider";
-import printingApi from "../../api/printingApi";
-import {Zpl} from "react-native-zpl-code";
-import PrintingService from "../../utils/PrintingService";
+import {useSelector} from "react-redux";
+import {RootState, useAppDispatch} from "../../redux/store";
+import {setDevice, setIsConnected} from "../../features/scannerState/scannerState";
+import {closeScanModal, setScannedData} from "../../features/scanModal/scanModalSlice";
-async function generateZplCodes(images: string[]) {
- const zplCodes = [];
- for (const image of images) {
- const zplBuilder = new Zpl.Builder();
- zplBuilder.setup({
- size: {
- heightDots: 320, // 40mm at 203 DPI
- widthDots: 464, // 58mm at 203 DPI
- },
- labelHome: {
- x: 0,
- y: 0,
- },
- labelTop: 0,
- labeShift: 0,
- orientation: "NORMAL",
- media: {
- type: "MARK_SENSING",
- dots: 0,
- },
- });
-
- zplBuilder.image({
- uri: `data:image/png;base64,${image}`,
- x: 0,
- y: 0,
- width: 464,
- height: 320,
- dither: true,
- });
-
- const zplCode = await zplBuilder.build();
- zplCodes.push(zplCode);
- }
-
- return zplCodes;
+type BluetoothSettingsState = {
+ enabled: boolean;
+ devices: BluetoothDevice[]
+}
+const DEFAULT_BLUETOOTH_SETTINGS_STATE: BluetoothSettingsState = {
+ enabled: false,
+ devices: []
}
function MoneyScreen() {
- const {scan} = useScanningContext();
- const a = "123";
- const onPress = () => {
- printingApi.getLabel(391845).then(async result => {
- PrintingService.getInstance().printPdf("", result).then(() => {
- }
- ).catch(error => {
- console.error("Error printing:", error);
- });
- });
+ const [state, setState] = useState(DEFAULT_BLUETOOTH_SETTINGS_STATE);
+ const scannerState = useSelector((state: RootState) => state.scanner);
+ const dispatch = useAppDispatch();
+
+ async function requestBluetoothPermissions() {
+ if (Platform.OS === 'android') {
+ const permissions: Permission[] = [];
+
+ if (Platform.Version >= 31) { // Android 12+
+ permissions.push(
+ PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
+ PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
+ PermissionsAndroid.PERMISSIONS.BLUETOOTH_ADVERTISE,
+ PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION
+ );
+ console.log("android12")
+ } else { // Below Android 12
+ permissions.push(
+ PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION
+ );
+ }
+
+ const granted = await PermissionsAndroid.requestMultiple(permissions);
+
+
+ const hasBluetoothPermission =
+ (Platform.Version >= 31 &&
+ granted['android.permission.BLUETOOTH_SCAN'] === PermissionsAndroid.RESULTS.GRANTED &&
+ granted['android.permission.BLUETOOTH_CONNECT'] === PermissionsAndroid.RESULTS.GRANTED) ||
+ (Platform.Version < 31 &&
+ granted['android.permission.ACCESS_FINE_LOCATION'] === PermissionsAndroid.RESULTS.GRANTED);
+
+ if (hasBluetoothPermission) {
+ console.log('Bluetooth permissions granted');
+ } else {
+ console.warn('Bluetooth permissions denied');
+ }
+ }
}
+
+ const initialize = async () => {
+ const isEnabled = await RNBluetoothClassic.isBluetoothEnabled();
+ const devices = isEnabled ? await RNBluetoothClassic.getBondedDevices() : []
+ setState(prevState => {
+ return {
+ ...prevState,
+ enabled: isEnabled,
+ devices: devices
+ }
+ })
+ }
+ const setSelectedDevice = (device: BluetoothDevice) => {
+ dispatch(setDevice({deviceAddress: device.address}));
+ }
+ const onConnectPress = async () => {
+
+ if (!scannerState.deviceAddress) return;
+ if (scannerState.isConnected) {
+ console.log("connected")
+ await RNBluetoothClassic.disconnectFromDevice(scannerState.deviceAddress);
+ return
+ }
+ console.log("connecting")
+ const connected = await RNBluetoothClassic.connectToDevice(scannerState.deviceAddress, {
+ secureSocket: false,
+ charset: "UTF-8",
+ delimiter: "\r\r"
+ });
+ const isConnected = await connected.isConnected();
+ dispatch(setIsConnected({isConnected}));
+ if (isConnected) {
+ RNBluetoothClassic.onDeviceRead(connected.address, event => {
+ dispatch(setScannedData(event.data));
+ dispatch(closeScanModal())
+
+ })
+ }
+ }
+ useEffect(() => {
+ const interval = setInterval(initialize, 1000);
+
+ const {remove: removeOnConnectedListener} = RNBluetoothClassic.onDeviceConnected(event => {
+ console.log("Connected event")
+ dispatch(setIsConnected({isConnected: true}));
+
+ })
+ const {remove: removeOnDisconnectedListener} = RNBluetoothClassic.onDeviceDisconnected(event => {
+ dispatch(setIsConnected({isConnected: false}));
+
+ })
+ return () => {
+ clearInterval(interval);
+ removeOnConnectedListener();
+ removeOnDisconnectedListener()
+ }
+
+ }, []);
return (
-
- Money
- onPress()} label={"test"}/>
+
+
+ Блютуз включен: {state.enabled ? "да" : "нет"}
+
+
+
+ {state.devices.map(device => (
+ setSelectedDevice(device)} key={device.address}>
+
+
+ {device.name}
+
+
+
+ ))}
+
+
+
)
}