Compare commits

...

20 Commits

Author SHA1 Message Date
1403c3115c feat: scanner shit 2025-07-18 05:27:49 +03:00
52529a7974 feat: update filter visibility state to be hidden by default 2025-06-13 15:53:46 +03:00
2d5d04987b feat: implement filter state management and enhance order fetching in BarcodeScreen 2025-06-12 22:32:10 +03:00
43ddf76827 feat: add ordersCount to product model and display in SelectProductElement 2025-06-07 12:37:55 +03:00
640a9fb3a2 feat: enhance AssemblyView to select product image dynamically 2025-06-07 11:36:53 +03:00
be0483fbef feat: add confirmCurrent method to assemblyApi and enhance BarcodeScreen functionality 2025-05-29 15:37:57 +03:00
3b860a6d97 chore: update version to 1.4.3, enhance printing service, and add MyTcpSocket module 2025-05-27 19:31:27 +03:00
ba39bef3b9 chore: update version to 1.4.2 and add ios to .gitignore 2025-05-26 23:09:51 +03:00
6f75dafa0d refactor: streamline printing API and update version check logic 2025-05-26 22:59:58 +03:00
64a54dabb8 remove redundant return statement in checkUpdates function 2025-05-22 19:04:45 +03:00
bb8ea9a037 update dependencies, improve PDF handling, and enhance printer integration 2025-05-22 19:01:38 +03:00
e96db56927 new version 2025-05-18 13:46:15 +03:00
1684a3b46f crpt 2025-01-14 05:36:18 +03:00
35befd8e40 crpt 2025-01-13 05:47:09 +03:00
8f585da677 crpt 2025-01-13 05:17:37 +03:00
67209dbb1c crpt 2025-01-13 05:14:00 +03:00
55ba06295e crpt 2024-10-12 03:55:19 +03:00
c0da607b72 feat: ozon small stickers 2024-06-09 03:41:26 +03:00
a6531ca88a feat: reward 2024-02-23 13:34:06 +03:00
9010574b59 first balance 2024-02-18 19:51:22 +03:00
137 changed files with 2728 additions and 1006 deletions

1
.env Normal file
View File

@@ -0,0 +1 @@
EXPO_PUBLIC_DEV_MODE=true

1
.gitignore vendored
View File

@@ -43,3 +43,4 @@ package-lock.json
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
*.apk
*.log
ios

View File

@@ -1,4 +1,4 @@
import registerRootComponent from 'expo/build/launch/registerRootComponent';
import { registerRootComponent } from 'expo';
import App from "App";
registerRootComponent(App);

1
android/.gitignore vendored
View File

@@ -10,6 +10,7 @@ build/
local.properties
*.iml
*.hprof
.cxx/
# Bundle artifacts
*.jsbundle

View File

@@ -1,4 +1,5 @@
apply plugin: "com.android.application"
apply plugin: "org.jetbrains.kotlin.android"
apply plugin: "com.facebook.react"
def projectRoot = rootDir.getAbsoluteFile().getParentFile().getAbsolutePath()
@@ -11,20 +12,21 @@ react {
entryFile = file(["node", "-e", "require('expo/scripts/resolveAppEntry')", projectRoot, "android", "absolute"].execute(null, rootDir).text.trim())
reactNativeDir = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile()
hermesCommand = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/sdks/hermesc/%OS-BIN%/hermesc"
codegenDir = new File(["node", "--print", "require.resolve('@react-native/codegen/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile()
codegenDir = new File(["node", "--print", "require.resolve('@react-native/codegen/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile()
enableBundleCompression = (findProperty('android.enableBundleCompression') ?: false).toBoolean()
// Use Expo CLI to bundle the app, this ensures the Metro config
// works correctly with Expo projects.
cliFile = new File(["node", "--print", "require.resolve('@expo/cli')"].execute(null, rootDir).text.trim())
cliFile = new File(["node", "--print", "require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })"].execute(null, rootDir).text.trim())
bundleCommand = "export:embed"
/* Folders */
// The root of your project, i.e. where "package.json" lives. Default is '..'
// root = file("../")
// The folder where the react-native NPM package is. Default is ../node_modules/react-native
// reactNativeDir = file("../node_modules/react-native")
// The folder where the react-native Codegen package is. Default is ../node_modules/@react-native/codegen
// codegenDir = file("../node_modules/@react-native/codegen")
// The root of your project, i.e. where "package.json" lives. Default is '../..'
// root = file("../../")
// The folder where the react-native NPM package is. Default is ../../node_modules/react-native
// reactNativeDir = file("../../node_modules/react-native")
// The folder where the react-native Codegen package is. Default is ../../node_modules/@react-native/codegen
// codegenDir = file("../../node_modules/@react-native/codegen")
/* Variants */
// The list of variants to that are debuggable. For those we're going to
@@ -56,6 +58,9 @@ react {
//
// The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map"
// hermesFlags = ["-O", "-output-source-map"]
/* Autolinking */
autolinkLibrariesWithApp()
}
/**
@@ -74,12 +79,13 @@ def enableProguardInReleaseBuilds = (findProperty('android.enableProguardInRelea
* give correct results when using with locales other than en-US. Note that
* this variant is about 6MiB larger per architecture than default.
*/
def jscFlavor = 'org.webkit:android-jsc:+'
def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+'
android {
ndkVersion rootProject.ext.ndkVersion
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
compileSdk rootProject.ext.compileSdkVersion
namespace 'com.anonymous.Assemblr'
defaultConfig {
@@ -87,9 +93,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.1.7"
buildConfigField("boolean", "REACT_NATIVE_UNSTABLE_USE_RUNTIME_SCHEDULER_ALWAYS", (findProperty("reactNative.unstable_useRuntimeSchedulerAlways") ?: true).toString())
versionName "1.5.3"
}
signingConfigs {
debug {
@@ -110,8 +114,17 @@ android {
shrinkResources (findProperty('android.enableShrinkResourcesInReleaseBuilds')?.toBoolean() ?: false)
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
crunchPngs (findProperty('android.enablePngCrunchInReleaseBuilds')?.toBoolean() ?: true)
}
}
packagingOptions {
jniLibs {
useLegacyPackaging (findProperty('expo.useLegacyPackaging')?.toBoolean() ?: false)
}
}
androidResources {
ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:!CVS:!thumbs.db:!picasa.ini:!*~'
}
}
// Apply static values from `gradle.properties` to the `android.packagingOptions`
@@ -141,40 +154,28 @@ dependencies {
def isGifEnabled = (findProperty('expo.gif.enabled') ?: "") == "true";
def isWebpEnabled = (findProperty('expo.webp.enabled') ?: "") == "true";
def isWebpAnimatedEnabled = (findProperty('expo.webp.animated') ?: "") == "true";
def frescoVersion = rootProject.ext.frescoVersion
// If your app supports Android versions before Ice Cream Sandwich (API level 14)
if (isGifEnabled || isWebpEnabled) {
implementation("com.facebook.fresco:fresco:${frescoVersion}")
implementation("com.facebook.fresco:imagepipeline-okhttp3:${frescoVersion}")
}
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:${frescoVersion}")
implementation("com.facebook.fresco:animated-gif:${expoLibs.versions.fresco.get()}")
}
if (isWebpEnabled) {
// For webp support
implementation("com.facebook.fresco:webpsupport:${frescoVersion}")
implementation("com.facebook.fresco:webpsupport:${expoLibs.versions.fresco.get()}")
if (isWebpAnimatedEnabled) {
// Animated webp support
implementation("com.facebook.fresco:animated-webp:${frescoVersion}")
implementation("com.facebook.fresco:animated-webp:${expoLibs.versions.fresco.get()}")
}
}
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}")
debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
exclude group:'com.squareup.okhttp3', module:'okhttp'
}
debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}")
if (hermesEnabled.toBoolean()) {
implementation("com.facebook.react:hermes-android")
} else {
implementation jscFlavor
}
}
apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json')"].execute(null, rootDir).text.trim(), "../native_modules.gradle");
applyNativeModulesAppBuildGradle(project)

View File

@@ -1,7 +1,16 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<application android:usesCleartextTraffic="true" tools:targetApi="28" tools:ignore="GoogleAppIndexingWarning" tools:replace="android:usesCleartextTraffic" />
</manifest>

View File

@@ -1,75 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
* directory of this source tree.
*/
package com.anonymous.Assemblr;
import android.content.Context;
import com.facebook.flipper.android.AndroidFlipperClient;
import com.facebook.flipper.android.utils.FlipperUtils;
import com.facebook.flipper.core.FlipperClient;
import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin;
import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin;
import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin;
import com.facebook.flipper.plugins.inspector.DescriptorMapping;
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;
import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;
import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;
import com.facebook.react.ReactInstanceEventListener;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.modules.network.NetworkingModule;
import okhttp3.OkHttpClient;
/**
* Class responsible of loading Flipper inside your React Native application. This is the debug
* flavor of it. Here you can add your own plugins and customize the Flipper setup.
*/
public class ReactNativeFlipper {
public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
if (FlipperUtils.shouldEnableFlipper(context)) {
final FlipperClient client = AndroidFlipperClient.getInstance(context);
client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults()));
client.addPlugin(new DatabasesFlipperPlugin(context));
client.addPlugin(new SharedPreferencesFlipperPlugin(context));
client.addPlugin(CrashReporterPlugin.getInstance());
NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();
NetworkingModule.setCustomClientBuilder(
new NetworkingModule.CustomClientBuilder() {
@Override
public void apply(OkHttpClient.Builder builder) {
builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));
}
});
client.addPlugin(networkFlipperPlugin);
client.start();
// Fresco Plugin needs to ensure that ImagePipelineFactory is initialized
// Hence we run if after all native modules have been initialized
ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
if (reactContext == null) {
reactInstanceManager.addReactInstanceEventListener(
new ReactInstanceEventListener() {
@Override
public void onReactContextInitialized(ReactContext reactContext) {
reactInstanceManager.removeReactInstanceEventListener(this);
reactContext.runOnNativeModulesQueueThread(
new Runnable() {
@Override
public void run() {
client.addPlugin(new FrescoFlipperPlugin());
}
});
}
});
} else {
client.addPlugin(new FrescoFlipperPlugin());
}
}
}
}

