update dependencies, improve PDF handling, and enhance printer integration

This commit is contained in:
2025-05-22 19:01:38 +03:00
parent e96db56927
commit bb8ea9a037
68 changed files with 692 additions and 493 deletions

View File

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

1
android/.gitignore vendored
View File

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

View File

@@ -1,4 +1,5 @@
apply plugin: "com.android.application" apply plugin: "com.android.application"
apply plugin: "org.jetbrains.kotlin.android"
apply plugin: "com.facebook.react" apply plugin: "com.facebook.react"
def projectRoot = rootDir.getAbsoluteFile().getParentFile().getAbsolutePath() 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()) 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() 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" 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 // Use Expo CLI to bundle the app, this ensures the Metro config
// works correctly with Expo projects. // 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" bundleCommand = "export:embed"
/* Folders */ /* Folders */
// The root of your project, i.e. where "package.json" lives. Default is '..' // The root of your project, i.e. where "package.json" lives. Default is '../..'
// root = file("../") // root = file("../../")
// The folder where the react-native NPM package is. Default is ../node_modules/react-native // The folder where the react-native NPM package is. Default is ../../node_modules/react-native
// reactNativeDir = file("../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 // The folder where the react-native Codegen package is. Default is ../../node_modules/@react-native/codegen
// codegenDir = file("../node_modules/@react-native/codegen") // codegenDir = file("../../node_modules/@react-native/codegen")
/* Variants */ /* Variants */
// The list of variants to that are debuggable. For those we're going to // 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" // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map"
// hermesFlags = ["-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 * give correct results when using with locales other than en-US. Note that
* this variant is about 6MiB larger per architecture than default. * 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 { android {
ndkVersion rootProject.ext.ndkVersion ndkVersion rootProject.ext.ndkVersion
compileSdkVersion rootProject.ext.compileSdkVersion buildToolsVersion rootProject.ext.buildToolsVersion
compileSdk rootProject.ext.compileSdkVersion
namespace 'com.anonymous.Assemblr' namespace 'com.anonymous.Assemblr'
defaultConfig { defaultConfig {
@@ -87,9 +93,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1 versionCode 1
versionName "1.3.4" versionName "1.4.0"
buildConfigField("boolean", "REACT_NATIVE_UNSTABLE_USE_RUNTIME_SCHEDULER_ALWAYS", (findProperty("reactNative.unstable_useRuntimeSchedulerAlways") ?: true).toString())
} }
signingConfigs { signingConfigs {
debug { debug {
@@ -110,8 +114,17 @@ android {
shrinkResources (findProperty('android.enableShrinkResourcesInReleaseBuilds')?.toBoolean() ?: false) shrinkResources (findProperty('android.enableShrinkResourcesInReleaseBuilds')?.toBoolean() ?: false)
minifyEnabled enableProguardInReleaseBuilds minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 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` // Apply static values from `gradle.properties` to the `android.packagingOptions`
@@ -141,40 +154,24 @@ dependencies {
def isGifEnabled = (findProperty('expo.gif.enabled') ?: "") == "true"; def isGifEnabled = (findProperty('expo.gif.enabled') ?: "") == "true";
def isWebpEnabled = (findProperty('expo.webp.enabled') ?: "") == "true"; def isWebpEnabled = (findProperty('expo.webp.enabled') ?: "") == "true";
def isWebpAnimatedEnabled = (findProperty('expo.webp.animated') ?: "") == "true"; def isWebpAnimatedEnabled = (findProperty('expo.webp.animated') ?: "") == "true";
def frescoVersion = rootProject.ext.frescoVersion implementation project(':react-native-tcp-socket')
// 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}")
}
if (isGifEnabled) { if (isGifEnabled) {
// For animated gif support // For animated gif support
implementation("com.facebook.fresco:animated-gif:${frescoVersion}") implementation("com.facebook.fresco:animated-gif:${expoLibs.versions.fresco.get()}")
} }
if (isWebpEnabled) { if (isWebpEnabled) {
// For webp support // For webp support
implementation("com.facebook.fresco:webpsupport:${frescoVersion}") implementation("com.facebook.fresco:webpsupport:${expoLibs.versions.fresco.get()}")
if (isWebpAnimatedEnabled) { if (isWebpAnimatedEnabled) {
// Animated webp support // 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()) { if (hermesEnabled.toBoolean()) {
implementation("com.facebook.react:hermes-android") implementation("com.facebook.react:hermes-android")
} else { } else {
implementation jscFlavor 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,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

@@ -14,12 +14,11 @@
<data android:scheme="https"/> <data android:scheme="https"/>
</intent> </intent>
</queries> </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.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_CHECK_ON_LAUNCH" android:value="ALWAYS"/>
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0"/> <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> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
@@ -28,10 +27,8 @@
<action android:name="android.intent.action.VIEW"/> <action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/> <category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="com.anonymous.Assemblr"/>
<data android:scheme="exp+assemblr"/> <data android:scheme="exp+assemblr"/>
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" android:exported="false"/>
</application> </application>
</manifest> </manifest>

View File

@@ -1,112 +0,0 @@
package com.anonymous.Assemblr;
// @generated begin react-native-keyevent-import - expo prebuild (DO NOT MODIFY) sync-6d2345dd84c398e4e99d7d44eb792294628c7e34
import android.view.KeyEvent;
import com.github.kevinejohn.keyevent.KeyEventModule;
// @generated end react-native-keyevent-import
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 {
// @generated begin react-native-keyevent-body - expo prebuild (DO NOT MODIFY) sync-4ba28f58cc0b4a775aa231e380b7f40e8f34382a
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
// // Uncomment this is key events should only trigger once when key is held down
// if (event.getRepeatCount() == 0) {
// KeyEventModule.getInstance().onKeyDownEvent(keyCode, event);
// }
// // This will trigger the key repeat if the key is held down
// // Comment this out if uncommenting the above
KeyEventModule.getInstance().onKeyDownEvent(keyCode, event);
// // Uncomment this if you want the default keyboard behavior
// return super.onKeyDown(keyCode, event);
// // The default keyboard behaviour wll be overridden
// // This is similar to what e.preventDefault() does in a browser
// // comment this if uncommenting the above
super.onKeyDown(keyCode, event);
return true;
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
KeyEventModule.getInstance().onKeyUpEvent(keyCode, event);
// // Uncomment this if you want the default keyboard behavior
// return super.onKeyUp(keyCode, event);
// // The default keyboard behaviour wll be overridden
// // This is similar to what e.preventDefault() does in a browser
// // comment this if uncommenting the above
super.onKeyUp(keyCode, event);
return true;
}
@Override
public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
KeyEventModule.getInstance().onKeyMultipleEvent(keyCode, repeatCount, event);
return super.onKeyMultiple(keyCode, repeatCount, event);
}
// @generated end react-native-keyevent-body
@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,65 @@
package com.anonymous.Assemblr
import expo.modules.splashscreen.SplashScreenManager
import android.os.Build
import android.os.Bundle
import com.facebook.react.ReactActivity
import com.facebook.react.ReactActivityDelegate
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
import com.facebook.react.defaults.DefaultReactActivityDelegate
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()
}
}

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,57 @@
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 expo.modules.ApplicationLifecycleDispatcher
import expo.modules.ReactNativeHostWrapper
import com.asterinet.react.tcpsocket.TcpSocketPackage;
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())
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,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"> <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/splashscreen_background"/> <item android:drawable="@color/splashscreen_background"/>
<item>
<bitmap android:gravity="center" android:src="@drawable/splashscreen_logo"/>
</item>
</layer-list> </layer-list>

View File

@@ -17,7 +17,8 @@
android:insetLeft="@dimen/abc_edit_text_inset_horizontal_material" android:insetLeft="@dimen/abc_edit_text_inset_horizontal_material"
android:insetRight="@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: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> <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"> <resources xmlns:tools="http://schemas.android.com/tools">
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="android:textColor">@android:color/black</item>
<item name="android:editTextStyle">@style/ResetEditText</item>
<item name="android:editTextBackground">@drawable/rn_edit_text_material</item> <item name="android:editTextBackground">@drawable/rn_edit_text_material</item>
<item name="colorPrimary">@color/colorPrimary</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>
<style name="ResetEditText" parent="@android:style/Widget.EditText"> <style name="Theme.App.SplashScreen" parent="Theme.SplashScreen">
<item name="android:padding">0dp</item> <item name="windowSplashScreenBackground">@color/splashscreen_background</item>
<item name="android:textColorHint">#c8c8c8</item> <item name="windowSplashScreenAnimatedIcon">@drawable/splashscreen_logo</item>
<item name="android:textColor">@android:color/black</item> <item name="postSplashScreenTheme">@style/AppTheme</item>
</style>
<style name="Theme.App.SplashScreen" parent="AppTheme">
<item name="android:windowBackground">@drawable/splashscreen</item>
</style> </style>
</resources> </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. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext { repositories {
buildToolsVersion = findProperty('android.buildToolsVersion') ?: '33.0.0' google()
minSdkVersion = Integer.parseInt(findProperty('android.minSdkVersion') ?: '21') mavenCentral()
compileSdkVersion = Integer.parseInt(findProperty('android.compileSdkVersion') ?: '33') }
targetSdkVersion = Integer.parseInt(findProperty('android.targetSdkVersion') ?: '33') dependencies {
kotlinVersion = findProperty('android.kotlinVersion') ?: '1.8.10' classpath('com.android.tools.build:gradle')
frescoVersion = findProperty('expo.frescoVersion') ?: '2.5.0' classpath('com.facebook.react:react-native-gradle-plugin')
classpath('org.jetbrains.kotlin:kotlin-gradle-plugin')
// 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')
}
} }
def reactNativeAndroidDir = new File(
providers.exec {
workingDir(rootDir)
commandLine("node", "--print", "require.resolve('react-native/package.json')")
}.standardOutput.asText.get().trim(),
"../android"
)
allprojects { allprojects {
repositories { repositories {
maven { maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm // 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')) url(reactNativeAndroidDir)
}
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' }
} }
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 # https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX # Enable AAPT2 PNG crunching
android.enableJetifier=true android.enablePngCrunchInReleaseBuilds=true
# Version of flipper SDK to use with React Native
FLIPPER_VERSION=0.182.0
# Use this property to specify which architecture you want to build. # Use this property to specify which architecture you want to build.
# You can also override it from the CLI using # 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 # your application. You should enable this flag either if you want
# to write custom TurboModules/Fabric components OR use libraries that # to write custom TurboModules/Fabric components OR use libraries that
# are providing them. # are providing them.
newArchEnabled=false newArchEnabled=true
# Use this property to enable or disable the Hermes JS engine. # Use this property to enable or disable the Hermes JS engine.
# If set to false, you will be using JSC instead. # If set to false, you will be using JSC instead.
@@ -54,3 +51,9 @@ expo.webp.animated=false
# Enable network inspector # Enable network inspector
EX_DEV_CLIENT_NETWORK_INSPECTOR=true 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 distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists 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 networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists 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 # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
# #
# SPDX-License-Identifier: Apache-2.0
#
############################################################################## ##############################################################################
# #
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (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. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.
@@ -80,13 +82,11 @@ do
esac esac
done done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # This is normally unused
# shellcheck disable=SC2034
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum
@@ -133,22 +133,29 @@ location of your Java installation."
fi fi
else else
JAVACMD=java 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 Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #( case $MAX_FD in #(
max*) 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 ) || MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit" warn "Could not query maximum file descriptor limit"
esac esac
case $MAX_FD in #( case $MAX_FD in #(
'' | soft) :;; #( '' | 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" || ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD" warn "Could not set maximum file descriptor limit to $MAX_FD"
esac esac
@@ -193,11 +200,15 @@ if "$cygwin" || "$msys" ; then
done done
fi fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
# shell script including quotes and variable substitutions, so put them in DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded. # 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 -- \ set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \ "-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 See the License for the specific language governing permissions and
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################
@@ -26,6 +28,7 @@ if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=. if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@@ -42,11 +45,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute if %ERRORLEVEL% equ 0 goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail
@@ -56,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute if exist "%JAVA_EXE%" goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail

View File

@@ -1,10 +1,41 @@
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' rootProject.name = 'Assemblr'
apply from: new File(["node", "--print", "require.resolve('expo/package.json')"].execute(null, rootDir).text.trim(), "../scripts/autolinking.gradle"); expoAutolinking.useExpoVersionCatalog()
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)
include ':app' 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')

View File

@@ -8,9 +8,6 @@
"usesCleartextTraffic": true "usesCleartextTraffic": true
} }
} }
],
[
"react-native-keyevent-expo-config-plugin"
] ]
], ],
"name": "Assemblr", "name": "Assemblr",

View File

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

View File

@@ -3,10 +3,11 @@ import apiClient from "./apiClient";
const router = '/printing'; const router = '/printing';
const printingApi = { const printingApi = {
getLabel: async (orderId: number): Promise<Uint8Array> => { getLabel: async (orderId: number): Promise<string> => {
let response = await apiClient.get(`${router}/getLabel?orderId=${orderId}`, {responseType: 'arraybuffer'}); let response = await apiClient.get(`${router}/getLabel?orderId=${orderId}`, {responseType: 'arraybuffer'});
return new Uint8Array(response.data); const binaryString = String.fromCharCode(...new Uint8Array(response.data));
return btoa(binaryString);
}, },
} }
export default printingApi; export default printingApi;

View File

@@ -61,10 +61,9 @@ const styles = StyleSheet.create({
width: responsiveWidth(30) width: responsiveWidth(30)
}, },
pseudoInput: { pseudoInput: {
backgroundColor: "red", opacity: 0,
// opacity: 0,
position: "absolute", position: "absolute",
// zIndex: -1 zIndex: -1
} }
}) })
export default ScanModal; export default ScanModal;

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

@@ -196,8 +196,6 @@ const ConfirmedButtons = () => {
const order = useSelector((state: RootState) => state.assembly.order); const order = useSelector((state: RootState) => state.assembly.order);
const assembly = useSelector((state: RootState) => state.assembly.assembly); const assembly = useSelector((state: RootState) => state.assembly.assembly);
const onPrintLabel = () => { const onPrintLabel = () => {
return;
if (!order) return; if (!order) return;
let printer = printingService.getInstance().getPrinter(order.baseMarketplace); let printer = printingService.getInstance().getPrinter(order.baseMarketplace);
dispatch(setLoadingText('Идет печать этикетки...')) dispatch(setLoadingText('Идет печать этикетки...'))

View File

@@ -74,7 +74,6 @@ const styles = StyleSheet.create({
padding: RFPercentage(2), padding: RFPercentage(2),
}, },
bottomSection: { bottomSection: {
backgroundColor: "red",
flex: 1, flex: 1,
borderRadius: RFPercentage(3), borderRadius: RFPercentage(3),

View File

@@ -1,18 +1,93 @@
import {NativeModules, Text, View} from "react-native"; import {Text, View} from "react-native";
import BasicButton from "../../components/BasicButton/BasicButton"; import BasicButton from "../../components/BasicButton/BasicButton";
import {randomUUID} from "expo-crypto";
import {useScanningContext} from "../../providers/ScanProvider"; import {useScanningContext} from "../../providers/ScanProvider";
import {dampingFor} from "react-native-toast-message/lib/src/components/AnimatedContainer"; import printingApi from "../../api/printingApi";
import {convertPdfToBitmaps} from "../../connectors/PdfToBitmap";
import TcpSocket from 'react-native-tcp-socket';
import {Zpl} from "react-native-zpl-code";
const {AwesomeModule} = NativeModules; 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;
}
function MoneyScreen() { function MoneyScreen() {
const {scan} = useScanningContext(); const {scan} = useScanningContext();
const a = "123"; const a = "123";
const onPress = () => {
printingApi.getLabel(386618).then(async result => {
const images = await convertPdfToBitmaps(result);
const client = TcpSocket.createConnection(
{
host: "192.168.1.69",
port: 9100,
},
() => {
console.log('Connected')
generateZplCodes(images).then(codes => {
console.log('Generated codes')
codes.forEach((code, idx) => {
console.log(`Printing ${idx}`)
client.write(code)
})
console.log('Ending')
client.end();
// client.destroy();
console.log('Disconnected')
})
},
);
client.on("error", error => {
console.log("Error:", error);
});
// Handle connection close
client.on("close", () => {
console.log("Connection closed");
});
});
}
return ( return (
<View> <View>
<Text style={{fontSize: 36}}>Money</Text> <Text style={{fontSize: 36}}>Money</Text>
<BasicButton onPress={() => onPress()} label={"test"}/>
</View> </View>
) )
} }

View File

@@ -1,33 +1,72 @@
import {View, StyleSheet} from "react-native"; import {StyleSheet, TextInput, View} from "react-native";
import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions"; import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions";
import {blue} from "../../css/colors"; import {blue} from "../../css/colors";
import {RFPercentage} from "react-native-responsive-fontsize"; import {RFPercentage} from "react-native-responsive-fontsize";
import DTitle from "../../components/DTitle/DTitle"; import DTitle from "../../components/DTitle/DTitle";
import Separator from "../../components/Separator/Separator"; import Separator from "../../components/Separator/Separator";
import {useEffect} from "react"; import {useCallback, useEffect, useState} from "react";
import {useSelector} from "react-redux"; import {useSelector} from "react-redux";
import SettingsView from "./SettingsView"; import SettingsView from "./SettingsView";
import TransactionsView from "./TransactionView"; import TransactionsView from "./TransactionView";
import {RootState, useAppDispatch} from "../../redux/store"; import {RootState, useAppDispatch} from "../../redux/store";
import {fetchBalance, fetchTransactions, refreshTransactions} from "../../features/balance/balanceSlice"; import {fetchBalance, fetchTransactions, refreshTransactions} from "../../features/balance/balanceSlice";
import {formatBalanceNumber} from "../../utils/formatters"; import {formatBalanceNumber} from "../../utils/formatters";
import {debounce} from 'lodash';
import * as SecureStore from 'expo-secure-store';
function ProfileScreen() { function ProfileScreen() {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const loadPrinterIp = async () => {
try {
const value = await SecureStore.getItemAsync("printerIp");
if (value) {
setPrinterIp(value);
}
} catch (error) {
console.error("Error loading printer IP from secure store:", error);
}
}
const [printerIp, setPrinterIp] = useState<string>("");
const [printerIpDebounce, setPrinterIpDebounce] = useState<string>(printerIp);
const debouncedSetValue = useCallback(
debounce((value: string) => {
setPrinterIpDebounce(value);
}, 200), // 500ms debounce delay
[]
);
const balanceState = useSelector((state: RootState) => state.balance); const balanceState = useSelector((state: RootState) => state.balance);
const onTransactionsEndReached = () => { const onTransactionsEndReached = () => {
if (balanceState.transactions.isLoading || !balanceState.transactions.hasNext) return; if (balanceState.transactions.isLoading || !balanceState.transactions.hasNext) return;
dispatch(fetchTransactions(balanceState.transactions.currentPage)); dispatch(fetchTransactions(balanceState.transactions.currentPage));
}; };
const onTransactionsRefresh = () => { const onTransactionsRefresh = () => {
dispatch(refreshTransactions()); dispatch(refreshTransactions());
} }
useEffect(() => { useEffect(() => {
loadPrinterIp();
dispatch(fetchBalance()); dispatch(fetchBalance());
}, []); }, []);
function onPrinterIpChange(text: string) {
setPrinterIp(text);
debouncedSetValue(text);
}
useEffect(() => {
if (printerIpDebounce.length > 0) {
SecureStore.setItemAsync("printerIp", printerIpDebounce).then(() => {
console.log("Printer IP saved to secure store");
}).catch((error) => {
console.error("Error saving printer IP to secure store:", error);
});
}
}, [printerIpDebounce]);
return ( return (
<View style={styles.container}> <View style={styles.container}>
<View style={styles.header}> <View style={styles.header}>
<View style={styles.headerInfo}> <View style={styles.headerInfo}>
<DTitle style={styles.headerText}>Ваш <DTitle style={styles.headerText}>Ваш
@@ -35,10 +74,16 @@ function ProfileScreen() {
</View> </View>
</View> </View>
<View style={styles.printerInputView}>
<TextInput onChangeText={onPrinterIpChange} style={styles.printerInput}
placeholderTextColor={"black"} placeholder={"IP принтера"}/>
</View>
<View style={styles.content}> <View style={styles.content}>
<View style={styles.actionsCarouselContainer}> <View style={styles.actionsCarouselContainer}>
<SettingsView/> <SettingsView/>
</View> </View>
<Separator/> <Separator/>
<View style={styles.historyContainer}> <View style={styles.historyContainer}>
<DTitle>История операций</DTitle> <DTitle>История операций</DTitle>
@@ -49,6 +94,7 @@ function ProfileScreen() {
onEndReached={onTransactionsEndReached}/> onEndReached={onTransactionsEndReached}/>
</View> </View>
</View> </View>
</View> </View>
) )
} }
@@ -135,6 +181,19 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
paddingHorizontal: responsiveWidth(5), paddingHorizontal: responsiveWidth(5),
}, },
printerInputView: {
margin: responsiveHeight(2),
backgroundColor: "white",
borderRadius: RFPercentage(2),
padding: RFPercentage(1),
elevation: 1,
marginBottom: responsiveHeight(3),
color: "black",
},
printerInput: {
// text color in input is black
color: "black",
}
}) })
export default ProfileScreen; export default ProfileScreen;

View File

@@ -1,5 +1,7 @@
import axios from "axios"; import {Zpl} from "react-native-zpl-code";
import {blue100} from "react-native-paper/lib/typescript/styles/themes/v2/colors"; import {convertPdfToBitmaps} from "../connectors/PdfToBitmap";
import TcpSocket from "react-native-tcp-socket";
import * as SecureStore from 'expo-secure-store';
class PrintingService { class PrintingService {
private static instance: PrintingService | null = null; private static instance: PrintingService | null = null;
@@ -18,33 +20,100 @@ class PrintingService {
this.port = port; this.port = port;
} }
private newAbortSignal(timeoutMs: number) { private async printLabelAsync(pdf: string) {
const abortController = new AbortController(); const printerIp = await SecureStore.getItemAsync("printerIp");
setTimeout(() => abortController.abort(), timeoutMs || 0); if (!printerIp) {
throw new Error("Printer IP not found in secure store");
}
// Fetch the PDF, convert to bitmaps, generate ZPL codes
const images = await convertPdfToBitmaps(pdf);
const codes = await this.generateZplCodes(images);
return abortController.signal; return new Promise<void>((resolve, reject) => {
// Create and connect the socket
const client = TcpSocket.createConnection({host: printerIp, port: 9100}, () => {
// Write each ZPL chunk
codes.forEach((code, idx) => {
console.log(`Printing ${idx}`);
client.write(code);
});
client.end();
});
// Reject on socket error
client.on("error", (err) => {
console.error("Socket error:", err);
reject(err);
});
// Resolve when the socket fully closes
client.on("close", (hadError) => {
if (hadError) {
// close after an error will have already rejected
return;
}
console.log("Connection closed");
resolve();
});
});
} }
private async print(printer: string, type: string, bytes: Uint8Array): Promise<boolean> { private async generateZplCodes(images: string[]) {
try { const zplCodes = [];
let response = await axios.post(`http://${this.apiUrl}:${this.port}/print/${printer}/${type}`, bytes.buffer, {
timeout: 25 * 1000, for (const image of images) {
signal: this.newAbortSignal(25 * 1000) 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,
},
}); });
return response.data.ok;
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;
}
private async print(printer: string, type: string, bytes: string): Promise<boolean> {
try {
await this.printLabelAsync(bytes);
return true;
} catch (error) { } catch (error) {
console.log(error); console.log(error);
return false; return false;
} }
} }
public async printPdf(printer: string, pdfBytes: Uint8Array): Promise<boolean> { public async printPdf(printer: string, pdfBytes: string): Promise<boolean> {
return this.print(printer, "pdf", pdfBytes); return this.print(printer, "pdf", pdfBytes);
} }
public async printImage(printer: string, imageBytes: Uint8Array): Promise<boolean> {
return this.print(printer, "image", imageBytes);
}
public getPrinter(baseMarketplace: number): string { public getPrinter(baseMarketplace: number): string {
if (baseMarketplace == 2) return 'ozon'; if (baseMarketplace == 2) return 'ozon';