diff --git a/README.MD b/README.MD
index ff38f9b6..a019cbb7 100644
--- a/README.MD
+++ b/README.MD
@@ -12,8 +12,8 @@
[![stars](https://img.shields.io/github/stars/jimmydaddy/react-native-image-marker?logo=github&style=for-the-badge)](https://github.com/JimmyDaddy/react-native-image-marker) [![forks](https://img.shields.io/github/forks/jimmydaddy/react-native-image-marker?logo=github&style=for-the-badge)](https://github.com/JimmyDaddy/react-native-image-marker/fork)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?logo=github&style=for-the-badge)](https://github.com/JimmyDaddy/react-native-image-marker/pulls) ![license](https://img.shields.io/npm/l/react-native-image-marker?style=for-the-badge)
[![github](https://img.shields.io/badge/github-repo-blue?logo=github&style=for-the-badge)](https://github.com/JimmyDaddy/react-native-image-marker)
- [![CI](https://github.com/JimmyDaddy/react-native-image-marker/actions/workflows/ci.yml/badge.svg)](https://github.com/JimmyDaddy/react-native-image-marker/actions/workflows/ci.yml)
- ![platform-iOS](https://img.shields.io/badge/iOS-black?logo=Apple&style=for-the-badge) ![platform-Android](https://img.shields.io/badge/Android-black?logo=Android&style=for-the-badge)
+ [![CI](https://img.shields.io/github/actions/workflow/status/jimmydaddy/react-native-image-marker/ci.yml?style=for-the-badge)](https://github.com/JimmyDaddy/react-native-image-marker/actions/workflows/ci.yml)
+ ![platform-iOS](https://img.shields.io/badge/iOS-gray?logo=Apple&style=for-the-badge) ![platform-Android](https://img.shields.io/badge/Android-gray?logo=Android&style=for-the-badge)
diff --git a/example/.detoxrc.js b/example/.detoxrc.js
new file mode 100644
index 00000000..81da9fc1
--- /dev/null
+++ b/example/.detoxrc.js
@@ -0,0 +1,83 @@
+/** @type {Detox.DetoxConfig} */
+module.exports = {
+ testRunner: {
+ args: {
+ '$0': 'jest',
+ config: 'e2e/jest.config.js'
+ },
+ jest: {
+ setupTimeout: 120000
+ }
+ },
+ apps: {
+ 'ios.debug': {
+ type: 'ios.app',
+ binaryPath: 'ios/build/Build/Products/Debug-iphonesimulator/ImageMarkerExample.app',
+ build: 'xcodebuild -workspace ios/ImageMarkerExample.xcworkspace -scheme ImageMarkerExample -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build'
+ },
+ 'ios.release': {
+ type: 'ios.app',
+ binaryPath: 'ios/build/Build/Products/Release-iphonesimulator/ImageMarkerExample.app',
+ build: 'xcodebuild -workspace ios/ImageMarkerExample.xcworkspace -scheme ImageMarkerExample -configuration Release -sdk iphonesimulator -derivedDataPath ios/build'
+ },
+ 'android.debug': {
+ type: 'android.apk',
+ binaryPath: 'android/app/build/outputs/apk/debug/app-debug.apk',
+ build: 'cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug',
+ reversePorts: [
+ 8081
+ ]
+ },
+ 'android.release': {
+ type: 'android.apk',
+ binaryPath: 'android/app/build/outputs/apk/release/app-release.apk',
+ build: 'cd android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release'
+ }
+ },
+ devices: {
+ simulator: {
+ type: 'ios.simulator',
+ device: {
+ type: 'iPhone 15'
+ }
+ },
+ attached: {
+ type: 'android.attached',
+ device: {
+ adbName: '.*'
+ }
+ },
+ emulator: {
+ type: 'android.emulator',
+ device: {
+ avdName: 'Pixel_XL_API_34'
+ }
+ }
+ },
+ configurations: {
+ 'ios.sim.debug': {
+ device: 'simulator',
+ app: 'ios.debug'
+ },
+ 'ios.sim.release': {
+ device: 'simulator',
+ app: 'ios.release'
+ },
+ 'android.att.debug': {
+ device: 'attached',
+ app: 'android.debug'
+ },
+ 'android.att.release': {
+ device: 'attached',
+ app: 'android.release'
+ },
+ 'android.emu.debug': {
+ device: 'emulator',
+ app: 'android.debug'
+ },
+ 'android.emu.release': {
+ device: 'emulator',
+ app: 'android.release'
+ }
+ }
+};
diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle
index f7000abf..80abbf5f 100644
--- a/example/android/app/build.gradle
+++ b/example/android/app/build.gradle
@@ -2,7 +2,7 @@ apply plugin: "com.android.application"
apply plugin: "com.facebook.react"
apply plugin: 'kotlin-android'
-def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["kotlin_version"]
+def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["ImageMarkerExample_kotlinVersion"]
import com.android.build.OutputFile
@@ -103,6 +103,8 @@ android {
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
+ testBuildType System.getProperty('testBuildType', 'debug')
+ testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}
splits {
@@ -137,6 +139,7 @@ android {
signingConfig signingConfigs.release
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
+ proguardFile "${rootProject.projectDir}/../node_modules/detox/android/detox/proguard-rules-app.pro"
}
}
@@ -171,6 +174,10 @@ dependencies {
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+ androidTestImplementation('com.wix:detox:+')
+ implementation 'androidx.appcompat:appcompat:1.1.0'
+ implementation project(':react-native-vector-icons')
}
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
+apply from: file("../../node_modules/react-native-vector-icons/fonts.gradle")
diff --git a/example/android/app/src/androidTest/java/com/imagemarkerexample/DetoxTest.java b/example/android/app/src/androidTest/java/com/imagemarkerexample/DetoxTest.java
new file mode 100644
index 00000000..09afa878
--- /dev/null
+++ b/example/android/app/src/androidTest/java/com/imagemarkerexample/DetoxTest.java
@@ -0,0 +1,29 @@
+package com.imagemarkerexample;
+
+import com.wix.detox.Detox;
+import com.wix.detox.config.DetoxConfig;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import androidx.test.rule.ActivityTestRule;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class DetoxTest {
+ @Rule // (2)
+ public ActivityTestRule mActivityRule = new ActivityTestRule<>(MainActivity.class, false, false);
+
+ @Test
+ public void runDetoxTests() {
+ DetoxConfig detoxConfig = new DetoxConfig();
+ detoxConfig.idlePolicyConfig.masterTimeoutSec = 90;
+ detoxConfig.idlePolicyConfig.idleResourceTimeoutSec = 60;
+ detoxConfig.rnContextLoadTimeoutSec = (BuildConfig.DEBUG ? 180 : 60);
+
+ Detox.runTests(mActivityRule, detoxConfig);
+ }
+}
diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml
index 4122f36a..23843407 100644
--- a/example/android/app/src/main/AndroidManifest.xml
+++ b/example/android/app/src/main/AndroidManifest.xml
@@ -8,6 +8,8 @@
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="false"
+ android:networkSecurityConfig="@xml/network_security_config"
+ android:usesCleartextTraffic="true"
android:theme="@style/AppTheme">
+
+
+ 10.0.2.2
+ localhost
+
+
diff --git a/example/android/build.gradle b/example/android/build.gradle
index f7e02686..f23dcd8d 100644
--- a/example/android/build.gradle
+++ b/example/android/build.gradle
@@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
- def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["kotlin_version"]
+ def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["ImageMarkerExample_kotlinVersion"]
ext {
buildToolsVersion = "34.0.0"
@@ -21,6 +21,9 @@ buildscript {
maven { url "https://repository.jboss.org/maven2" }
maven { url 'https://maven.google.com' }
maven { url 'https://maven.fabric.io/public' }
+ maven {
+ url("$rootDir/../node_modules/detox/Detox-android")
+ }
}
dependencies {
classpath("com.android.tools.build:gradle")
@@ -29,4 +32,19 @@ buildscript {
}
}
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ jcenter()
+ maven { url 'https://dl.google.com/dl/android/maven2' }
+ maven { url "https://repository.jboss.org/maven2" }
+ maven { url 'https://maven.google.com' }
+ maven { url 'https://maven.fabric.io/public' }
+ maven {
+ url("$rootDir/../node_modules/detox/Detox-android")
+ }
+ }
+}
+
apply plugin: "com.facebook.react.rootproject"
diff --git a/example/android/gradle.properties b/example/android/gradle.properties
index 9194f2da..414e55c3 100644
--- a/example/android/gradle.properties
+++ b/example/android/gradle.properties
@@ -40,4 +40,4 @@ newArchEnabled=false
# If set to false, you will be using JSC instead.
hermesEnabled=true
-kotlin_version=1.8.0
+ImageMarkerExample_kotlinVersion=1.8.0
diff --git a/example/android/settings.gradle b/example/android/settings.gradle
index df5352ac..dfff2ff6 100644
--- a/example/android/settings.gradle
+++ b/example/android/settings.gradle
@@ -1,4 +1,6 @@
rootProject.name = 'ImageMarkerExample'
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
include ':app'
-includeBuild('../node_modules/@react-native/gradle-plugin')
\ No newline at end of file
+includeBuild('../node_modules/@react-native/gradle-plugin')
+include ':react-native-vector-icons'
+project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
diff --git a/example/babel.config.js b/example/babel.config.js
index d9addbba..ef67d0c7 100644
--- a/example/babel.config.js
+++ b/example/babel.config.js
@@ -14,4 +14,9 @@ module.exports = {
},
],
],
+ env: {
+ production: {
+ plugins: ['react-native-paper/babel'],
+ },
+ },
};
diff --git a/example/e2e/App.test.js b/example/e2e/App.test.js
new file mode 100644
index 00000000..2240cc29
--- /dev/null
+++ b/example/e2e/App.test.js
@@ -0,0 +1,90 @@
+import { device, element, by, waitFor } from 'detox';
+import assert from 'power-assert';
+
+describe('e2e/App.test.js', () => {
+ beforeAll(async () => {
+ await device.launchApp();
+ });
+
+ beforeEach(async () => {
+ await device.reloadReactNative();
+ });
+
+ it('should display correctly', async () => {
+ await expect(element(by.id('backgroundImageFormatLabel'))).toBeVisible();
+ await expect(element(by.id('backgroundImageFormatLabel'))).toHaveText(
+ 'background image format:'
+ );
+
+ await expect(element(by.id('backgroundImageFormatBtn'))).toBeVisible();
+ await expect(element(by.id('backgroundImageFormatBtn'))).toHaveLabel(
+ 'image'
+ );
+
+ await expect(element(by.id('watermarkTypeLabel'))).toBeVisible();
+ await expect(element(by.id('watermarkTypeLabel'))).toHaveText(
+ 'watermark type:'
+ );
+
+ await expect(element(by.id('watermarkTypeBtn'))).toBeVisible();
+ await expect(element(by.id('watermarkTypeBtn'))).toHaveLabel('text');
+
+ await expect(element(by.id('exportResultFormatLabel'))).toBeVisible();
+ await expect(element(by.id('exportResultFormatLabel'))).toHaveText(
+ 'export result format:'
+ );
+
+ await expect(element(by.id('exportResultFormatBtn'))).toBeVisible();
+ await expect(element(by.id('exportResultFormatBtn'))).toHaveLabel('png');
+
+ await expect(element(by.id('selectBgBtn'))).toBeVisible();
+ await expect(element(by.id('selectBgBtn'))).toHaveLabel('select bg');
+
+ // await expect(element(by.id('selectWatermarkBtn'))).toBeVisible();
+ // await expect(element(by.id('selectWatermarkBtn'))).toHaveLabel(
+ // 'select watermark'
+ // );
+
+ await expect(element(by.id('resultFileSizeLabel'))).toBeVisible();
+ await expect(element(by.id('resultFilePathLabel'))).toBeVisible();
+ if (device.getPlatform() === 'ios') {
+ const resultFileSizeLabel = await element(
+ by.id('resultFileSizeLabel')
+ ).getAttributes('text');
+
+ assert.ok(
+ /^result file size:\d+(.\d+)?\s(KB|MB)$/.test(resultFileSizeLabel.text)
+ );
+ const resultFilePathLabel = await element(
+ by.id('resultFilePathLabel')
+ ).getAttributes('text');
+ assert.ok(/^file path:.*\.png$/.test(resultFilePathLabel.text));
+ } else {
+ await expect(element(by.id('resultFileSizeLabel'))).toHaveLabel(
+ /^result file size:\d+(\.\d+)?\s(KB|MB)$/
+ );
+ await expect(element(by.id('resultFilePathLabel'))).toBeVisible();
+ await expect(element(by.id('resultFilePathLabel'))).toHaveText(
+ /^file path:.*\.png$/
+ );
+ }
+
+ await expect(element(by.id('resultImage'))).toBeVisible();
+ });
+
+ describe('when click backgroundImageFormatBtn', () => {
+ it('should display correctly', async () => {
+ await expect(element(by.id('backgroundImageFormatBtn'))).toHaveLabel(
+ 'image'
+ );
+ await element(by.id('backgroundImageFormatBtn')).tap();
+ await waitFor(element(by.type('RCTModalHostView')))
+ .toBeVisible()
+ .withTimeout(2000);
+ await element(by.label('base64')).tap();
+ await expect(element(by.id('backgroundImageFormatBtn'))).toHaveLabel(
+ 'base64'
+ );
+ });
+ });
+});
diff --git a/example/e2e/jest.config.js b/example/e2e/jest.config.js
new file mode 100644
index 00000000..4f980203
--- /dev/null
+++ b/example/e2e/jest.config.js
@@ -0,0 +1,12 @@
+/** @type {import('@jest/types').Config.InitialOptions} */
+module.exports = {
+ rootDir: '..',
+ testMatch: ['/e2e/**/*.test.js'],
+ testTimeout: 120000,
+ maxWorkers: 1,
+ globalSetup: 'detox/runners/jest/globalSetup',
+ globalTeardown: 'detox/runners/jest/globalTeardown',
+ reporters: ['detox/runners/jest/reporter'],
+ testEnvironment: 'detox/runners/jest/testEnvironment',
+ verbose: true,
+};
diff --git a/example/ios/ImageMarkerExample.xcodeproj/project.pbxproj b/example/ios/ImageMarkerExample.xcodeproj/project.pbxproj
index 6a5818eb..b771a3b2 100644
--- a/example/ios/ImageMarkerExample.xcodeproj/project.pbxproj
+++ b/example/ios/ImageMarkerExample.xcodeproj/project.pbxproj
@@ -15,6 +15,8 @@
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
A119E9E12B162437000C0527 /* ImageMarkerExampleUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A119E9E02B162437000C0527 /* ImageMarkerExampleUITests.swift */; };
A119E9E32B162437000C0527 /* ImageMarkerExampleUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A119E9E22B162437000C0527 /* ImageMarkerExampleUITestsLaunchTests.swift */; };
+ A1C8E4732B26D5F600D9233E /* Fonts in Resources */ = {isa = PBXBuildFile; fileRef = A1C8E4722B26D5F600D9233E /* Fonts */; };
+ A1C8E4742B26D5F600D9233E /* Fonts in Resources */ = {isa = PBXBuildFile; fileRef = A1C8E4722B26D5F600D9233E /* Fonts */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -36,14 +38,17 @@
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = ImageMarkerExample/main.m; sourceTree = ""; };
15E0FEF5E88EB5BA856E015A /* Pods-ImageMarkerExample-ImageMarkerExampleUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageMarkerExample-ImageMarkerExampleUITests.release.xcconfig"; path = "Target Support Files/Pods-ImageMarkerExample-ImageMarkerExampleUITests/Pods-ImageMarkerExample-ImageMarkerExampleUITests.release.xcconfig"; sourceTree = ""; };
3B4392A12AC88292D35C810B /* Pods-ImageMarkerExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageMarkerExample.debug.xcconfig"; path = "Target Support Files/Pods-ImageMarkerExample/Pods-ImageMarkerExample.debug.xcconfig"; sourceTree = ""; };
+ 4BD0221D883ACD1958386DAA /* Pods-ImageMarkerExample-ImageMarkerExampleUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageMarkerExample-ImageMarkerExampleUITests.debug.xcconfig"; path = "Target Support Files/Pods-ImageMarkerExample-ImageMarkerExampleUITests/Pods-ImageMarkerExample-ImageMarkerExampleUITests.debug.xcconfig"; sourceTree = ""; };
5709B34CF0A7D63546082F79 /* Pods-ImageMarkerExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageMarkerExample.release.xcconfig"; path = "Target Support Files/Pods-ImageMarkerExample/Pods-ImageMarkerExample.release.xcconfig"; sourceTree = ""; };
5DCACB8F33CDC322A6C60F78 /* libPods-ImageMarkerExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ImageMarkerExample.a"; sourceTree = BUILT_PRODUCTS_DIR; };
6F2EF2314753552DE3ED8A96 /* libPods-ImageMarkerExample-ImageMarkerExampleUITests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ImageMarkerExample-ImageMarkerExampleUITests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = ImageMarkerExample/LaunchScreen.storyboard; sourceTree = ""; };
A0BC82937F23256F1BD51A72 /* Pods-ImageMarkerExample-ImageMarkerExampleUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageMarkerExample-ImageMarkerExampleUITests.debug.xcconfig"; path = "Target Support Files/Pods-ImageMarkerExample-ImageMarkerExampleUITests/Pods-ImageMarkerExample-ImageMarkerExampleUITests.debug.xcconfig"; sourceTree = ""; };
+ 894CD00B2EE09ABC6790EC62 /* libPods-ImageMarkerExample-ImageMarkerExampleUITests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ImageMarkerExample-ImageMarkerExampleUITests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
A119E9DE2B162437000C0527 /* ImageMarkerExampleUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ImageMarkerExampleUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
A119E9E02B162437000C0527 /* ImageMarkerExampleUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageMarkerExampleUITests.swift; sourceTree = ""; };
A119E9E22B162437000C0527 /* ImageMarkerExampleUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageMarkerExampleUITestsLaunchTests.swift; sourceTree = ""; };
+ A1C8E4722B26D5F600D9233E /* Fonts */ = {isa = PBXFileReference; lastKnownFileType = folder; name = Fonts; path = "../node_modules/react-native-vector-icons/Fonts"; sourceTree = ""; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */
@@ -100,6 +105,7 @@
83CBB9F61A601CBA00E9B192 = {
isa = PBXGroup;
children = (
+ A1C8E4722B26D5F600D9233E /* Fonts */,
13B07FAE1A68108700A75B9A /* ImageMarkerExample */,
832341AE1AAA6A7D00B99B32 /* Libraries */,
A119E9DF2B162437000C0527 /* ImageMarkerExampleUITests */,
@@ -228,6 +234,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ A1C8E4732B26D5F600D9233E /* Fonts in Resources */,
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */,
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
);
@@ -237,6 +244,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ A1C8E4742B26D5F600D9233E /* Fonts in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -504,7 +512,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 12.4;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = (
/usr/lib/swift,
"$(inherited)",
@@ -576,7 +584,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 12.4;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = (
/usr/lib/swift,
"$(inherited)",
diff --git a/example/ios/ImageMarkerExample/Info.plist b/example/ios/ImageMarkerExample/Info.plist
index fc03d6b9..1338aaaa 100644
--- a/example/ios/ImageMarkerExample/Info.plist
+++ b/example/ios/ImageMarkerExample/Info.plist
@@ -2,6 +2,32 @@
+ UIAppFonts
+
+ AntDesign.ttf
+ Entypo.ttf
+ EvilIcons.ttf
+ Feather.ttf
+ FontAwesome.ttf
+ FontAwesome5_Brands.ttf
+ FontAwesome5_Regular.ttf
+ FontAwesome5_Solid.ttf
+ FontAwesome6_Brands.ttf
+ FontAwesome6_Regular.ttf
+ FontAwesome6_Solid.ttf
+ Foundation.ttf
+ Ionicons.ttf
+ MaterialIcons.ttf
+ MaterialCommunityIcons.ttf
+ SimpleLineIcons.ttf
+ Octicons.ttf
+ Zocial.ttf
+ Fontisto.ttf
+
+ NSCameraUsageDescription
+ want to use
+ NSPhotoLibraryUsageDescription
+ want to use
CFBundleDevelopmentRegion
en
CFBundleDisplayName
diff --git a/example/ios/ImageMarkerExampleUITests/ImageMarkerExampleUITests.swift b/example/ios/ImageMarkerExampleUITests/ImageMarkerExampleUITests.swift
index e334a0d2..9f5c42e2 100644
--- a/example/ios/ImageMarkerExampleUITests/ImageMarkerExampleUITests.swift
+++ b/example/ios/ImageMarkerExampleUITests/ImageMarkerExampleUITests.swift
@@ -7,32 +7,44 @@
import XCTest
+@MainActor
final class ImageMarkerExampleUITests: XCTestCase {
-
override func setUpWithError() throws {
+ super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
-
+
// In UI tests it is usually best to stop immediately when a failure occurs.
continueAfterFailure = false
-
+
// In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
}
-
+
override func tearDownWithError() throws {
+ super.tearDown()
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
-
- func testApp() throws {
+
+ func testAppDisplay() throws {
// UI tests must launch the application that they test.
let app = XCUIApplication()
app.launch()
sleep(5)
+
var ele = app.textFields["100"]
XCTAssert(ele.exists)
- ele = app.staticTexts["watermark type:"]
+ ele = app.staticTexts["watermark type"]
XCTAssert(ele.exists)
- ele = app.staticTexts["background image format:"]
+ ele = app.staticTexts["bg format:"]
XCTAssert(ele.exists)
+ var predicate = NSPredicate(format: "label BEGINSWITH %@", "file path:")
+ ele = app.staticTexts.element(matching: predicate)
+ XCTAssert(ele.exists, "File path label does not exist.")
+ predicate = NSPredicate(format: "label MATCHES %@", "result file size:[0-9]+\\.?[0-9]*\\s(MB|KB)")
+ ele = app.staticTexts.element(matching: predicate)
+ XCTAssert(ele.exists, "result file size label does not exist.")
+ let button = app.otherElements["watermarkTypeBtn"]
+ XCTAssert(button.exists, "WatermarkTypeBtn button does not exist.")
+
}
func testLaunchPerformance() throws {
diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock
index 17b76699..985f7625 100644
--- a/example/ios/Podfile.lock
+++ b/example/ios/Podfile.lock
@@ -1196,6 +1196,7 @@ DEPENDENCIES:
- React-runtimescheduler (from `../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`)
- React-utils (from `../node_modules/react-native/ReactCommon/react/utils`)
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
+ - RNVectorIcons (from `../node_modules/react-native-vector-icons`)
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
SPEC REPOS:
@@ -1316,6 +1317,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/react/utils"
ReactCommon:
:path: "../node_modules/react-native/ReactCommon"
+ RNVectorIcons:
+ :path: "../node_modules/react-native-vector-icons"
Yoga:
:path: "../node_modules/react-native/ReactCommon/yoga"
diff --git a/example/package.json b/example/package.json
index 8f21eae1..ae74be99 100644
--- a/example/package.json
+++ b/example/package.json
@@ -8,7 +8,13 @@
"start": "react-native start",
"pods": "pod-install",
"m1-pods": "cd ios && arch -x86_64 pod install",
- "postinstall": "patch-package"
+ "postinstall": "patch-package",
+ "e2e-test-build:android": "detox build -c android.emu.debug",
+ "e2e-test-build:ios": "detox build -c ios.sim.debug",
+ "e2e-test-run:android": "detox test -c android.emu.debug",
+ "e2e-test-run:ios": "detox test -c ios.sim.debug",
+ "e2e-test:android": "run-s e2e-test-build:android e2e-test-run:android",
+ "e2e-test:ios": "run-s e2e-test-build:ios e2e-test-run:ios"
},
"dependencies": {
"@expo/react-native-action-sheet": "^4.0.1",
@@ -17,8 +23,11 @@
"react-native": "0.73.1",
"react-native-blob-util": "^0.19.2",
"react-native-image-picker": "^5.6.0",
+ "react-native-paper": "^5.11.3",
"react-native-reanimated-table": "^0.0.2",
- "react-native-toast-message": "^2.1.6"
+ "react-native-safe-area-context": "^4.8.0",
+ "react-native-toast-message": "^2.1.6",
+ "react-native-vector-icons": "^10.0.2"
},
"devDependencies": {
"@babel/core": "^7.20.0",
@@ -33,6 +42,11 @@
"prettier": "2.8.8",
"typescript": "5.0.4",
"patch-package": "^8.0.0",
- "postinstall-postinstall": "^2.1.0"
+ "postinstall-postinstall": "^2.1.0",
+ "detox": "^20.13.5",
+ "jest": "^29.7.0",
+ "metro-react-native-babel-preset": "0.73.10",
+ "npm-run-all": "^4.1.5",
+ "power-assert": "^1.6.1"
}
}
diff --git a/example/react-native.config.js b/example/react-native.config.js
index a5166956..3f2dd5ac 100644
--- a/example/react-native.config.js
+++ b/example/react-native.config.js
@@ -6,5 +6,10 @@ module.exports = {
[pak.name]: {
root: path.join(__dirname, '..'),
},
+ 'react-native-vector-icons': {
+ platforms: {
+ ios: null,
+ },
+ },
},
};
diff --git a/example/src/App.tsx b/example/src/App.tsx
index 38d1d741..ba582c3a 100644
--- a/example/src/App.tsx
+++ b/example/src/App.tsx
@@ -1,44 +1,34 @@
/* eslint-disable react-native/no-inline-styles */
-import React, { useEffect, useState } from 'react';
+import React from 'react';
import {
StyleSheet,
View,
- Text,
Dimensions,
TouchableOpacity,
ScrollView,
ActivityIndicator,
- Platform,
Image,
- TextInput,
Switch,
Modal,
+ TextInput as RNTextInput,
} from 'react-native';
-import Marker, {
- Position,
- ImageFormat,
- TextBackgroundType,
-} from 'react-native-image-marker';
-import { launchImageLibrary } from 'react-native-image-picker';
-import {
- ActionSheetProvider,
- useActionSheet,
-} from '@expo/react-native-action-sheet';
+import { ImageFormat, TextBackgroundType } from 'react-native-image-marker';
+import { ActionSheetProvider } from '@expo/react-native-action-sheet';
import Toast from 'react-native-toast-message';
-import RNBlobUtil from 'react-native-blob-util';
-import filesize from 'filesize';
+import { PaperProvider, Text } from 'react-native-paper';
-const icon = require('./icon.jpeg');
-const icon1 = require('./yahaha.jpeg');
-const bg = require('./bg.png');
-const base64Bg = require('./bas64bg').default;
+import useViewModel from './ViewModel';
+import RadioGroup from './components/RadioGroup';
+import ImageOptions from './components/ImageOptions';
+import TextOptions from './components/TextOptions';
+import { Row, RowSplit } from './components/Row';
const { width, height } = Dimensions.get('window');
const s = StyleSheet.create({
container: {
flex: 1,
- marginTop: 50,
+ marginTop: 30,
},
op: {
marginTop: 10,
@@ -50,13 +40,6 @@ const s = StyleSheet.create({
paddingRight: 10,
paddingBottom: 5,
},
- row: {
- flexDirection: 'row',
- justifyContent: 'flex-start',
- alignItems: 'center',
- marginTop: 5,
- flex: 1,
- },
btn: {
padding: 10,
paddingTop: 5,
@@ -118,7 +101,6 @@ const s = StyleSheet.create({
padding: 10,
},
shortTextInput: {
- width: 30,
height: 30,
backgroundColor: '#ffA',
borderColor: '#00B96B5A',
@@ -130,690 +112,8 @@ const s = StyleSheet.create({
marginRight: 2,
textAlign: 'left',
},
- rowSplit: {
- flex: 1,
- alignItems: 'center',
- justifyContent: 'flex-start',
- flexDirection: 'row',
- },
});
-function RowSplit(props: any) {
- return {props.children};
-}
-
-function ImageOptions(props: {
- alpha: number;
- scale: number;
- rotate: number;
- quality: number;
- setAlpha: (alpha: number) => void;
- setScale: (scale: number) => void;
- setRotate: (rotate: number) => void;
- setQuality: (quality: number) => void;
-}) {
- const {
- alpha,
- scale,
- rotate,
- quality,
- setAlpha,
- setScale,
- setRotate,
- setQuality,
- } = props;
- return (
-
- scale:
- {
- const value = Number(v);
- if (value < 0) {
- Toast.show({
- type: 'error',
- text1: 'scale range error',
- text2: 'scale must greater than or equal to 1',
- });
- return;
- }
- setScale(value);
- }}
- />
- alpha:
- {
- const value = Number(v);
- if (value < 0 || value > 1) {
- Toast.show({
- type: 'error',
- text1: 'alpha range error',
- text2: 'alpha must be between 0 and 1',
- });
- return;
- }
- setAlpha(value);
- }}
- />
- rotate:
- {
- const value = Number(v);
- if (value < -360 || value > 360) {
- Toast.show({
- type: 'error',
- text1: 'rotate range error',
- text2: 'rotate must be between -360 and 360',
- });
- return;
- }
- setRotate(value);
- }}
- />
- quality:
- {
- const value = Number(v);
- if (value < 0 || value > 100) {
- Toast.show({
- type: 'error',
- text1: 'quality range error',
- text2: 'quality must be between 0 and 100',
- });
- return;
- }
- setQuality(value);
- }}
- />
-
- );
-}
-
-function useViewModel() {
- const { showActionSheetWithOptions } = useActionSheet();
- const [backgroundFormat, setBackgroundFormat] = useState<
- 'normal image' | 'base64'
- >('normal image');
- const [waterMarkType, setWaterMarkType] = useState<'text' | 'image'>('text');
- const [saveFormat, setSaveFormat] = useState(ImageFormat.png);
- const [image, setImage] = useState(bg);
- const [uri, setUri] = useState('');
- const [marker, setMarker] = useState(icon);
- const [text, setText] = useState('hello world \n 你好');
- const [useTextShadow, setUseTextShadow] = useState(true);
- const [useTextBgStyle, setUseTextBgStyle] = useState(true);
- const [textBgStretch, setTextBgStretch] = useState(
- TextBackgroundType.none
- );
- const [position, setPosition] = useState(Position.topLeft);
- const [X, setX] = useState(20);
- const [Y, setY] = useState(20);
- const [loading, setLoading] = useState(false);
- const [show, setShow] = useState(false);
- const [underline, setUnderline] = useState(false);
- const [italic, setItalic] = useState(false);
- const [bold, setBold] = useState(false);
- const [strikeThrough, setStrikeThrough] = useState(false);
- const [textAlign, setTextAlign] = useState<'left' | 'right' | 'center'>(
- 'left'
- );
-
- const [textRotate, setTextRotate] = useState(0);
-
- const [textOptionsVisible, setTextOptionsVisible] = useState(false);
-
- const [backgroundScale, setBackgroundScale] = useState(1);
- const [backgroundRotate, setBackgroundRotate] = useState(0);
- const [backgroundAlpha, setBackgroundAlpha] = useState(1);
- const [watermarkScale, setWatermarkScale] = useState(1);
- const [watermarkRotate, setWatermarkRotate] = useState(0);
- const [watermarkAlpha, setWatermarkAlpha] = useState(1);
- const [quality, setQuality] = useState(100);
- const [fileSize, setFileSize] = useState('0');
- const [fontSize, setFontSize] = useState(44);
-
- function showBackgroundFormatSelector() {
- const options = ['normal image', 'base64', 'cancel'];
- const cancelButtonIndex = 2;
-
- showActionSheetWithOptions(
- {
- options,
- title: 'select background format',
- cancelButtonIndex,
- useModal: true,
- },
- (buttonIndex) => {
- if (buttonIndex === cancelButtonIndex || buttonIndex == null) return;
- else setBackgroundFormat(options[buttonIndex] as any);
- }
- );
- }
-
- function showWatermarkTypeSelector() {
- const options = ['image', 'text', 'cancel'];
- const cancelButtonIndex = 2;
-
- showActionSheetWithOptions(
- {
- options,
- title: 'select watermark type',
- cancelButtonIndex,
- useModal: true,
- },
- (buttonIndex) => {
- if (buttonIndex === cancelButtonIndex || buttonIndex == null) return;
- else setWaterMarkType(options[buttonIndex] as any);
- }
- );
- }
-
- function showExportResultTypeSelector() {
- const options = [
- ImageFormat.png,
- ImageFormat.jpg,
- ImageFormat.base64,
- 'cancel',
- ];
- const cancelButtonIndex = 3;
-
- showActionSheetWithOptions(
- {
- options,
- title: 'select export result format type',
- cancelButtonIndex,
- useModal: true,
- },
- (buttonIndex) => {
- if (buttonIndex === cancelButtonIndex || buttonIndex == null) return;
- else setSaveFormat(options[buttonIndex] as any);
- }
- );
- }
-
- function showPositionSelector() {
- const options = [
- Position.topLeft,
- Position.topCenter,
- Position.topRight,
- Position.center,
- Position.bottomLeft,
- Position.bottomCenter,
- Position.bottomRight,
- 'cancel',
- ];
- const cancelButtonIndex = 7;
-
- showActionSheetWithOptions(
- {
- options,
- title: 'select export result format type',
- cancelButtonIndex,
- useModal: true,
- },
- (buttonIndex) => {
- if (buttonIndex === cancelButtonIndex || buttonIndex == null) return;
- else {
- setPosition(options[buttonIndex] as any);
- }
- }
- );
- }
-
- function showTextBgStretchSelector() {
- const options = [
- TextBackgroundType.none,
- TextBackgroundType.stretchX,
- TextBackgroundType.stretchY,
- 'cancel',
- ];
- const cancelButtonIndex = 3;
-
- showActionSheetWithOptions(
- {
- options,
- title: 'select text bg stretch type',
- cancelButtonIndex,
- useModal: true,
- },
- (buttonIndex) => {
- if (buttonIndex === cancelButtonIndex || buttonIndex == null) return;
- else {
- setTextBgStretch(options[buttonIndex] as any);
- }
- }
- );
- }
-
- function showTextAlignSelector() {
- const options = ['left', 'right', 'center', 'cancel'];
- const cancelButtonIndex = 3;
-
- showActionSheetWithOptions(
- {
- options,
- title: 'select text align type',
- cancelButtonIndex,
- useModal: true,
- },
- (buttonIndex) => {
- if (buttonIndex === cancelButtonIndex || buttonIndex == null) return;
- else {
- setTextAlign(options[buttonIndex] as any);
- }
- }
- );
- }
-
- useEffect(() => {
- if (backgroundFormat === 'normal image') {
- setImage(bg);
- } else {
- setImage(base64Bg);
- }
- }, [backgroundFormat]);
-
- function showLoading() {
- setLoading(true);
- }
-
- async function markByPosition() {
- showLoading();
- let path = '';
-
- try {
- if (waterMarkType === 'image') {
- path = await Marker.markImage({
- backgroundImage: {
- src: image,
- scale: backgroundScale,
- alpha: backgroundAlpha,
- rotate: backgroundRotate,
- },
- watermarkImage: {
- src: marker,
- scale: watermarkScale,
- alpha: watermarkAlpha,
- rotate: watermarkRotate,
- },
- watermarkPositions: {
- position,
- },
- quality,
- saveFormat: saveFormat,
- watermarkImages: [
- {
- src: icon1,
- scale: watermarkScale,
- alpha: watermarkAlpha,
- rotate: watermarkRotate,
- position: {
- position: Position.topLeft,
- },
- },
- {
- src: marker,
- scale: watermarkScale,
- alpha: watermarkAlpha,
- rotate: watermarkRotate,
- position: {
- position: Position.topRight,
- },
- },
- ],
- });
- } else {
- path = await Marker.markText({
- backgroundImage: {
- src: image,
- scale: backgroundScale,
- alpha: backgroundAlpha,
- rotate: backgroundRotate,
- },
- watermarkTexts: [
- {
- text,
- positionOptions: {
- position,
- },
- style: {
- color: '#FF0000AA',
- fontName: 'Arial',
- fontSize,
- underline,
- bold,
- italic,
- strikeThrough,
- textAlign: textAlign,
- rotate: textRotate,
- shadowStyle: useTextShadow
- ? {
- dx: 10.5,
- dy: 20.8,
- radius: 20.9,
- color: '#0000FF',
- }
- : null,
- textBackgroundStyle: useTextBgStyle
- ? {
- type: textBgStretch,
- paddingBottom: '15%',
- paddingRight: '10%',
- paddingTop: '15%',
- paddingLeft: '100',
- color: '#0f0A',
- }
- : null,
- },
- },
- {
- text: `text marker normal`,
- positionOptions: {
- position: Position.center,
- },
- style: {
- color: '#FF00AA9F',
- fontName: 'NotoSansSC-Regular',
- fontSize,
- underline,
- bold,
- italic,
- strikeThrough,
- textAlign: textAlign,
- rotate: textRotate,
- shadowStyle: useTextShadow
- ? {
- dx: 10.5,
- dy: 20.8,
- radius: 20.9,
- color: '#00EEFF',
- }
- : null,
- textBackgroundStyle: useTextBgStyle
- ? {
- type: textBgStretch,
- padding: '10%',
- color: '#0fA',
- cornerRadius: {
- topLeft: {
- x: '20%',
- y: '50%',
- },
- topRight: {
- x: '20%',
- y: '50%',
- },
- },
- }
- : null,
- },
- },
- ],
- quality,
- saveFormat: saveFormat,
- });
- }
- setUri(
- saveFormat === ImageFormat.base64
- ? path
- : Platform.OS === 'android'
- ? 'file:' + path
- : path
- );
- setLoading(false);
- setShow(true);
- const stat = await RNBlobUtil.fs.stat(path);
- setFileSize(filesize.filesize(stat.size));
- } catch (err) {
- console.log('====================================');
- console.log(err, 'err');
- console.log('====================================');
- }
- }
-
- useEffect(() => {
- if (position) {
- markByPosition();
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [position]);
-
- async function mark() {
- showLoading();
- let path = '';
- try {
- if (waterMarkType === 'image') {
- path = await Marker.markImage({
- backgroundImage: {
- src: image,
- scale: backgroundScale,
- rotate: backgroundRotate,
- alpha: backgroundAlpha,
- },
- watermarkImages: [
- {
- src: marker,
- scale: watermarkScale,
- alpha: watermarkAlpha,
- rotate: watermarkRotate,
- position: { X, Y },
- },
- {
- src: icon1,
- scale: watermarkScale,
- alpha: watermarkAlpha,
- rotate: watermarkRotate,
- position: { X: 200, Y: 100 },
- },
- ],
- quality,
- saveFormat: saveFormat,
- });
- } else {
- path = await Marker.markText({
- backgroundImage: {
- src: image,
- scale: backgroundScale,
- alpha: backgroundAlpha,
- rotate: backgroundRotate,
- },
- watermarkTexts: [
- {
- text,
- positionOptions: {
- X,
- Y,
- },
- style: {
- underline,
- strikeThrough,
- color: '#FF0',
- fontName: 'NotoSansSC-Regular',
- fontSize,
- bold,
- italic,
- textAlign: textAlign,
- rotate: textRotate,
- shadowStyle: useTextShadow
- ? {
- dx: 10.5,
- dy: 20.8,
- radius: 20.9,
- color: '#0000FF',
- }
- : null,
- textBackgroundStyle: useTextBgStyle
- ? {
- type: textBgStretch,
- paddingX: 10,
- paddingY: 10,
- color: '#00B96B',
- }
- : null,
- },
- },
- {
- text,
- positionOptions: {
- X: 500,
- Y: 600,
- },
- style: {
- underline: true,
- strikeThrough: true,
- bold: true,
- italic: true,
- color: '#FF0',
- fontSize,
- textAlign: textAlign,
- rotate: textRotate,
- shadowStyle: useTextShadow
- ? {
- dx: 10.5,
- dy: 20.8,
- radius: 20.9,
- color: '#0000FF',
- }
- : null,
- textBackgroundStyle: useTextBgStyle
- ? {
- type: textBgStretch,
- // paddingX: 10,
- // paddingY: 10,
- padding: '10%',
- color: '#0f09',
- }
- : null,
- },
- },
- ],
- quality,
- saveFormat: saveFormat,
- });
- }
- setUri(
- saveFormat === ImageFormat.base64
- ? path
- : Platform.OS === 'android'
- ? 'file:' + path
- : path
- );
- setShow(true);
- setLoading(false);
- const stat = await RNBlobUtil.fs.stat(path);
- setFileSize(filesize.filesize(stat.size));
- } catch (error) {
- console.log('====================================');
- console.log(error, 'error');
- console.log('====================================');
- }
- }
-
- async function pickImage(type: any) {
- const response = await launchImageLibrary({
- quality: 0.5,
- mediaType: 'photo',
- maxWidth: 2000,
- maxHeight: 2000,
- selectionLimit: 1,
- });
-
- if (response.didCancel) {
- console.log('User cancelled photo picker');
- } else if (response.errorCode) {
- console.log('ImagePickerManager Error: ', response.errorMessage);
- } else {
- // You can display the image using either:
- // const source = {uri: 'data:image/jpeg;base64,' + response.data, isStatic: true};
- const myUri = response.assets?.[0]?.uri;
- if (type === 'image') {
- setImage(myUri);
- } else {
- setMarker(myUri);
- }
- }
- }
-
- return {
- state: {
- image,
- uri,
- marker,
- loading,
- show,
- backgroundFormat,
- saveFormat,
- useTextShadow,
- useTextBgStyle,
- textBgStretch,
- waterMarkType,
- text,
- position,
- underline,
- strikeThrough,
- bold,
- italic,
- X,
- Y,
- backgroundScale,
- backgroundAlpha,
- backgroundRotate,
- watermarkScale,
- watermarkAlpha,
- watermarkRotate,
- textOptionsVisible,
- textAlign,
- textRotate,
- quality,
- fileSize,
- fontSize,
- },
- setLoading,
- setImage,
- setMarker,
- setShow,
- setUri,
- setSaveFormat,
- setUseTextShadow,
- setUseTextBgStyle,
- setTextBgStretch,
- mark,
- markByPosition,
- pickImage,
- showBackgroundFormatSelector,
- showWatermarkTypeSelector,
- showExportResultTypeSelector,
- setText,
- showPositionSelector,
- showTextBgStretchSelector,
- setItalic,
- setBold,
- setStrikeThrough,
- setUnderline,
- setX,
- setY,
- setBackgroundAlpha,
- setBackgroundScale,
- setBackgroundRotate,
- setWatermarkAlpha,
- setWatermarkRotate,
- setWatermarkScale,
- setTextOptionsVisible,
- setTextAlign,
- setTextRotate,
- showTextAlignSelector,
- setQuality,
- setFontSize,
- };
-}
-
function App() {
const {
state,
@@ -821,9 +121,6 @@ function App() {
mark,
setUseTextShadow,
setUseTextBgStyle,
- showBackgroundFormatSelector,
- showWatermarkTypeSelector,
- showExportResultTypeSelector,
setText,
showPositionSelector,
showTextBgStretchSelector,
@@ -844,352 +141,341 @@ function App() {
setTextRotate,
setQuality,
setFontSize,
+ setBackgroundFormat,
+ setWaterMarkType,
+ setSaveFormat,
} = useViewModel();
return (
-
-
-
-
-
- background image format:
-
-
-
- {state.backgroundFormat}
-
-
-
-
-
- watermark type:
-
-
-
- {state.waterMarkType}
-
-
-
-
-
- export result format:
-
-
-
- {state.saveFormat}
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ pickImage('image'),
+ }}
+ rotate={{
+ value: state.backgroundRotate,
+ setValue: setBackgroundRotate,
+ testID: 'bgRotate',
+ }}
+ scale={{
+ value: state.backgroundScale,
+ setValue: setBackgroundScale,
+ testID: 'bgScale',
+ }}
+ alpha={{
+ value: state.backgroundAlpha,
+ setValue: setBackgroundAlpha,
+ testID: 'bgAlpha',
+ }}
+ quality={{
+ value: state.quality,
+ setValue: setQuality,
+ testID: 'bgQuality',
+ }}
+ />
+
+ {state.waterMarkType === 'image' ? (
+
+ pickImage('mark'),
}}
- >
- pickImage('image')}
- >
- select bg
-
-
-
-
-
+ />
-
- {state.waterMarkType === 'image' ? (
-
-
+ ) : (
+
+
+
+ )}
+
+
+
+
+
+ given position:
+
+
+
+ {state.position}
+
+
+
+
+
+ custom x/y:
+
+
+ X:
+ setX(v)}
+ />
+ Y:
+ setY(v)}
+ />
+
+ mark
+
+
+
+
+
+
+
+
+
+ text shadow:
+
+
+
+ text background:
+
+
+
+ {state.useTextBgStyle ? (
+
+
+ text bg stretch:
pickImage('mark')}
+ style={s.btn}
+ onPress={showTextBgStretchSelector}
>
- select watermark icon
+
+ {state.textBgStretch == null ||
+ state.textBgStretch === TextBackgroundType.none
+ ? 'fit'
+ : state.textBgStretch}
+
-
-
-
-
- ) : (
-
-
- text watermark:
-
-
-
- fontSize:
- {
- const value = Number(v);
- if (value <= 0) {
- Toast.show({
- type: 'error',
- text1: 'fontSize range error',
- text2: 'fontSize must be greater than 0',
- });
- return;
- }
- setFontSize(value);
- }}
- />
+
+ text align:
setTextOptionsVisible(true)}
+ style={s.btn}
+ onPress={showTextAlignSelector}
>
- options
+ {state.textAlign}
-
- )}
-
-
-
-
-
- given position:
-
-
-
- {state.position}
-
-
-
-
-
- custom x/y:
-
-
- X:
- setX(v)}
- />
- Y:
- setY(v)}
- />
-
- mark
-
-
-
-
-
-
-
-
-
- text shadow:
-
+
+ ) : null}
+
+
+
+ underline:
- text background:
-
- {state.useTextBgStyle ? (
-
-
- text bg stretch:
-
-
- {state.textBgStretch == null ||
- state.textBgStretch === TextBackgroundType.none
- ? 'fit'
- : state.textBgStretch}
-
-
-
-
- text align:
-
- {state.textAlign}
-
-
-
- ) : null}
-
-
-
- underline:
-
-
-
-
+
+
+
+ italic:
-
- italic:
-
-
-
-
+
-
-
-
-
- bold:
-
-
-
-
+
+
+
+
+
+ bold:
-
- strikeThrough:
-
-
-
-
+
+
+
+
+
+ strikeThrough:
-
-
- rotate:
- {
- const value = Number(v);
- if (value < -360 || value > 360) {
- Toast.show({
- type: 'error',
- text1: 'rotate range error',
- text2: 'rotate must be between -360 and 360',
- });
- return;
- }
- setTextRotate(value);
- }}
+
- {/*
+
+
+
+
+ rotate:
+ {
+ const value = Number(v);
+ if (value < -360 || value > 360) {
+ Toast.show({
+ type: 'error',
+ text1: 'rotate range error',
+ text2: 'rotate must be between -360 and 360',
+ });
+ return;
+ }
+ setTextRotate(value);
+ }}
+ />
+
+ {/*
*/}
- {
- setTextOptionsVisible(false);
- }}
- >
- Confirm
-
-
-
+ {
+ setTextOptionsVisible(false);
+ }}
+ >
+ Confirm
+
+
-
-
-
- result file size: {state.fileSize}
-
- {state.show ? (
-
- ) : null}
-
+
+
+
+ result file size:{state.fileSize}
+
+
+ file path:{state.uri}
+
+ {state.show ? (
+
+ ) : null}
+
{state.loading && (
loading...
)}
-
+
);
}
export default function AppContainer() {
return (
- <>
-
-
- >
+
+ <>
+
+
+ >
+
);
}
diff --git a/example/src/ViewModel.ts b/example/src/ViewModel.ts
new file mode 100644
index 00000000..0a52901f
--- /dev/null
+++ b/example/src/ViewModel.ts
@@ -0,0 +1,536 @@
+import { useState, useEffect } from 'react';
+import { Platform } from 'react-native';
+import { launchImageLibrary } from 'react-native-image-picker';
+import { useActionSheet } from '@expo/react-native-action-sheet';
+import Marker, {
+ ImageFormat,
+ Position,
+ TextBackgroundType,
+} from 'react-native-image-marker';
+import RNBlobUtil from 'react-native-blob-util';
+import filesize from 'filesize';
+
+const icon = require('./icon.jpeg');
+const icon1 = require('./yahaha.jpeg');
+const bg = require('./bg.png');
+const base64Bg = require('./bas64bg').default;
+
+function useViewModel() {
+ const { showActionSheetWithOptions } = useActionSheet();
+ const [backgroundFormat, setBackgroundFormat] = useState<'image' | 'base64'>(
+ 'image'
+ );
+ const [waterMarkType, setWaterMarkType] = useState<'text' | 'image'>('text');
+ const [saveFormat, setSaveFormat] = useState(ImageFormat.png);
+ const [image, setImage] = useState(bg);
+ const [uri, setUri] = useState('');
+ const [marker, setMarker] = useState(icon);
+ const [text, setText] = useState('hello world \n 你好');
+ const [useTextShadow, setUseTextShadow] = useState(true);
+ const [useTextBgStyle, setUseTextBgStyle] = useState(true);
+ const [textBgStretch, setTextBgStretch] = useState(
+ TextBackgroundType.none
+ );
+ const [position, setPosition] = useState(Position.topLeft);
+ const [X, setX] = useState(20);
+ const [Y, setY] = useState(20);
+ const [loading, setLoading] = useState(false);
+ const [show, setShow] = useState(false);
+ const [underline, setUnderline] = useState(false);
+ const [italic, setItalic] = useState(false);
+ const [bold, setBold] = useState(false);
+ const [strikeThrough, setStrikeThrough] = useState(false);
+ const [textAlign, setTextAlign] = useState<'left' | 'right' | 'center'>(
+ 'left'
+ );
+
+ const [textRotate, setTextRotate] = useState(0);
+
+ const [textOptionsVisible, setTextOptionsVisible] = useState(false);
+
+ const [backgroundScale, setBackgroundScale] = useState(1);
+ const [backgroundRotate, setBackgroundRotate] = useState(0);
+ const [backgroundAlpha, setBackgroundAlpha] = useState(1);
+ const [watermarkScale, setWatermarkScale] = useState(1);
+ const [watermarkRotate, setWatermarkRotate] = useState(0);
+ const [watermarkAlpha, setWatermarkAlpha] = useState(1);
+ const [quality, setQuality] = useState(100);
+ const [fileSize, setFileSize] = useState('0');
+ const [fontSize, setFontSize] = useState(44);
+
+ function showPositionSelector() {
+ const options = [
+ Position.topLeft,
+ Position.topCenter,
+ Position.topRight,
+ Position.center,
+ Position.bottomLeft,
+ Position.bottomCenter,
+ Position.bottomRight,
+ 'cancel',
+ ];
+ const cancelButtonIndex = 7;
+
+ showActionSheetWithOptions(
+ {
+ options,
+ title: 'select export result format type',
+ cancelButtonIndex,
+ useModal: true,
+ },
+ (buttonIndex) => {
+ if (buttonIndex === cancelButtonIndex || buttonIndex == null) return;
+ else {
+ setPosition(options[buttonIndex] as any);
+ }
+ }
+ );
+ }
+
+ function showTextBgStretchSelector() {
+ const options = [
+ TextBackgroundType.none,
+ TextBackgroundType.stretchX,
+ TextBackgroundType.stretchY,
+ 'cancel',
+ ];
+ const cancelButtonIndex = 3;
+
+ showActionSheetWithOptions(
+ {
+ options,
+ title: 'select text bg stretch type',
+ cancelButtonIndex,
+ useModal: true,
+ },
+ (buttonIndex) => {
+ if (buttonIndex === cancelButtonIndex || buttonIndex == null) return;
+ else {
+ setTextBgStretch(options[buttonIndex] as any);
+ }
+ }
+ );
+ }
+
+ function showTextAlignSelector() {
+ const options = ['left', 'right', 'center', 'cancel'];
+ const cancelButtonIndex = 3;
+
+ showActionSheetWithOptions(
+ {
+ options,
+ title: 'select text align type',
+ cancelButtonIndex,
+ useModal: true,
+ },
+ (buttonIndex) => {
+ if (buttonIndex === cancelButtonIndex || buttonIndex == null) return;
+ else {
+ setTextAlign(options[buttonIndex] as any);
+ }
+ }
+ );
+ }
+
+ useEffect(() => {
+ if (backgroundFormat === 'image') {
+ setImage(bg);
+ } else {
+ setImage(base64Bg);
+ }
+ }, [backgroundFormat]);
+
+ function showLoading() {
+ setLoading(true);
+ }
+
+ async function markByPosition() {
+ showLoading();
+ let path = '';
+
+ try {
+ if (waterMarkType === 'image') {
+ path = await Marker.markImage({
+ backgroundImage: {
+ src: image,
+ scale: backgroundScale,
+ alpha: backgroundAlpha,
+ rotate: backgroundRotate,
+ },
+ watermarkImage: {
+ src: marker,
+ scale: watermarkScale,
+ alpha: watermarkAlpha,
+ rotate: watermarkRotate,
+ },
+ watermarkPositions: {
+ position,
+ },
+ quality,
+ saveFormat: saveFormat,
+ watermarkImages: [
+ {
+ src: icon1,
+ scale: watermarkScale,
+ alpha: watermarkAlpha,
+ rotate: watermarkRotate,
+ position: {
+ position: Position.topLeft,
+ },
+ },
+ {
+ src: marker,
+ scale: watermarkScale,
+ alpha: watermarkAlpha,
+ rotate: watermarkRotate,
+ position: {
+ position: Position.topRight,
+ },
+ },
+ ],
+ });
+ } else {
+ path = await Marker.markText({
+ backgroundImage: {
+ src: image,
+ scale: backgroundScale,
+ alpha: backgroundAlpha,
+ rotate: backgroundRotate,
+ },
+ watermarkTexts: [
+ {
+ text,
+ positionOptions: {
+ position,
+ },
+ style: {
+ color: '#FF0000AA',
+ fontName: 'Arial',
+ fontSize,
+ underline,
+ bold,
+ italic,
+ strikeThrough,
+ textAlign: textAlign,
+ rotate: textRotate,
+ shadowStyle: useTextShadow
+ ? {
+ dx: 10.5,
+ dy: 20.8,
+ radius: 20.9,
+ color: '#0000FF',
+ }
+ : null,
+ textBackgroundStyle: useTextBgStyle
+ ? {
+ type: textBgStretch,
+ paddingBottom: '15%',
+ paddingRight: '10%',
+ paddingTop: '15%',
+ paddingLeft: '100',
+ color: '#0f0A',
+ }
+ : null,
+ },
+ },
+ {
+ text: `text marker normal`,
+ positionOptions: {
+ position: Position.center,
+ },
+ style: {
+ color: '#FF00AA9F',
+ fontName: 'NotoSansSC-Regular',
+ fontSize,
+ underline,
+ bold,
+ italic,
+ strikeThrough,
+ textAlign: textAlign,
+ rotate: textRotate,
+ shadowStyle: useTextShadow
+ ? {
+ dx: 10.5,
+ dy: 20.8,
+ radius: 20.9,
+ color: '#00EEFF',
+ }
+ : null,
+ textBackgroundStyle: useTextBgStyle
+ ? {
+ type: textBgStretch,
+ padding: '10%',
+ color: '#0fA',
+ cornerRadius: {
+ topLeft: {
+ x: '20%',
+ y: '50%',
+ },
+ topRight: {
+ x: '20%',
+ y: '50%',
+ },
+ },
+ }
+ : null,
+ },
+ },
+ ],
+ quality,
+ saveFormat: saveFormat,
+ });
+ }
+ setUri(
+ saveFormat === ImageFormat.base64
+ ? path
+ : Platform.OS === 'android'
+ ? 'file:' + path
+ : path
+ );
+ setLoading(false);
+ setShow(true);
+ const stat = await RNBlobUtil.fs.stat(path);
+ setFileSize(filesize.filesize(stat.size));
+ } catch (err) {
+ console.log('====================================');
+ console.log(err, 'err');
+ console.log('====================================');
+ }
+ }
+
+ useEffect(() => {
+ if (position) {
+ markByPosition();
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [position]);
+
+ async function mark() {
+ showLoading();
+ let path = '';
+ try {
+ if (waterMarkType === 'image') {
+ path = await Marker.markImage({
+ backgroundImage: {
+ src: image,
+ scale: backgroundScale,
+ rotate: backgroundRotate,
+ alpha: backgroundAlpha,
+ },
+ watermarkImages: [
+ {
+ src: marker,
+ scale: watermarkScale,
+ alpha: watermarkAlpha,
+ rotate: watermarkRotate,
+ position: { X, Y },
+ },
+ {
+ src: icon1,
+ scale: watermarkScale,
+ alpha: watermarkAlpha,
+ rotate: watermarkRotate,
+ position: { X: 200, Y: 100 },
+ },
+ ],
+ quality,
+ saveFormat: saveFormat,
+ });
+ } else {
+ path = await Marker.markText({
+ backgroundImage: {
+ src: image,
+ scale: backgroundScale,
+ alpha: backgroundAlpha,
+ rotate: backgroundRotate,
+ },
+ watermarkTexts: [
+ {
+ text,
+ positionOptions: {
+ X,
+ Y,
+ },
+ style: {
+ underline,
+ strikeThrough,
+ color: '#FF0',
+ fontName: 'NotoSansSC-Regular',
+ fontSize,
+ bold,
+ italic,
+ textAlign: textAlign,
+ rotate: textRotate,
+ shadowStyle: useTextShadow
+ ? {
+ dx: 10.5,
+ dy: 20.8,
+ radius: 20.9,
+ color: '#0000FF',
+ }
+ : null,
+ textBackgroundStyle: useTextBgStyle
+ ? {
+ type: textBgStretch,
+ paddingX: 10,
+ paddingY: 10,
+ color: '#00B96B',
+ }
+ : null,
+ },
+ },
+ {
+ text,
+ positionOptions: {
+ X: 500,
+ Y: 600,
+ },
+ style: {
+ underline: true,
+ strikeThrough: true,
+ bold: true,
+ italic: true,
+ color: '#FF0',
+ fontSize,
+ textAlign: textAlign,
+ rotate: textRotate,
+ shadowStyle: useTextShadow
+ ? {
+ dx: 10.5,
+ dy: 20.8,
+ radius: 20.9,
+ color: '#0000FF',
+ }
+ : null,
+ textBackgroundStyle: useTextBgStyle
+ ? {
+ type: textBgStretch,
+ // paddingX: 10,
+ // paddingY: 10,
+ padding: '10%',
+ color: '#0f09',
+ }
+ : null,
+ },
+ },
+ ],
+ quality,
+ saveFormat: saveFormat,
+ });
+ }
+ setUri(
+ saveFormat === ImageFormat.base64
+ ? path
+ : Platform.OS === 'android'
+ ? 'file:' + path
+ : path
+ );
+ setShow(true);
+ setLoading(false);
+ const stat = await RNBlobUtil.fs.stat(path);
+ setFileSize(filesize.filesize(stat.size));
+ } catch (error) {
+ console.log('====================================');
+ console.log(error, 'error');
+ console.log('====================================');
+ }
+ }
+
+ async function pickImage(type: any) {
+ const response = await launchImageLibrary({
+ quality: 0.5,
+ mediaType: 'photo',
+ maxWidth: 2000,
+ maxHeight: 2000,
+ selectionLimit: 1,
+ });
+
+ if (response.didCancel) {
+ console.log('User cancelled photo picker');
+ } else if (response.errorCode) {
+ console.log('ImagePickerManager Error: ', response.errorMessage);
+ } else {
+ // You can display the image using either:
+ // const source = {uri: 'data:image/jpeg;base64,' + response.data, isStatic: true};
+ const myUri = response.assets?.[0]?.uri;
+ if (type === 'image') {
+ setImage(myUri);
+ } else {
+ setMarker(myUri);
+ }
+ }
+ }
+
+ return {
+ state: {
+ image,
+ uri,
+ marker,
+ loading,
+ show,
+ backgroundFormat,
+ saveFormat,
+ useTextShadow,
+ useTextBgStyle,
+ textBgStretch,
+ waterMarkType,
+ text,
+ position,
+ underline,
+ strikeThrough,
+ bold,
+ italic,
+ X,
+ Y,
+ backgroundScale,
+ backgroundAlpha,
+ backgroundRotate,
+ watermarkScale,
+ watermarkAlpha,
+ watermarkRotate,
+ textOptionsVisible,
+ textAlign,
+ textRotate,
+ quality,
+ fileSize,
+ fontSize,
+ },
+ setLoading,
+ setImage,
+ setMarker,
+ setShow,
+ setUri,
+ setSaveFormat,
+ setUseTextShadow,
+ setUseTextBgStyle,
+ setTextBgStretch,
+ mark,
+ markByPosition,
+ pickImage,
+ setWaterMarkType,
+ setText,
+ showPositionSelector,
+ showTextBgStretchSelector,
+ setItalic,
+ setBold,
+ setStrikeThrough,
+ setUnderline,
+ setX,
+ setY,
+ setBackgroundAlpha,
+ setBackgroundScale,
+ setBackgroundRotate,
+ setWatermarkAlpha,
+ setWatermarkRotate,
+ setWatermarkScale,
+ setTextOptionsVisible,
+ setTextAlign,
+ setTextRotate,
+ showTextAlignSelector,
+ setQuality,
+ setFontSize,
+ setBackgroundFormat,
+ };
+}
+
+export default useViewModel;
diff --git a/example/src/components/ImageOptions.tsx b/example/src/components/ImageOptions.tsx
new file mode 100644
index 00000000..d8cb4bf2
--- /dev/null
+++ b/example/src/components/ImageOptions.tsx
@@ -0,0 +1,144 @@
+import * as React from 'react';
+import { Card, Button } from 'react-native-paper';
+import { StyleSheet } from 'react-native';
+
+const s = StyleSheet.create({
+ row: {
+ marginVertical: 8,
+ flex: 1,
+ marginHorizontal: 8,
+ paddingHorizontal: 8,
+ },
+});
+
+import TextInput from './TextInput';
+
+export interface ValueOptions {
+ value: number;
+ setValue: (value: number) => void;
+ testID?: string;
+}
+
+export default function ImageOptions(props: {
+ alpha: ValueOptions;
+ scale: ValueOptions;
+ rotate: ValueOptions;
+ quality?: ValueOptions;
+ pickerOptions: {
+ pickImage: () => void;
+ testID?: string;
+ label: string;
+ };
+}) {
+ const { alpha, scale, rotate, quality } = props;
+ return (
+
+
+
+
+
+ {
+ const value = Number(v);
+ scale.setValue(value);
+ }}
+ validate={(v) => {
+ const value = Number(v);
+ if (value < 0) {
+ return {
+ pass: false,
+ message: 'scale must be greater than 0',
+ };
+ }
+ return {
+ pass: true,
+ message: '',
+ };
+ }}
+ />
+ {
+ const value = Number(v);
+ alpha.setValue(value);
+ }}
+ validate={(v) => {
+ const value = Number(v);
+ if (value < 0 || value > 1) {
+ return {
+ pass: false,
+ message: 'alpha must be between 0 and 1',
+ };
+ }
+ return {
+ pass: true,
+ message: '',
+ };
+ }}
+ />
+ {
+ const value = Number(v);
+ rotate.setValue(value);
+ }}
+ validate={(v) => {
+ const value = Number(v);
+ if (value < -360 || value > 360) {
+ return {
+ pass: false,
+ message: 'rotate must be between -360 and 360',
+ };
+ }
+ return {
+ pass: true,
+ message: '',
+ };
+ }}
+ />
+ {quality && (
+ {
+ const value = Number(v);
+ quality.setValue(value);
+ }}
+ validate={(v) => {
+ const value = Number(v);
+ if (value < 0 || value > 100) {
+ return {
+ pass: false,
+ message: 'quality must be between 0 and 100',
+ };
+ }
+ return {
+ pass: true,
+ message: '',
+ };
+ }}
+ />
+ )}
+
+
+ );
+}
diff --git a/example/src/components/RadioGroup.tsx b/example/src/components/RadioGroup.tsx
new file mode 100644
index 00000000..594715b6
--- /dev/null
+++ b/example/src/components/RadioGroup.tsx
@@ -0,0 +1,102 @@
+import * as React from 'react';
+import { RadioButton, Card } from 'react-native-paper';
+import { View, StyleSheet } from 'react-native';
+
+const s = StyleSheet.create({
+ card: {
+ marginVertical: 8,
+ flex: 1,
+ marginHorizontal: 8,
+ paddingHorizontal: 8,
+ },
+ cardTitle: {
+ minHeight: 16,
+ paddingLeft: 8,
+ paddingTop: 8,
+ marginRight: 8,
+ },
+ cardTitleText: {
+ lineHeight: 16,
+ fontWeight: 'bold',
+ minHeight: 16,
+ },
+ cardContent: {
+ display: 'flex',
+ flexDirection: 'row',
+ alignItems: 'flex-start',
+ justifyContent: 'flex-start',
+ flex: 1,
+ paddingVertical: 0,
+ paddingHorizontal: 0,
+ paddingBottom: 0,
+ },
+ radioButtons: {
+ display: 'flex',
+ flexDirection: 'row',
+ alignItems: 'flex-start',
+ justifyContent: 'flex-start',
+ flex: 1,
+ },
+ radioItem: {
+ paddingVertical: 0,
+ paddingLeft: 0,
+ paddingRight: 0,
+ marginLeft: 0,
+ },
+});
+
+export default (props: {
+ defaultValue: string;
+ title?: string;
+ subTitle?: string;
+ onValueChange: (value: any) => void;
+ options: Array<{
+ label: string;
+ value: string;
+ testID?: string;
+ }>;
+}) => {
+ const [value, setValue] = React.useState(props.defaultValue);
+
+ React.useEffect(() => {
+ typeof props.onValueChange === 'function' && props.onValueChange(value);
+ }, [props, value]);
+
+ return (
+
+ {props.title && (
+
+ )}
+
+ setValue(newValue)}
+ value={value}
+ >
+
+ {props.options.map((option) => (
+
+ ))}
+
+
+
+
+ );
+};
diff --git a/example/src/components/Row.tsx b/example/src/components/Row.tsx
new file mode 100644
index 00000000..c1e32d3a
--- /dev/null
+++ b/example/src/components/Row.tsx
@@ -0,0 +1,26 @@
+import React from 'react';
+import { StyleSheet, View } from 'react-native';
+
+const s = StyleSheet.create({
+ row: {
+ flexDirection: 'row',
+ justifyContent: 'flex-start',
+ alignItems: 'center',
+ marginTop: 5,
+ flex: 1,
+ },
+ rowSplit: {
+ flex: 1,
+ alignItems: 'center',
+ justifyContent: 'flex-start',
+ flexDirection: 'row',
+ },
+});
+
+export function RowSplit(props: any) {
+ return {props.children};
+}
+
+export function Row(props: any) {
+ return {props.children};
+}
diff --git a/example/src/components/TextInput.tsx b/example/src/components/TextInput.tsx
new file mode 100644
index 00000000..9c38e687
--- /dev/null
+++ b/example/src/components/TextInput.tsx
@@ -0,0 +1,69 @@
+import * as React from 'react';
+import { TextInput, HelperText } from 'react-native-paper';
+import { StyleSheet, View } from 'react-native';
+
+const s = StyleSheet.create({
+ shortTextInput: {
+ height: 30,
+ backgroundColor: '#ffA',
+ borderColor: '#00B96B5A',
+ borderWidth: 0,
+ padding: 0,
+ fontSize: 12,
+ },
+});
+
+export default (props: {
+ label: string;
+ defaultValue: any;
+ placeholder: string;
+ multiline?: boolean;
+ onChangeText: (value: any) => void;
+ validate: (value: string) => { pass: boolean; message: string };
+ testID?: string;
+ textColor?: string;
+ style?: any;
+ numberOfLines?: number;
+ contentStyle?: any;
+}) => {
+ const [value, setValue] = React.useState(props.defaultValue);
+ const [error, setError] = React.useState('');
+
+ React.useEffect(() => {
+ typeof props.onChangeText === 'function' && props.onChangeText(value);
+ }, [props, value]);
+
+ return (
+
+ {
+ if (typeof props.validate === 'function') {
+ const { pass, message } = props.validate(v);
+ if (!pass) {
+ setError(message);
+ return;
+ } else {
+ setError('');
+ }
+ }
+ setValue(value);
+ }}
+ autoFocus={false}
+ />
+
+ {error}
+
+
+ );
+};
diff --git a/example/src/components/TextOptions.tsx b/example/src/components/TextOptions.tsx
new file mode 100644
index 00000000..b116c521
--- /dev/null
+++ b/example/src/components/TextOptions.tsx
@@ -0,0 +1,172 @@
+import * as React from 'react';
+import { Card, Text, Switch } from 'react-native-paper';
+import { StyleSheet } from 'react-native';
+
+import TextInput from './TextInput';
+import { Row, RowSplit } from './Row';
+
+const s = StyleSheet.create({
+ label: {
+ marginRight: 8,
+ },
+ container: {
+ flex: 1,
+ },
+ markTextInputContentStyle: {
+ minHeight: 120,
+ paddingTop: 8,
+ paddingBottom: 8,
+ },
+ markTextInputContainerStyle: {
+ minHeight: 120,
+ marginRight: 8,
+ },
+ splitContent: {
+ display: 'flex',
+ flexDirection: 'column',
+ },
+});
+
+export default function TextOptions(props: {
+ text: string;
+ setText: (value: string) => void;
+ fontSize: number;
+ setFontSize: (value: number) => void;
+ useTextShadow: boolean;
+ setUseTextShadow: (value: boolean) => void;
+ useTextBgStyle: boolean;
+ setUseTextBgStyle: (value: boolean) => void;
+ underline: boolean;
+ setUnderline: (value: boolean) => void;
+ italic: boolean;
+ setItalic: (value: boolean) => void;
+ bold: boolean;
+ setBold: (value: boolean) => void;
+ strikeThrough: boolean;
+ setStrikeThrough: (value: boolean) => void;
+}) {
+ const {
+ text,
+ fontSize,
+ setText,
+ setFontSize,
+ useTextBgStyle,
+ setUseTextBgStyle,
+ useTextShadow,
+ setUseTextShadow,
+ underline,
+ setUnderline,
+ italic,
+ setItalic,
+ bold,
+ setBold,
+ strikeThrough,
+ setStrikeThrough,
+ } = props;
+ return (
+
+
+
+
+ {
+ setText(v);
+ }}
+ validate={(v) => {
+ if (!v) {
+ return {
+ pass: false,
+ message: 'text water mark must not be empty',
+ };
+ }
+ return {
+ pass: true,
+ message: '',
+ };
+ }}
+ />
+
+
+
+ {
+ const value = Number(v);
+ if (value <= 0) {
+ return {
+ pass: false,
+ message: 'font size must be greater than 0',
+ };
+ }
+ return {
+ pass: true,
+ message: '',
+ };
+ }}
+ defaultValue={fontSize}
+ onChangeText={setFontSize}
+ />
+
+
+ shadow:
+
+
+
+
+
+
+ background:
+
+
+
+
+
+
+ underline:
+
+
+
+
+
+
+
+ italic:
+
+
+
+
+
+
+
+
+
+ bold:
+
+
+
+
+
+
+
+ strikeThrough:
+
+
+
+
+
+
+
+
+ );
+}