View File

@@ -1,10 +1,15 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<queries>
<intent>
<action android:name="android.intent.action.VIEW"/>
@@ -12,12 +17,11 @@
<data android:scheme="https"/>
</intent>
</queries>
<application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="true" android:theme="@style/AppTheme" android:usesCleartextTraffic="true">
<application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="true" android:theme="@style/AppTheme" android:supportsRtl="true" android:usesCleartextTraffic="true">
<meta-data android:name="expo.modules.updates.ENABLED" android:value="false"/>
<meta-data android:name="expo.modules.updates.EXPO_SDK_VERSION" android:value="49.0.0"/>
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ALWAYS"/>
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0"/>
<activity android:name=".MainActivity" android:label="@string/app_name" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen" android:exported="true" android:screenOrientation="portrait">
<activity android:name=".MainActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|uiMode" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen" android:exported="true" android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
@@ -26,9 +30,8 @@
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="com.anonymous.Assemblr"/>
<data android:scheme="exp+assemblr"/>
</intent-filter>
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" android:exported="false"/>
</application>
</manifest>

View File

@@ -1,65 +0,0 @@
package com.anonymous.Assemblr;
import android.os.Build;
import android.os.Bundle;
import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
import com.facebook.react.defaults.DefaultReactActivityDelegate;
import expo.modules.ReactActivityDelegateWrapper;
public class MainActivity extends ReactActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
// 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);
super.onCreate(null);
}
/**
* Returns the name of the main component registered from JavaScript.
* This is used to schedule rendering of the component.
*/
@Override
protected String getMainComponentName() {
return "main";
}
/**
* Returns the instance of the {@link ReactActivityDelegate}. Here we use a util class {@link
* DefaultReactActivityDelegate} which allows you to easily enable Fabric and Concurrent React
* (aka React 18) with two boolean flags.
*/
@Override
protected ReactActivityDelegate createReactActivityDelegate() {
return new ReactActivityDelegateWrapper(this, BuildConfig.IS_NEW_ARCHITECTURE_ENABLED, new DefaultReactActivityDelegate(
this,
getMainComponentName(),
// If you opted-in for the New Architecture, we enable the Fabric Renderer.
DefaultNewArchitectureEntryPoint.getFabricEnabled()));
}
/**
* Align the back button behavior with Android S
* where moving root activities to background instead of finishing activities.
* @see <a href="https://developer.android.com/reference/android/app/Activity#onBackPressed()">onBackPressed</a>
*/
@Override
public void 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 {@link Activity#moveTaskToBack} in fact.
super.invokeDefaultOnBackPressed();
}
}

View File

@@ -0,0 +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)
}
/**
* 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
) {})
}
/**
* Align the back button behavior with Android S
* where moving root activities to background instead of finishing activities.
* @see <a href="https://developer.android.com/reference/android/app/Activity#onBackPressed()">onBackPressed</a>
*/
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()
}
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)
}
}

View File

@@ -1,80 +0,0 @@
package com.anonymous.Assemblr;
import android.app.Application;
import android.content.res.Configuration;
import androidx.annotation.NonNull;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.config.ReactFeatureFlags;
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
import com.facebook.react.defaults.DefaultReactNativeHost;
import com.facebook.soloader.SoLoader;
import expo.modules.ApplicationLifecycleDispatcher;
import expo.modules.ReactNativeHostWrapper;
import java.util.List;
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost =
new ReactNativeHostWrapper(this, new DefaultReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
return packages;
}
@Override
protected String getJSMainModuleName() {
return ".expo/.virtual-metro-entry";
}
@Override
protected boolean isNewArchEnabled() {
return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
}
@Override
protected Boolean isHermesEnabled() {
return BuildConfig.IS_HERMES_ENABLED;
}
});
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
if (!BuildConfig.REACT_NATIVE_UNSTABLE_USE_RUNTIME_SCHEDULER_ALWAYS) {
ReactFeatureFlags.unstable_useRuntimeSchedulerAlways = false;
}
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
// If you opted-in for the New Architecture, we load the native entry point for this app.
DefaultNewArchitectureEntryPoint.load();
}
ReactNativeFlipper.initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
ApplicationLifecycleDispatcher.onApplicationCreate(this);
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig);
}
}

View File

@@ -0,0 +1,63 @@
package com.anonymous.Assemblr
import android.app.Application
import android.content.res.Configuration
import com.facebook.react.PackageList
import com.facebook.react.ReactApplication
import com.facebook.react.ReactNativeHost
import com.facebook.react.ReactPackage
import com.facebook.react.ReactHost
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(
this,
object : DefaultReactNativeHost(this) {
override fun getPackages(): List<ReactPackage> {
val packages = PackageList(this).packages
packages.add(PdfToBitmapPackage())
packages.add(TcpSocketPackage())
packages.add(MyTcpSocketPackage())
packages.add(KeyEventPackage())
packages.add(RNBluetoothClassicPackage())
return packages
}
override fun getJSMainModuleName(): String = ".expo/.virtual-metro-entry"
override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG
override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
}
)
override val reactHost: ReactHost
get() = ReactNativeHostWrapper.createReactHost(applicationContext, reactNativeHost)
override fun onCreate() {
super.onCreate()
SoLoader.init(this, OpenSourceMergedSoMapping)
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
// If you opted-in for the New Architecture, we load the native entry point for this app.
load()
}
ApplicationLifecycleDispatcher.onApplicationCreate(this)
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig)
}
}

View File

@@ -0,0 +1,42 @@
package com.anonymous.Assemblr;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReadableArray;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.List;
public class MyTcpSocketModule extends ReactContextBaseJavaModule {
public MyTcpSocketModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@NonNull
@Override
public String getName() {
return "MyTcpSocket";
}
@ReactMethod
public void writeToSocket(String ip, int port, ReadableArray data, Promise promise) {
try (Socket socket = new Socket(ip, port)) {
OutputStream output = socket.getOutputStream();
for (int i = 0; i < data.size(); i++) {
String item = data.getString(i);
assert item != null;
output.write(item.getBytes());
}
output.flush();
promise.resolve("ok");
} catch (IOException e) {
promise.reject("ERROR", e.getMessage());
}
}
}

View File

