diff --git a/packages/jsActions/nanoflow-actions-native/src/other/Base64DecodeToImage.ts b/packages/jsActions/nanoflow-actions-native/src/other/Base64DecodeToImage.ts index 6b1ff8f7c..53469c366 100644 --- a/packages/jsActions/nanoflow-actions-native/src/other/Base64DecodeToImage.ts +++ b/packages/jsActions/nanoflow-actions-native/src/other/Base64DecodeToImage.ts @@ -6,7 +6,6 @@ // - the code between BEGIN EXTRA CODE and END EXTRA CODE // Other code you write will be lost the next time you deploy the project. import { Base64 } from "js-base64"; -import RNBlobUtil from "react-native-blob-util"; // BEGIN EXTRA CODE // END EXTRA CODE @@ -30,28 +29,7 @@ export async function Base64DecodeToImage(base64: string, image: mendix.lib.MxOb // Native platform if (navigator && navigator.product === "ReactNative") { try { - // Remove data URI prefix if present (e.g., "data:image/png;base64,") - let cleanBase64 = base64; - if (base64.includes(",")) { - cleanBase64 = base64.split(",")[1]; - } - - // Remove any whitespace/newlines - cleanBase64 = cleanBase64.replace(/\s/g, ""); - - // Validate base64 format - if (!/^[A-Za-z0-9+/]*={0,2}$/.test(cleanBase64)) { - throw new Error("Invalid base64 format"); - } - - // Create a temporary file path - const tempPath = `${RNBlobUtil.fs.dirs.CacheDir}/temp_image_${Date.now()}.png`; - - // Write Base64 data to a temporary file - await RNBlobUtil.fs.writeFile(tempPath, cleanBase64, "base64"); - - // Fetch the file as a blob - const res = await fetch(`file://${tempPath}`); + const res = await fetch(base64); const blob = await res.blob(); return new Promise((resolve, reject) => { @@ -61,11 +39,9 @@ export async function Base64DecodeToImage(base64: string, image: mendix.lib.MxOb {}, blob, () => { - RNBlobUtil.fs.unlink(tempPath).catch(e => console.info("Temp file cleanup failed:", e)); resolve(true); }, error => { - RNBlobUtil.fs.unlink(tempPath).catch(e => console.info("Temp file cleanup failed:", e)); reject(error); } ); diff --git a/packages/pluggableWidgets/signature-native/CHANGELOG.md b/packages/pluggableWidgets/signature-native/CHANGELOG.md index a30ff810e..0c3555e5b 100644 --- a/packages/pluggableWidgets/signature-native/CHANGELOG.md +++ b/packages/pluggableWidgets/signature-native/CHANGELOG.md @@ -6,6 +6,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Added + +- Added direct image upload to System.Image object using the `imageSource` property with `allowUpload` enabled +- Added optional `hasSignatureAttribute` Boolean attribute to track whether a signature has been captured or cleared + +### Changed + +- Renamed `onSave` action to `onSignEnd` to match web signature widget naming convention +- Removed `imageAttribute` property in favor of unified `imageSource` property + ## [2.3.0] - 2025-7-7 - Updated react-native-webview from version v13.12.5 to latest diff --git a/packages/pluggableWidgets/signature-native/package.json b/packages/pluggableWidgets/signature-native/package.json index 8a1de5e7e..57a82a2dc 100644 --- a/packages/pluggableWidgets/signature-native/package.json +++ b/packages/pluggableWidgets/signature-native/package.json @@ -1,7 +1,7 @@ { "name": "signature-native", "widgetName": "Signature", - "version": "2.3.0", + "version": "2.4.0", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/pluggableWidgets/signature-native/src/Signature.tsx b/packages/pluggableWidgets/signature-native/src/Signature.tsx index 02b99ceb1..01e3c27c7 100644 --- a/packages/pluggableWidgets/signature-native/src/Signature.tsx +++ b/packages/pluggableWidgets/signature-native/src/Signature.tsx @@ -10,6 +10,12 @@ import { SignatureStyle, defaultSignatureStyle, webStyles } from "./ui/Styles"; export type Props = SignatureProps; +async function dataUriToFile(dataUri: string): Promise { + const response = await fetch(dataUri); + const blob = await response.blob(); + return new File([blob], `signature_${Date.now()}.png`, { type: "image/png", lastModified: Date.now() }); +} + export function Signature(props: Props): ReactElement { const ref = useRef(null); const styles = mergeNativeStyles(defaultSignatureStyle, props.style); @@ -28,11 +34,22 @@ export function Signature(props: Props): ReactElement { const buttonCaptionSave = props.buttonCaptionSave?.value ?? "Save"; const handleSignature = useCallback( - (base64signature: string): void => { - props.imageAttribute.setValue(base64signature); - executeAction(props.onSave); + async (dataUri: string): Promise => { + try { + /* + if (props.imageSource.status !== "available" || props.imageSource.readOnly) { + return; + } This check needs to add once the EditableImageValue is released from widget tools + */ + const blob = await dataUriToFile(dataUri); + (props.imageSource as any)?.setValue(blob); // as any hack needs to remove once the EditableImageValue is released from widget tools + props.hasSignatureAttribute?.setValue(true); + executeAction(props.onSignEndAction); + } catch (error) { + console.error("Signature: failed to save image", error); + } }, - [props.imageAttribute, props.onSave] + [props.imageSource, props.hasSignatureAttribute, props.onSignEndAction] ); return ( @@ -43,7 +60,10 @@ export function Signature(props: Props): ReactElement { onEmpty={() => executeAction(props.onEmpty)} onEnd={() => executeAction(props.onEnd)} onOK={handleSignature} - onClear={() => executeAction(props.onClear)} + onClear={() => { + props.hasSignatureAttribute?.setValue(false); + executeAction(props.onClear); + }} webStyle={webStyles} {...signatureProps} /> diff --git a/packages/pluggableWidgets/signature-native/src/Signature.xml b/packages/pluggableWidgets/signature-native/src/Signature.xml index aef8609f4..d66a3f783 100644 --- a/packages/pluggableWidgets/signature-native/src/Signature.xml +++ b/packages/pluggableWidgets/signature-native/src/Signature.xml @@ -1,21 +1,22 @@ - - + + Signature Display signature. - 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 - - - Attribute - + + + Image + The System.Image (or specialization) object the signature will be saved into. Commit the object in your On sign end handler to persist it. + + + Has signature + Optional Boolean attribute that is set to true when a signature has been captured and false when cleared. Useful for validation and conditional visibility. - + @@ -35,9 +36,9 @@ On clear Fired when the clear button is clicked. - - On save - Fired when the save button is clicked. + + On sign end + Fired after the signature image has been saved to the image object. Commit the object here to persist it. On end @@ -49,7 +50,7 @@ - + diff --git a/packages/pluggableWidgets/signature-native/src/__tests__/Signature.android.spec.tsx b/packages/pluggableWidgets/signature-native/src/__tests__/Signature.android.spec.tsx index 0e8960057..e6a256474 100644 --- a/packages/pluggableWidgets/signature-native/src/__tests__/Signature.android.spec.tsx +++ b/packages/pluggableWidgets/signature-native/src/__tests__/Signature.android.spec.tsx @@ -4,7 +4,14 @@ import SignatureScreen from "react-native-signature-canvas"; import { fireEvent, render } from "@testing-library/react-native"; import { Signature, Props } from "../Signature"; -import { actionValue, dynamicValue, EditableValueBuilder } from "@mendix/piw-utils-internal"; +import { actionValue, dynamicValue } from "@mendix/piw-utils-internal"; + +// Mock fetch for dataUriToFile +global.fetch = jest.fn(() => + Promise.resolve({ + blob: () => Promise.resolve(new Blob()) + }) +) as jest.Mock; jest.mock("react-native", () => { const RN = jest.requireActual("react-native"); @@ -40,10 +47,14 @@ jest.mock("react-native/Libraries/Components/Touchable/TouchableNativeFeedback", return TouchableNativeFeedback; }); +const mockImageSource: any = { + setValue: jest.fn() +}; + const defaultProps: Props = { name: "signature-test", style: [], - imageAttribute: new EditableValueBuilder().withValue("").build(), + imageSource: mockImageSource, buttonCaptionClear: dynamicValue("Clear"), buttonCaptionSave: dynamicValue("Save") }; @@ -88,13 +99,17 @@ describe("Signature Android", () => { fireEvent(canvas, "onClear"); expect(onClearAction.execute).toHaveBeenCalledTimes(1); }); - it("on save", () => { - const onSaveAction = actionValue(); - const component = render(); + it("on sign end", async () => { + const onSignEndAction = actionValue(); + const component = render(); const canvas = component.UNSAFE_getByType(SignatureScreen); - fireEvent(canvas, "onOK"); - expect(onSaveAction.execute).toHaveBeenCalledTimes(1); + fireEvent(canvas, "onOK", "data:image/png;base64,test"); + + // Wait for async operations + await new Promise(resolve => setTimeout(resolve, 0)); + + expect(onSignEndAction.execute).toHaveBeenCalledTimes(1); }); it("on empty", () => { const onEmptyAction = actionValue(); diff --git a/packages/pluggableWidgets/signature-native/src/__tests__/Signature.ios.spec.tsx b/packages/pluggableWidgets/signature-native/src/__tests__/Signature.ios.spec.tsx index 13ffc39b9..58d17edc9 100644 --- a/packages/pluggableWidgets/signature-native/src/__tests__/Signature.ios.spec.tsx +++ b/packages/pluggableWidgets/signature-native/src/__tests__/Signature.ios.spec.tsx @@ -2,7 +2,15 @@ import SignatureScreen from "react-native-signature-canvas"; import { fireEvent, render } from "@testing-library/react-native"; import { Signature, Props } from "../Signature"; -import { actionValue, dynamicValue, EditableValueBuilder } from "@mendix/piw-utils-internal"; + +import { actionValue, dynamicValue } from "@mendix/piw-utils-internal"; + +// Mock fetch for dataUriToFile +global.fetch = jest.fn(() => + Promise.resolve({ + blob: () => Promise.resolve(new Blob()) + }) +) as jest.Mock; jest.mock("react-native", () => { const RN = jest.requireActual("react-native"); @@ -16,10 +24,14 @@ jest.mock("react-native/Libraries/Utilities/Platform", () => { return Platform; }); +const mockImageSource: any = { + setValue: jest.fn() +}; + const defaultProps: Props = { name: "signature-test", style: [], - imageAttribute: new EditableValueBuilder().withValue("").build(), + imageSource: mockImageSource, buttonCaptionClear: dynamicValue("Clear"), buttonCaptionSave: dynamicValue("Save") }; @@ -74,13 +86,17 @@ describe("Signature iOS", () => { fireEvent(canvas, "onClear"); expect(onClearAction.execute).toHaveBeenCalledTimes(1); }); - it("on save", () => { - const onSaveAction = actionValue(); - const component = render(); + it("on sign end", async () => { + const onSignEndAction = actionValue(); + const component = render(); const canvas = component.UNSAFE_getByType(SignatureScreen); - fireEvent(canvas, "onOK"); - expect(onSaveAction.execute).toHaveBeenCalledTimes(1); + await fireEvent(canvas, "onOK", "data:image/png;base64,test"); + + // Wait for async operations + await new Promise(resolve => setTimeout(resolve, 0)); + + expect(onSignEndAction.execute).toHaveBeenCalledTimes(1); }); it("on empty", () => { const onEmptyAction = actionValue(); diff --git a/packages/pluggableWidgets/signature-native/src/package.xml b/packages/pluggableWidgets/signature-native/src/package.xml index 5ad56899c..457e35131 100644 --- a/packages/pluggableWidgets/signature-native/src/package.xml +++ b/packages/pluggableWidgets/signature-native/src/package.xml @@ -1,6 +1,6 @@ - + diff --git a/packages/pluggableWidgets/signature-native/typings/SignatureProps.d.ts b/packages/pluggableWidgets/signature-native/typings/SignatureProps.d.ts index ebc9afc79..5eec25705 100644 --- a/packages/pluggableWidgets/signature-native/typings/SignatureProps.d.ts +++ b/packages/pluggableWidgets/signature-native/typings/SignatureProps.d.ts @@ -4,16 +4,17 @@ * @author Mendix Widgets Framework Team */ import { CSSProperties } from "react"; -import { ActionValue, DynamicValue, EditableValue } from "mendix"; +import { ActionValue, DynamicValue, EditableValue, NativeImage } from "mendix"; export interface SignatureProps