@@ -0,0 +1,27 @@
package com.anonymous.Assemblr;
import androidx.annotation.NonNull;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class MyTcpSocketPackage implements ReactPackage {
@NonNull
@Override
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new MyTcpSocketModule(reactContext));
return modules;
}
@NonNull
@Override
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,67 @@
package com.anonymous.Assemblr
import android.graphics.Bitmap
import android.graphics.pdf.PdfRenderer
import android.os.ParcelFileDescriptor
import android.util.Base64
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.WritableNativeArray
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileOutputStream
import androidx.core.graphics.createBitmap
class PdfToBitmapModule(reactContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactContext) {
override fun getName(): String = "PdfToBitmap"
@ReactMethod
fun convertPdfToBitmaps(pdfBase64: String, promise: Promise) {
try {
// Decode base64 PDF to byte array
val pdfBytes = Base64.decode(pdfBase64, Base64.DEFAULT)
// Create temporary file for PDF
val tempFile = File.createTempFile("tempPdf", ".pdf", reactApplicationContext.cacheDir)
FileOutputStream(tempFile).use { fos ->
fos.write(pdfBytes)
}
// Initialize PdfRenderer
val fd = ParcelFileDescriptor.open(tempFile, ParcelFileDescriptor.MODE_READ_ONLY)
val pdfRenderer = PdfRenderer(fd)
// Convert each page to a bitmap
val bitmapBase64List = WritableNativeArray()
for (pageIndex in 0 until pdfRenderer.pageCount) {
val page = pdfRenderer.openPage(pageIndex)
val scaleFactor = if (page.width < 100) 100 else 5
val bitmap = createBitmap(page.width * scaleFactor, page.height * scaleFactor)
page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)
// Convert bitmap to base64
val stream = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream)
val byteArray = stream.toByteArray()
val base64String = Base64.encodeToString(byteArray, Base64.NO_WRAP)
bitmapBase64List.pushString(base64String)
// Clean up
bitmap.recycle()
page.close()
}
// Clean up renderer and file
pdfRenderer.close()
tempFile.delete()
promise.resolve(bitmapBase64List)
} catch (e: Exception) {
promise.reject("PDF_CONVERSION_ERROR", "Failed to convert PDF to bitmaps: ${e.message}")
}
}
}

View File

@@ -0,0 +1,16 @@
package com.anonymous.Assemblr
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager
class PdfToBitmapPackage : ReactPackage {
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
return listOf(PdfToBitmapModule(reactContext))
}
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
return emptyList()
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -1,3 +1,6 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/splashscreen_background"/>
<item>
<bitmap android:gravity="center" android:src="@drawable/splashscreen_logo"/>
</item>
</layer-list>

View File

@@ -17,7 +17,8 @@
android:insetLeft="@dimen/abc_edit_text_inset_horizontal_material"
android:insetRight="@dimen/abc_edit_text_inset_horizontal_material"
android:insetTop="@dimen/abc_edit_text_inset_top_material"
android:insetBottom="@dimen/abc_edit_text_inset_bottom_material">
android:insetBottom="@dimen/abc_edit_text_inset_bottom_material"
>
<selector>
<!--

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

@@ -1,17 +1,13 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:textColor">@android:color/black</item>
<item name="android:editTextStyle">@style/ResetEditText</item>
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="android:editTextBackground">@drawable/rn_edit_text_material</item>
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="android:statusBarColor">#ffffff</item>
<item name="android:windowOptOutEdgeToEdgeEnforcement" tools:targetApi="35">true</item>
</style>
<style name="ResetEditText" parent="@android:style/Widget.EditText">
<item name="android:padding">0dp</item>
<item name="android:textColorHint">#c8c8c8</item>
<item name="android:textColor">@android:color/black</item>
</style>
<style name="Theme.App.SplashScreen" parent="AppTheme">
<item name="android:windowBackground">@drawable/splashscreen</item>
<style name="Theme.App.SplashScreen" parent="Theme.SplashScreen">
<item name="windowSplashScreenBackground">@color/splashscreen_background</item>
<item name="windowSplashScreenAnimatedIcon">@drawable/splashscreen_logo</item>
<item name="postSplashScreenTheme">@style/AppTheme</item>
</style>
</resources>

View File

@@ -1,20 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
* directory of this source tree.
*/
package com.anonymous.Assemblr;
import android.content.Context;
import com.facebook.react.ReactInstanceManager;
/**
* Class responsible of loading Flipper inside your React Native application. This is the release
* flavor of it so it's empty as we don't want to load Flipper.
*/
public class ReactNativeFlipper {
public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
// Do nothing as we don't want to initialize Flipper on Release.
}
}

View File

@@ -1,40 +1,37 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext {
buildToolsVersion = findProperty('android.buildToolsVersion') ?: '33.0.0'
minSdkVersion = Integer.parseInt(findProperty('android.minSdkVersion') ?: '21')
compileSdkVersion = Integer.parseInt(findProperty('android.compileSdkVersion') ?: '33')
targetSdkVersion = Integer.parseInt(findProperty('android.targetSdkVersion') ?: '33')
kotlinVersion = findProperty('android.kotlinVersion') ?: '1.8.10'
frescoVersion = findProperty('expo.frescoVersion') ?: '2.5.0'
// We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP.
ndkVersion = "23.1.7779620"
}
repositories {
google()
mavenCentral()
}
dependencies {
classpath('com.android.tools.build:gradle:7.4.2')
classpath('com.facebook.react:react-native-gradle-plugin')
}
repositories {
google()
mavenCentral()
}
dependencies {
classpath('com.android.tools.build:gradle')
classpath('com.facebook.react:react-native-gradle-plugin')
classpath('org.jetbrains.kotlin:kotlin-gradle-plugin')
}
}
def reactNativeAndroidDir = new File(
providers.exec {
workingDir(rootDir)
commandLine("node", "--print", "require.resolve('react-native/package.json')")
}.standardOutput.asText.get().trim(),
"../android"
)
allprojects {
repositories {
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url(new File(['node', '--print', "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), '../android'))
}
maven {
// Android JSC is installed from npm
url(new File(['node', '--print', "require.resolve('jsc-android/package.json')"].execute(null, rootDir).text.trim(), '../dist'))
}
google()
mavenCentral()
maven { url 'https://www.jitpack.io' }
repositories {
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url(reactNativeAndroidDir)
}
google()
mavenCentral()
maven { url 'https://www.jitpack.io' }
}
}
apply plugin: "expo-root-project"
apply plugin: "com.facebook.react.rootproject"

View File

@@ -22,11 +22,8 @@ org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# Version of flipper SDK to use with React Native
FLIPPER_VERSION=0.182.0
# Enable AAPT2 PNG crunching
android.enablePngCrunchInReleaseBuilds=true
# Use this property to specify which architecture you want to build.
# You can also override it from the CLI using
@@ -38,7 +35,7 @@ reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
# your application. You should enable this flag either if you want
# to write custom TurboModules/Fabric components OR use libraries that
# are providing them.
newArchEnabled=false
newArchEnabled=true
# Use this property to enable or disable the Hermes JS engine.
# If set to false, you will be using JSC instead.
@@ -54,3 +51,9 @@ expo.webp.animated=false
# Enable network inspector
EX_DEV_CLIENT_NETWORK_INSPECTOR=true
# Use legacy packaging to compress native libraries in the resulting APK.
expo.useLegacyPackaging=false
# Whether the app is configured to use edge-to-edge via the app config or `react-native-edge-to-edge` plugin
expo.edgeToEdgeEnabled=false

Binary file not shown.

View File

@@ -1,6 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

37
android/gradlew vendored Normal file → Executable file
View File

@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -80,13 +82,11 @@ do
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -133,22 +133,29 @@ location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -193,11 +200,15 @@ if "$cygwin" || "$msys" ; then
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \

23
android/gradlew.bat vendored
View File

@@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@@ -26,6 +28,7 @@ if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@@ -42,11 +45,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@@ -56,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail

View File

@@ -1,10 +1,48 @@
pluginManagement {
def reactNativeGradlePlugin = new File(
providers.exec {
workingDir(rootDir)
commandLine("node", "--print", "require.resolve('@react-native/gradle-plugin/package.json', { paths: [require.resolve('react-native/package.json')] })")
}.standardOutput.asText.get().trim()
).getParentFile().absolutePath
includeBuild(reactNativeGradlePlugin)
def expoPluginsPath = new File(
providers.exec {
workingDir(rootDir)
commandLine("node", "--print", "require.resolve('expo-modules-autolinking/package.json', { paths: [require.resolve('expo/package.json')] })")
}.standardOutput.asText.get().trim(),
"../android/expo-gradle-plugin"
).absolutePath
includeBuild(expoPluginsPath)
}
plugins {
id("com.facebook.react.settings")
id("expo-autolinking-settings")
}
extensions.configure(com.facebook.react.ReactSettingsExtension) { ex ->
if (System.getenv('EXPO_USE_COMMUNITY_AUTOLINKING') == '1') {
ex.autolinkLibrariesFromCommand()
} else {
ex.autolinkLibrariesFromCommand(expoAutolinking.rnConfigCommand)
}
}
expoAutolinking.useExpoModules()
rootProject.name = 'Assemblr'
apply from: new File(["node", "--print", "require.resolve('expo/package.json')"].execute(null, rootDir).text.trim(), "../scripts/autolinking.gradle");
useExpoModules()
apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json')"].execute(null, rootDir).text.trim(), "../native_modules.gradle");
applyNativeModulesSettingsGradle(settings)
expoAutolinking.useExpoVersionCatalog()
include ':app'
includeBuild(new File(["node", "--print", "require.resolve('@react-native/gradle-plugin/package.json')"].execute(null, rootDir).text.trim()).getParentFile())
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')

View File

@@ -8,14 +8,11 @@
"usesCleartextTraffic": true
}
}
],
[
"react-native-keyevent-expo-config-plugin"
]
],
"name": "Assemblr",
"slug": "Assemblr",
"version": "1.1.7",
"version": "1.5.3",
"orientation": "portrait",
"icon": "./src/assets/icon.png",
"userInterfaceStyle": "light",
@@ -28,7 +25,8 @@
"src/**/*"
],
"ios": {
"supportsTablet": true
"supportsTablet": true,
"bundleIdentifier": "com.anonymous.Assemblr"
},
"android": {
"adaptiveIcon": {
@@ -52,4 +50,4 @@
}
}
}
}
}

View File

@@ -4,8 +4,11 @@ module.exports = function (api) {
presets: ['babel-preset-expo'],
plugins: [
'react-native-reanimated/plugin',
'react-native-paper/babel'
]
'@babel/plugin-proposal-nullish-coalescing-operator',
'@babel/plugin-proposal-optional-chaining',
'@babel/plugin-transform-arrow-functions',
'@babel/plugin-transform-shorthand-properties',
'@babel/plugin-transform-template-literals',
],
};
};
};

View File

@@ -9,61 +9,97 @@
"web": "expo start --web"
},
"resolutions": {
"react-native-reanimated": "3.3.0"
"react": "19.0.0",
"react-dom": "19.0.0",
"react-native": "0.79.2",
"@expo/config-plugins": "~10.0.0",
"@expo/prebuild-config": "~9.0.0"
},
"dependencies": {
"@expo/webpack-config": "^19.0.0",
"@gorhom/bottom-sheet": "^4",
"@react-native-community/datetimepicker": "7.2.0",
"@react-native-picker/picker": "^2.5.1",
"@react-navigation/bottom-tabs": "^6.5.8",
"@react-navigation/native": "^6.1.7",
"@reduxjs/toolkit": "^1.9.5",
"@rneui/base": "^4.0.0-rc.7",
"@rneui/themed": "^4.0.0-rc.8",
"@shopify/flash-list": "1.4.3",
"axios": "^1.5.0",
"babel-plugin-module-resolver": "^5.0.0",
"expo": "~49.0.8",
"expo-application": "~5.3.0",
"expo-build-properties": "~0.8.3",
"expo-constants": "~14.4.2",
"expo-dev-client": "~2.4.12",
"expo-file-system": "~15.4.4",
"expo-intent-launcher": "~10.7.0",
"expo-secure-store": "~12.3.1",
"expo-splash-screen": "~0.20.5",
"expo-status-bar": "~1.6.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-native": "0.72.6",
"react-native-gesture-handler": "~2.12.0",
"@expo/webpack-config": "^19.0.1",
"@gorhom/bottom-sheet": "^5.1.6",
"@react-native-community/datetimepicker": "8.3.0",
"@react-native-picker/picker": "2.11.0",
"@react-navigation/bottom-tabs": "^6.6.1",
"@react-navigation/native": "^6.1.18",
"@reduxjs/toolkit": "^2.3.0",
"@shopify/flash-list": "1.7.6",
"@types/lodash": "^4.17.17",
"axios": "^1.7.7",
"eas-cli": "^13.0.0",
"expo": "^53.0.9",
"expo-application": "~6.1.4",
"expo-av": "~15.1.4",
"expo-build-properties": "~0.14.6",
"expo-constants": "~17.1.6",
"expo-crypto": "~14.1.4",
"expo-dev-client": "~5.1.8",
"expo-file-system": "~18.1.10",
"expo-intent-launcher": "~12.1.4",
"expo-secure-store": "~14.2.3",
"expo-splash-screen": "~0.30.8",
"expo-status-bar": "~2.2.3",
"lodash": "^4.17.21",
"prop-types": "^15.8.1",
"react": "19.0.0",
"react-dom": "19.0.0",
"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.1",
"react-native-keyevent-expo-config-plugin": "^1.0.49",
"react-native-keyevent": "^0.3.2",
"react-native-modal": "^13.0.1",
"react-native-paper": "^5.10.6",
"react-native-paper": "^5.12.5",
"react-native-picker-select": "^9.3.1",
"react-native-progress": "^5.0.1",
"react-native-progress-circle-updated": "^2.2.1",
"react-native-radio-buttons-group": "^3.0.5",
"react-native-reanimated": "3.3.0",
"react-native-radio-buttons-group": "^3.1.0",
"react-native-reanimated": "~3.17.4",
"react-native-responsive-dimension": "^1.0.0",
"react-native-responsive-dimensions": "^3.1.1",
"react-native-responsive-fontsize": "^0.5.1",
"react-native-safe-area-context": "4.6.3",
"react-native-screens": "~3.22.0",
"react-native-svg": "13.9.0",
"react-native-toast-message": "^2.1.7",
"react-native-vector-icons": "^10.0.0",
"react-native-web": "~0.19.6",
"react-native-webview": "13.2.2",
"react-redux": "^8.1.2",
"redux": "^4.2.1",
"rn-openapp": "^2.1.2"
"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",
"react-native-vector-icons": "^10.2.0",
"react-native-web": "^0.20.0",
"react-native-webview": "13.13.5",
"react-native-zpl-code": "^0.2.3",
"react-redux": "^9.1.2",
"redux": "^5.0.1"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@types/react": "~18.2.14",
"typescript": "^5.1.3"
"@babel/core": "^7.26.0",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6",
"@babel/plugin-proposal-optional-chaining": "^7.21.0",
"@babel/plugin-transform-arrow-functions": "^7.24.7",
"@babel/plugin-transform-shorthand-properties": "^7.24.7",
"@babel/plugin-transform-template-literals": "^7.24.7",
"@babel/preset-env": "^7.26.0",
"@babel/runtime": "^7.25.7",
"@react-native-community/cli": "^18.0.0",
"@types/react": "~19.0.10",
"babel-plugin-module-resolver": "^5.0.2",
"typescript": "~5.8.3"
},
"private": true
"expo": {
"doctor": {
"reactNativeDirectoryCheck": {
"exclude": [
"eas-cli",
"prop-types",
"react-native-vector-icons",
"react-native-image-pan-zoom"
]
}
}
},
"private": true,
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}

View File

@@ -1,31 +1,23 @@
import {StyleSheet, Text, View} from 'react-native';
import {StyleSheet} from 'react-native';
import {Provider} from "react-redux";
import {useFonts} from 'expo-font';
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 Toast from "react-native-toast-message";
import Constants from "expo-constants";
import KeyEvent from "react-native-keyevent";
export default function App() {
let [fontsLoading] = useFonts({
// 'SF Pro Text': require('./assets/fonts/SF-Pro-Text-Regular.otf')
})
if (!fontsLoading)
return <View><Text>Loading...</Text></View>
return (
<Provider store={store}>
<>
</>
<GestureHandlerRootView style={{flex: 1}}>
<BottomSheetModalProvider>
<CommonPage/>
</BottomSheetModalProvider>
</GestureHandlerRootView>
</Provider>
);

View File

@@ -1,33 +1,10 @@
import axios, {AxiosHeaders, AxiosRequestConfig, AxiosRequestHeaders, InternalAxiosRequestConfig} from 'axios';
import * as SecureStore from 'expo-secure-store';
import {useDispatch} from "react-redux";
import {logout} from "../features/auth/authSlice";
import {store} from "../redux/store";
import axios from 'axios';
export const baseUrl = 'https://assemblr.denco.store';
// export const baseUrl = 'http://192.168.1.101:5000';
const apiClient = axios.create({
baseURL: baseUrl
});
apiClient.interceptors.request.use(async (config) => {
const accessToken = await SecureStore.getItemAsync('accessToken');
if (!config.headers) {
config.headers = new AxiosHeaders();
}
if (accessToken) {
config.headers.set('Authorization', `Bearer ${accessToken}`, true);
}
config.validateStatus = (status) => {
if (status == 401) {
SecureStore.deleteItemAsync('accessToken');
store.dispatch(logout());
}
return true;
};
return config;
}, function (error) {
});
export default apiClient;

View File

@@ -1,21 +1,20 @@
import apiClient from "./apiClient";
import {Assembly} from "../types/assembly";
import {closeCancelAssemblyModal} from "../features/cancelAssemblyModal/cancelAssemblyModalSlice";
const router = '/assembly';
export type CreateAssemblyResponse = {
ok: boolean,
message: string,
assemblyId: number,
statusCode: AssemblyCreationStatusCode,
userName?: string
}
const assemblyApi = {
create: async (orderId: number): Promise<{
ok: boolean,
message: string,
assemblyId: number,
statusCode: AssemblyCreationStatusCode,
userName?: string
}> => {
create: async (orderId: number): Promise<CreateAssemblyResponse> => {
let response = await apiClient.post(`${router}/create`, {orderId});
return response.data;
},
close: async (assemblyId: number): Promise<{ ok: boolean, message: string }> => {
close: async (assemblyId: number): Promise<{ ok: boolean, message: string, reward: number }> => {
let response = await apiClient.post(`${router}/close`, {assemblyId});
return response.data;
},
@@ -39,9 +38,21 @@ const assemblyApi = {
let response = await apiClient.post(`${router}/confirm`, {assemblyId});
return response.data;
},
confirmCurrent: async (): Promise<{ ok: boolean, message: string }> => {
let response = await apiClient.post(`${router}/confirmCurrent`);
return response.data;
},
cancelById: async (assemblyId: number): Promise<{ ok: boolean, message: string }> => {
let response = await apiClient.post(`${router}/cancelById`, {assemblyId});
return response.data;
},
attachCrpt: async (orderProductId: number, crpt: string): Promise<{ ok: boolean, message: string }> => {
let response = await apiClient.post(`${router}/attachCrpt`, {orderProductId, crpt});
return response.data;
},
needCrpt: async (orderProductId: number): Promise<{ needCrpt: boolean }> => {
let response = await apiClient.get(`${router}/needCrpt`, {params: {orderProductId}});
return response.data;
}
}

16
src/api/balanceApi.ts Normal file
View File

@@ -0,0 +1,16 @@
import {BalanceInfo, BalanceTransaction} from "../types/balance";
import apiClient from "./apiClient";
const router = '/balance';
const balanceApi = {
getTransactions: async (page: number): Promise<{ balanceTransactions: BalanceTransaction[] }> => {
const response = await apiClient.get(`${router}/transactions`, {params: {page}});
return response.data;
},
getBalanceInfo: async (): Promise<BalanceInfo> => {
const response = await apiClient.get(`${router}/info`);
return response.data;
}
}
export default balanceApi;

View File

@@ -1,14 +1,17 @@
import apiClient from "./apiClient";
import {Order} from "../types/order";
import * as inspector from "inspector";
import {OrderStatus} from "../features/ordersFilter/ordersFilterSlice";
import {ShippingWarehouse} from "../types/shippingWarehouse";
import {City} from "../types/city";
const router = '/orders';
const ordersApi = {
getOrders: async (page: number, orderBy: string, desc: boolean, shipmentDate: string, status: number, shipmentWarehouseId: number): Promise<Order[]> => {
getOrders: async ({page, orderBy, desc, status, shipmentDate, shipmentWarehouseId}: {
page: number,
orderBy: string,
desc: boolean,
shipmentDate: string,
status: number,
shipmentWarehouseId: number
}): Promise<Order[]> => {
const params = {
page: page,
orderBy: orderBy,
@@ -22,18 +25,14 @@ const ordersApi = {
},
getOrdersByProduct: async (params: {
productId: number,
orderBy: "createdOn" | "shipmentDate",
orderBy: string,
desc: boolean,
status: OrderStatus,
shipmentDate: string,
shippingWarehouse: ShippingWarehouse,
city: City
shippingWarehouseId: number,
}): Promise<Order[]> => {
let response = await apiClient.get(`${router}/getByProductId`, {
params: {
...params,
shippingWarehouse: params.shippingWarehouse.id,
city: params.city.id
shippingWarehouse: params.shippingWarehouseId,
}
});
return response.data;

View File

@@ -3,10 +3,9 @@ import apiClient from "./apiClient";
const router = '/printing';
const printingApi = {
getLabel: async (orderId: number): Promise<Uint8Array> => {
let response = await apiClient.get(`${router}/getLabel?orderId=${orderId}`, {responseType: 'arraybuffer'});
return new Uint8Array(response.data);
getLabel: async (orderId: number): Promise<{ labels: string[] }> => {
let response = await apiClient.get(`${router}/getLabel?orderId=${orderId}&format=zpl`);
return response.data
},
}
export default printingApi;

BIN
src/assets/icons/reward.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,93 @@
import {FC, useEffect, useState} from "react";
import {StyleSheet, View, Image} from "react-native";
import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions";
import * as Animatable from 'react-native-animatable';
import DText from "../DText/DText";
import {useSelector} from "react-redux";
import {RootState, useAppDispatch} from "../../redux/store";
import {hideReward} from "../../features/animations/animationsSlice";
// const width = responsiveWidth(20)
const AnimationsView: FC = () => {
const dispatch = useAppDispatch();
const state = useSelector((state: RootState) => state.animations);
const [currentAnimation, setCurrentAnimation] = useState('bounceInRight');
const [animationStage, setAnimationStage] = useState('in'); // Управляет текущей стадией анимации
useEffect(() => {
// После завершения анимации входа, начинаем анимацию выхода
if (animationStage === 'out') {
setCurrentAnimation('bounceOutRight');
}
}, [animationStage]);
const onAnimationEnd = () => {
if (animationStage === 'in') {
// После завершения анимации входа, переходим к анимации выхода
setAnimationStage('out');
} else if (animationStage === "out") {
dispatch(hideReward());
}
};
const startAnimation = () => {
setCurrentAnimation("bounceInRight");
setAnimationStage('in');
}
useEffect(() => {
if (state.isRewardAnimationVisible)
startAnimation();
}, [state.isRewardAnimationVisible]);
return (
<View style={{...style.container, display: state.isRewardAnimationVisible ? "flex" : "none"}}>
<Animatable.View
style={{
backgroundColor: "white",
borderRadius: responsiveWidth(3),
// width: width,
paddingHorizontal: responsiveWidth(2),
display: "flex",
flexDirection: "row",
alignItems: "center",
gap: responsiveWidth(1),
zIndex: 1,
elevation: 10,
justifyContent: "center"
}}
animation={currentAnimation}
duration={1000} // Продолжительность анимации
easing="ease-in-out" // Тип анимации
iterationCount={1} // Бесконечное повторение
onAnimationEnd={onAnimationEnd}
>
<View style={{
height: responsiveHeight(5)
}}>
<Image
style={{
height: responsiveHeight(5),
width:responsiveWidth(5),
resizeMode: "contain"
}}
source={require('assets/icons/reward.png')}/>
</View>
<DText>+{state.rewardAmount.toLocaleString('ru-RU')}</DText>
</Animatable.View>
</View>
)
}
const style = StyleSheet.create({
container: {
position: "absolute",
right: 0,
top: 0,
marginTop: responsiveHeight(10),
}
})
export default AnimationsView;

View File

@@ -5,7 +5,7 @@ import {RFPercentage} from "react-native-responsive-fontsize";
import DText from "../DText/DText";
import DTitle from "../DTitle/DTitle";
type Props = {
export type BasicButtonProps = {
label: string;
style?: StyleProp<ViewStyle>;
containerStyle?: StyleProp<ViewStyle>;
@@ -14,7 +14,7 @@ type Props = {
disabled?: boolean
};
const BasicButton: FC<Props> = ({label, onPress, containerStyle, style, isUnset = false, disabled = false}) => {
const BasicButton: FC<BasicButtonProps> = ({label, onPress, containerStyle, style, isUnset = false, disabled = false}) => {
return (
<TouchableOpacity style={containerStyle} disabled={disabled} onPress={onPress}>
<View style={[styles.container, style, disabled ? {backgroundColor: "#A0A0A0"} : {}, containerStyle]}>

View File

@@ -1,11 +1,10 @@
import React, {FC, ReactElement} from "react";
import {StyleProp, StyleSheet, Text, TextStyle, View, ViewStyle} from 'react-native';
import {RFPercentage} from "react-native-responsive-fontsize";
import React, {FC, ReactNode} from "react";
import {StyleProp, StyleSheet, TextStyle} from 'react-native';
import DText from "../DText/DText";
import {responsiveScreenFontSize, responsiveWidth} from "react-native-responsive-dimensions";
import {responsiveScreenFontSize} from "react-native-responsive-dimensions";
type Props = {
children: string;
children: ReactNode;
style?: StyleProp<TextStyle>;
}
const DTitle: FC<Props> = ({children, style}) => {

View File

@@ -12,7 +12,6 @@ const Hyperlink: React.FC<HyperlinkProps> = ({url, children}) => {
if (supported) {
Linking.openURL(url);
} else {
console.log("Don't know how to open URI: " + url);
}
});
};

View File

@@ -7,13 +7,13 @@ import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensi
import DTitle from "../../DTitle/DTitle";
import BasicButton from "../../BasicButton/BasicButton";
type Props = {
export type AcceptModalProps = {
visible: boolean;
text: string;
onAccepted: () => void;
onRefused: () => void;
}
const AcceptModal: FC<Props> = ({visible, text, onAccepted, onRefused}) => {
const AcceptModal: FC<AcceptModalProps> = ({visible, text, onAccepted, onRefused}) => {
return (
<Modal isVisible={visible}>

View File

@@ -31,7 +31,6 @@ const ImageZoomModal: FC = () => {
</View>
<ImageViewer
imageUrls={imageUrls.map(imageUrl => {
console.log(imageUrl)
return {
url: imageUrl
}

View File

@@ -24,7 +24,9 @@ const SelectProductElement: FC<Props> = React.memo(({product, onPress}) => {
<View style={styles.descriptionContent}>
<DText>{product.productName}</DText>
<DText style={{textAlign: "justify"}}>{}</DText>
<DText>Заказов: {product.ordersCount}</DText>
<DText style={{textAlign: "justify"}}>""</DText>
<DText>Артикул DENCO: {product.dencoArticle}</DText>
</View>

View File

@@ -0,0 +1,19 @@
import {View} from "react-native";
import {RadioButton} from "react-native-paper";
import DText from "../../DText/DText";
import {FC} from "react";
type Props = {
value: string;
label: string;
}
const SortingButton: FC<Props> = ({value, label}) => {
return (<View style={{display: "flex", flexDirection: "row", alignItems: "center"}}>
<RadioButton value={value}/>
<DText>{label}</DText>
</View>
)
}
export default SortingButton;

View File

@@ -0,0 +1,24 @@
import {ControllerRenderProps} from 'react-hook-form';
import {FilterState} from "../../../features/filterSlice/filterSlice";
import {FC} from "react";
import {RadioButton} from "react-native-paper";
import SortingButton from "./SortingButton";
import {View} from 'react-native';
type Props = ControllerRenderProps<FilterState, "sorting">;
const SortingButtons: FC<Props> = ({value, onChange, disabled}) => {
return (
<RadioButton.Group value={value} onValueChange={onChange}>
<View style={{display: "flex", flexDirection: "column"}}>
<SortingButton value={"shipment_date_desc"} label={"Дата отгрузки по возрастанию"}/>
<SortingButton value={"shipment_date_asc"} label={"Дата отгрузки по убыванию"}/>
<SortingButton value={"created_date_desc"} label={"Дата создание по возрастанию"}/>
<SortingButton value={"created_date_asc"} label={"Дата создания по убыванию"}/>
</View>
</RadioButton.Group>
)
}
export default SortingButtons;

View File

@@ -1,185 +1,37 @@
import {FC, forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState} from "react";
import {BottomSheetModal} from "@gorhom/bottom-sheet";
import {disableDim, enableDim} from "../../../features/interface/interfaceSlice";
import {StyleSheet, View} from "react-native";
import BasicButton from "../../BasicButton/BasicButton";
import {useDispatch, useSelector} from "react-redux";
import {RFPercentage} from "react-native-responsive-fontsize";
import RadioGroup from 'react-native-radio-buttons-group';
import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions";
import {blue} from "../../../css/colors";
import {Picker} from "@react-native-picker/picker";
import DateTimePicker from '@react-native-community/datetimepicker';
import {RootState} from "../../../redux/store";
import {
closeOrdersFilterModal,
orderStatuses, setCity,
setDesc,
setOrderBy, setShipmentDate, setShippingWarehouse, setStatus
} from "../../../features/ordersFilter/ordersFilterSlice";
import ShippingWarehouseSelect from "../../ShippingWarehouseSelect/ShippingWarehouseSelect";
import CitySelect from "../../CitySelect/CitySelect";
import {useEffect, useRef} from "react";
import {BottomSheetModal, BottomSheetView} from "@gorhom/bottom-sheet";
import {useSelector} from "react-redux";
import {RootState, useAppDispatch} from "../../../redux/store";
import {closeFilter} from "../../../features/filterSlice/filterSlice";
import SortingModalBody from "./SortingModalBody";
import {View} from "react-native";
export type SortingModalHandles = {
present: () => void;
dismiss: () => void;
}
export type SortingModalElement = {
id: string;
label: string;
value: string;
}
const createRadioButton = (element: SortingModalElement) => {
return {
id: element.id,
label: element.label,
value: element.value,
size: RFPercentage(5),
color: blue,
labelStyle: {
fontSize: responsiveWidth(3),
fontWeight: "500" as const
},
borderSize: RFPercentage(0.5)
};
}
const SortingModal = () => {
const state = useSelector((state: RootState) => state.ordersFilter);
const shipmentWarehouseSelectorState = useSelector((state: RootState) => state.shippingWarehouseSelect);
const citySelectorState = useSelector((state: RootState) => state.citySelect);
const elements = [
{id: 'createdOnAsc', value: 'createdOnAsc', label: 'Дата создания по возрастанию'},
{id: 'createdOnDesc', value: 'createdOnDesc', label: 'Дата создания по убыванию'},
{id: 'shipmentDateAsc', value: 'shipmentDateAsc', label: 'Дата отгрузки по возрастанию'},
{id: 'shipmentDateDesc', value: 'shipmentDateDesc', label: 'Дата отгрузки по убыванию'},
];
const [showShipmentPicker, setShowShipmentPicker] = useState(false);
const snapPoints = useMemo(() => ['60%', '60%'], []);
const dispatch = useDispatch();
export const SortingModal = () => {
const dispatch = useAppDispatch();
const modalRef = useRef<BottomSheetModal>(null);
const dismiss = () => {
const visible = useSelector((state: RootState) => state.filter.visible);
useEffect(() => {
if (!modalRef.current) return;
dispatch(disableDim());
modalRef.current.dismiss();
}
const present = () => {
if (!modalRef.current) return;
modalRef.current.present();
dispatch(enableDim());
}
useEffect(() => {
if (state.isVisible) present();
else dismiss();
}, [state.isVisible]);
useEffect(() => {
dispatch(setShippingWarehouse(shipmentWarehouseSelectorState.selectedShippingWarehouse));
}, [shipmentWarehouseSelectorState.selectedShippingWarehouse]);
useEffect(() => {
dispatch(setCity(citySelectorState.selectedCity));
}, [citySelectorState.selectedCity]);
if (visible) {
modalRef.current.present();
} else {
modalRef.current.dismiss();
}
}, [visible]);
return (
<BottomSheetModal
onDismiss={() => dispatch(closeFilter())}
ref={modalRef}
snapPoints={snapPoints}
onDismiss={() => {
dispatch(disableDim());
dispatch(closeOrdersFilterModal())
}}>
<View style={styles.container}>
<View style={styles.content}>
<RadioGroup selectedId={state.orderBy + (state.desc ? "Desc" : "Asc")}
onPress={(event) => {
const orderRegex = /(Asc|Desc)$/;
const orderMatch = event.match(orderRegex);
if (!orderMatch) return;
const isDesc = orderMatch[0] === 'Desc';
const orderByField = event.replace(orderRegex, '');
if (!["createdOn", "shipmentDate"].includes(orderByField)) return
dispatch(setDesc(isDesc));
dispatch(setOrderBy(orderByField as "createdOn" | "shipmentDate"));
>
<BottomSheetView>
<View>
}}
containerStyle={styles.radioButtons}
radioButtons={elements.map(createRadioButton)}/>
<View style={styles.selectors}>
{/*<View style={styles.selector}>*/}
{/* <Picker selectedValue={state.status}*/}
{/* onValueChange={(value, event) => dispatch(setStatus(value))}>*/}
{/* {orderStatuses.map((status) => {*/}
{/* return (*/}
{/* <Picker.Item*/}
{/* key={status.key}*/}
{/* label={status.label}*/}
{/* value={status.key}*/}
{/* style={{fontSize: responsiveWidth(3)}}*/}
{/* />*/}
{/* )*/}
{/* })}*/}
{/* </Picker>*/}
{/*</View>*/}
<ShippingWarehouseSelect/>
{/*<CitySelect/>*/}
</View>
{/*<BasicButton onPress={() => setShowShipmentPicker(oldValue => !oldValue)}*/}
{/* label={"Выбрать дату отгрузки"}/>*/}
{/*{showShipmentPicker &&*/}
{/* <DateTimePicker value={new Date(state.shipmentDate)}*/}
{/* onChange={(event) => {*/}
{/* if (!event.nativeEvent.timestamp) return;*/}
{/* setShowShipmentPicker(false);*/}
{/* if (event.type === 'set') {*/}
{/* const selectedDate = new Date(event.nativeEvent.timestamp);*/}
{/* dispatch(setShipmentDate(selectedDate.toISOString()));*/}
{/* }*/}
{/* }}/>}*/}
<SortingModalBody/>
</View>
<BasicButton label={"Закрыть"} style={styles.button} onPress={() => {
dispatch(closeOrdersFilterModal());
}}/>
</View>
</BottomSheetView>
</BottomSheetModal>
)
};
const styles = StyleSheet.create({
container: {
width: "100%",
height: "100%",
flex: 1,
padding: RFPercentage(3),
flexDirection: "column",
justifyContent: "space-between",
rowGap: responsiveHeight(1)
},
radioButtons: {
alignItems: "flex-start"
},
content: {
rowGap: responsiveHeight(1)
},
button: {
marginTop: "auto"
},
selectors: {
rowGap: responsiveHeight(1)
},
selector: {
borderWidth: responsiveWidth(0.1),
borderRadius: responsiveWidth(1)
}
});
export default SortingModal;

View File

@@ -0,0 +1,43 @@
import {closeFilter, FilterState, setFilterState} from "../../../features/filterSlice/filterSlice";
import {Controller, useForm} from "react-hook-form";
import {Button, View} from "react-native";
import ShippingWarehouseSelect from "../../ShippingWarehouseSelect/ShippingWarehouseSelect";
import {RFPercentage} from "react-native-responsive-fontsize";
import SortingButtons from "./SortingButtons";
import {useSelector} from "react-redux";
import {RootState, useAppDispatch} from "../../../redux/store";
const SortingModalBody = () => {
const currentState = useSelector((state: RootState) => state.filter.state);
const dispatch = useAppDispatch();
const {control, handleSubmit} = useForm<FilterState>({
defaultValues: currentState,
})
const onSubmit = (data: FilterState) => {
dispatch(setFilterState(data));
dispatch(closeFilter())
}
return (
<View style={{
padding: RFPercentage(1),
display: "flex",
flexDirection: "column",
gap: RFPercentage(1),
}}>
<Controller
name={"sorting"}
control={control}
render={({field}) => (<SortingButtons {...field}/>)}
/>
<Controller control={control} name={"shippingWarehouseId"}
render={({field}) => (
<ShippingWarehouseSelect
controllerProps={field}/>)}/>
<Button title={"Применить"} onPress={handleSubmit(onSubmit)}/>
</View>
);
}
export default SortingModalBody;

View File

@@ -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,15 +11,18 @@ import {closeScanModal, setScannedData} from "../../features/scanModal/scanModal
const ScanModal: FC = () => {
const inputRef = useRef<TextInput | null>(null);
const visible = useSelector((state: RootState) => state.scanModal.isVisible);
const scannerState = useSelector((state: RootState) => state.scanner);
const state = useSelector((state: RootState) => state.scanModal);
const getDefaultLabel = () => "Наведите сканер на штрихкод товара или заказа";
const dispatch = useDispatch();
useEffect(() => {
if (visible) inputRef.current?.focus();
}, [visible]);
if (state.isVisible) inputRef.current?.focus();
}, [state.isVisible]);
return (
<Modal isVisible={visible}>
<Modal isVisible={state.isVisible}>
<View style={styles.container}>
<DText style={styles.text}>Наведите сканер на штрихкод товара или заказа</DText>
<DText style={styles.text}>{state.customLabel || getDefaultLabel()}</DText>
<BasicButton onPress={() => {
dispatch(closeScanModal());
}} style={styles.cancelButton} label={"Отмена"}/>
@@ -31,7 +34,7 @@ const ScanModal: FC = () => {
style={styles.pseudoInput}
ref={inputRef}
autoFocus={true}
showSoftInputOnFocus={false}
// showSoftInputOnFocus={false}
/>
</View>
</Modal>
@@ -60,7 +63,6 @@ const styles = StyleSheet.create({
width: responsiveWidth(30)
},
pseudoInput: {
backgroundColor: "red",
opacity: 0,
position: "absolute",
zIndex: -1

View File

@@ -51,7 +51,7 @@ const SearchBar: FC<Props> = ({onSearch, onProductSelected}) => {
}} style={styles.scanButton} label={"Поиск"}/>
<View style={styles.scanImageWrapper}>
<TouchableOpacity onPress={() => {
dispatch(openScanModal());
dispatch(openScanModal({customLabel: undefined, data: undefined}));
}}>
<Image style={styles.scanImage} source={require('assets/icons/scan.png')}/>
</TouchableOpacity>
@@ -101,6 +101,7 @@ const styles = StyleSheet.create({
borderBottomLeftRadius: responsiveWidth(1),
paddingLeft: responsiveHeight(2),
fontSize: RFPercentage(2),
color:"black",
}
})

View File

@@ -1,43 +1,40 @@
import {FC, useEffect} from "react";
import {useDispatch, useSelector} from "react-redux";
import {RootState} from "../../redux/store";
import {Picker} from "@react-native-picker/picker";
import {FC, useEffect, useState} from "react";
import RNPickerSelect, {PickerSelectProps} from 'react-native-picker-select';
import {ControllerRenderProps} from "react-hook-form";
import {FilterState} from "../../features/filterSlice/filterSlice";
import {ShippingWarehouse} from "../../types/shippingWarehouse";
import generalApi from "../../api/generalApi";
import {
initializeShippingWarehouseSelect,
selectShippingWarehouse
} from "../../features/shippingWarehouseSelect/shippingWarehouseSelectSlice";
import {responsiveWidth} from "react-native-responsive-dimensions";
import {View} from "react-native";
const ShippingWarehouseSelect: FC = () => {
const state = useSelector((state: RootState) => state.shippingWarehouseSelect);
const dispatch = useDispatch();
type Props = {
selectProps?: Omit<PickerSelectProps, "items" | "onValueChange">;
controllerProps?: ControllerRenderProps<FilterState, "shippingWarehouseId">
}
const ShippingWarehouseSelect: FC<Props> = (props) => {
const [items, setItems] = useState<ShippingWarehouse[]>([]);
useEffect(() => {
if (state.initialized) return;
generalApi.getShippingWarehouses().then(shippingWarehouses =>
dispatch(initializeShippingWarehouseSelect(shippingWarehouses)))
generalApi.getShippingWarehouses().then(response => {
setItems(response);
})
}, []);
return (
<View style={{
borderWidth: responsiveWidth(0.1),
borderRadius: responsiveWidth(1)
}}>
<Picker
selectedValue={state.selectedShippingWarehouse?.id}
onValueChange={(value, event) => dispatch(selectShippingWarehouse({shippingWarehouseId: value}))}
>
{state.shippingWarehouses.map(shippingWarehouse => (
<Picker.Item
key={shippingWarehouse.id}
label={shippingWarehouse.name}
value={shippingWarehouse.id}
style={{fontSize: responsiveWidth(3)}}
/>
))}
</Picker>
</View>
<RNPickerSelect
useNativeAndroidPickerStyle={true}
style={{
viewContainer: {
borderWidth: 1,
borderColor: "#9EA0A4",
borderRadius: "1%"
}
}}
placeholder={{label: "Все склады", value: -1, color: "#9EA0A4"}}
{...props.selectProps}
onValueChange={(value, _) => {
if (typeof value !== 'number') return;
props.controllerProps?.onChange(value);
}}
value={props.controllerProps?.value}
itemKey={"value"}
items={items.map(sw => ({label: sw.name, value: sw.id, color:"black"}))}/>
)
}

View File

@@ -1,14 +1,16 @@
import {FC} from "react";
import {StyleSheet, View, Image, TouchableOpacity, GestureResponderEvent} from "react-native";
import {GestureResponderEvent, Image, StyleSheet, TouchableOpacity, View} from "react-native";
import DText from "../DText/DText";
import {RFPercentage} from "react-native-responsive-fontsize";
import {responsiveWidth} from "react-native-responsive-dimensions";
import {useAppDispatch} from "../../redux/store";
import {openFilter} from "../../features/filterSlice/filterSlice";
type Props = {
onPress?: (event: GestureResponderEvent) => void
};
const SortingButton: FC<Props> = ({onPress}) => {
const SortingButton: FC = () => {
const dispatch = useAppDispatch();
const onPress = (event: GestureResponderEvent) => {
dispatch(openFilter())
}
return (
<TouchableOpacity onPress={onPress}>
<View style={styles.container}>

View File

@@ -0,0 +1,15 @@
import {NativeModules} from 'react-native';
interface PdfToBitmapInterface {
convertPdfToBitmaps(pdfBase64: string): Promise<string[]>;
}
const { PdfToBitmap } = NativeModules as { PdfToBitmap: PdfToBitmapInterface };
export async function convertPdfToBitmaps(pdfBase64: string): Promise<string[]> {
try {
return await PdfToBitmap.convertPdfToBitmaps(pdfBase64);
} catch (error) {
throw new Error(`Failed to convert PDF to bitmaps: ${error}`);
}
}

View File

@@ -0,0 +1,13 @@
import { NativeModules } from 'react-native';
const { MyTcpSocket } = NativeModules;
interface MyTcpSocketInterface {
writeToSocket(ip: string, port: number, data: string[]): Promise<string>;
}
export const myTcpSocket: MyTcpSocketInterface = {
writeToSocket: async (ip: string, port: number, data: string[]): Promise<string> => {
return await MyTcpSocket.writeToSocket(ip, port, data);
},
};

View File

@@ -0,0 +1,25 @@
import {createContext, FC, ReactNode, useContext} from "react";
type ApiState = {
onLogout: () => void;
};
const apiContext = createContext<ApiState | undefined>(undefined);
export const useApiContext = () => {
const context = useContext(apiContext);
if (!context) throw new Error('useApiContext must be used within ApiContextProvider');
return context;
}
type ApiContextProviderProps = {
children: ReactNode;
state: ApiState;
}
export const ApiContextProvider: FC<ApiContextProviderProps> = ({children, state}) => {
return (
<ApiContextProvider state={state}>
{children}
</ApiContextProvider>
)
}

View File

@@ -1,3 +1,5 @@
export const background = "#F5F5F5";
export const blue = "#2478F8";
export const gray = '#A5A5A5';
export const gray = '#A5A5A5';
export const red = "#e03131"
export const blueButton = '#2478F8'

View File

@@ -0,0 +1,29 @@
import {createSlice, PayloadAction} from "@reduxjs/toolkit";
interface AnimationsSlice {
isRewardAnimationVisible: boolean;
rewardAmount: number;
}
const initialState: AnimationsSlice = {
isRewardAnimationVisible: false,
rewardAmount: 0
}
export const animationsSlice = createSlice({
name: 'animations',
initialState,
reducers: {
showReward: (state, action: PayloadAction<{ reward: number }>) => {
state.rewardAmount = action.payload.reward;
state.isRewardAnimationVisible = true;
},
hideReward: (state) => {
state.isRewardAnimationVisible = false;
state.rewardAmount = 0;
}
}
})
export const {showReward, hideReward} = animationsSlice.actions;
export default animationsSlice.reducer;

View File

@@ -10,11 +10,13 @@ export interface AssemblyState {
assembly?: Assembly;
selectedProductId?: number;
localState?: ASSEMBLY_STATE;
selectedProduct?: OrderProduct
selectedProduct?: OrderProduct;
acceptModalVisible: boolean;
}
const initialState: AssemblyState = {
localState: ASSEMBLY_STATE.NOT_STARTED
localState: ASSEMBLY_STATE.NOT_STARTED,
acceptModalVisible: false
}
export const assembly = createSlice({
@@ -31,12 +33,18 @@ export const assembly = createSlice({
state.assembly = action.payload;
state.localState = action.payload.state;
},
skipCrpt: (state) => {
if (!state.assembly) return;
state.assembly.state = ASSEMBLY_STATE.ASSEMBLING_PRODUCTS;
state.localState = ASSEMBLY_STATE.ASSEMBLING_PRODUCTS
},
startAssembly: (state) => {
if (!state.assembly) return;
state.assembly.createdAt = (new Date()).toDateString();
state.assembly.state = ASSEMBLY_STATE.ASSEMBLING_PRODUCTS;
state.localState = ASSEMBLY_STATE.ASSEMBLING_PRODUCTS;
state.assembly.state = ASSEMBLY_STATE.SCANNING_CRPT;
state.localState = ASSEMBLY_STATE.SCANNING_CRPT;
},
endAssembly: (state) => {
if (!state.assembly) return;
@@ -94,6 +102,12 @@ export const assembly = createSlice({
state.localState = ASSEMBLY_STATE.NOT_STARTED
state.selectedProductId = undefined;
state.selectedProduct = undefined;
},
openAcceptModal: (state) => {
state.acceptModalVisible = true;
},
closeAcceptModal: (state) => {
state.acceptModalVisible = false;
}
}
})
@@ -107,6 +121,10 @@ export const {
endAssembly,
confirmAssembly,
setLocalState,
reset
reset,
openAcceptModal,
closeAcceptModal,
skipCrpt
} = assembly.actions;
export default assembly.reducer;

View File

@@ -0,0 +1,71 @@
import {BalanceInfo, BalanceTransaction} from "../../types/balance";
import {createAsyncThunk, createSlice} from "@reduxjs/toolkit";
import balanceApi from "../../api/balanceApi";
const name = 'balance';
interface TransactionsState {
items: BalanceTransaction[];
isLoading: boolean;
currentPage: number;
hasNext: boolean;
}
interface BalanceState {
balance: number;
transactions: TransactionsState;
}
const transactionsInitialState: TransactionsState = {
currentPage: 1,
hasNext: true,
isLoading: false,
items: []
}
const initialState: BalanceState = {
balance: 0,
transactions: transactionsInitialState
}
export const fetchTransactions = createAsyncThunk(
`${name}/fetchTransactions`,
async (page: number, _): Promise<BalanceTransaction[]> => {
const response = await balanceApi.getTransactions(page);
return response.balanceTransactions;
}
)
export const fetchBalance = createAsyncThunk(
`${name}/fetchBalance`,
async (_): Promise<BalanceInfo> => {
return await balanceApi.getBalanceInfo();
}
)
export const balanceSlice = createSlice({
name: name,
initialState,
reducers: {
refreshTransactions: (state) => {
state.transactions = transactionsInitialState;
}
},
extraReducers: (builder) => {
builder.addCase(fetchTransactions.pending, (state, action) => {
state.transactions.isLoading = true;
})
builder.addCase(fetchTransactions.fulfilled, (state, action) => {
state.transactions.isLoading = false;
state.transactions.hasNext = action.payload.length > 0;
state.transactions.items = [...state.transactions.items, ...action.payload];
state.transactions.currentPage = state.transactions.currentPage + 1;
})
builder.addCase(fetchBalance.fulfilled, (state, action) => {
state.balance = action.payload.balance;
})
}
})
export const {refreshTransactions} = balanceSlice.actions;
export default balanceSlice.reducer;

View File

@@ -0,0 +1,38 @@
import {createSlice} from "@reduxjs/toolkit";
export type FilterState = {
sorting: string;
shippingWarehouseId: number;
}
export interface FilterSliceState {
visible: boolean;
state: FilterState
}
const initialState: FilterSliceState = {
visible: false,
state: {
sorting: 'shipment_date_desc',
shippingWarehouseId: -1,
}
}
export const filterSlice = createSlice({
name: 'filter',
initialState,
reducers: {
openFilter: (state) => {
state.visible = true;
},
closeFilter: (state) => {
state.visible = false;
},
setFilterState: (state, action) => {
state.state = action.payload;
},
},
})
export const {openFilter, closeFilter, setFilterState} = filterSlice.actions;
export default filterSlice.reducer;

View File

@@ -89,7 +89,6 @@ export const ordersFilterSlice = createSlice({
state.city = action.payload;
},
setPage: (state, action: PayloadAction<number>) => {
console.log(action)
state.page = action.payload;
},
nextPage: (state) => {

View File

@@ -1,30 +1,44 @@
import {createSlice} from "@reduxjs/toolkit";
import {createSlice, PayloadAction} from "@reduxjs/toolkit";
export interface ScanModalState {
isVisible: boolean;
scannedData?: string
scannedData?: string;
customLabel?: string;
data?: any;
uuid?: string;
}
const initialState: ScanModalState = {
isVisible: false,
scannedData: undefined
}
export const scanModalSlice = createSlice({
name: 'scanModal',
initialState,
reducers: {
openScanModal: (state) => {
openScanModalFormContext(state, action: PayloadAction<{ uuid: string }>) {
state.isVisible = true;
state.uuid = action.payload.uuid;
},
openScanModal: (state, action: PayloadAction<{ customLabel?: string, data?: any }>) => {
state.isVisible = true
if (!action) return;
state.customLabel = action.payload.customLabel;
state.data = action.payload.data;
},
closeScanModal: (state) => {
state.isVisible = false
state.customLabel = undefined;
state.data = undefined;
},
setScannedData: (state, action) => {
state.scannedData = action.payload;
},
resolveUuid: (state) => {
state.uuid = undefined;
}
}
})
export const {openScanModal, closeScanModal, setScannedData} = scanModalSlice.actions;
export const {openScanModal, closeScanModal, setScannedData, openScanModalFormContext,resolveUuid} = scanModalSlice.actions;
export default scanModalSlice.reducer;

View File

@@ -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

Some files were not shown because too many files have changed in this diff Show More