From 125e8a041c6d4d5c74bcfcee1ab60d6ff6655324 Mon Sep 17 00:00:00 2001 From: Shivang Mishra Date: Mon, 23 Jun 2025 04:14:27 +0530 Subject: [PATCH 01/12] impl signature form element --- packages/openchs-android/package-lock.json | 49 ++++-- packages/openchs-android/package.json | 3 +- .../form/formElement/SignatureFormElement.js | 157 ++++++++++++++++++ 3 files changed, 192 insertions(+), 17 deletions(-) create mode 100644 packages/openchs-android/src/views/form/formElement/SignatureFormElement.js diff --git a/packages/openchs-android/package-lock.json b/packages/openchs-android/package-lock.json index ca8c9a09b..30b12539e 100644 --- a/packages/openchs-android/package-lock.json +++ b/packages/openchs-android/package-lock.json @@ -66,13 +66,14 @@ "react-native-randombytes": "^3.6.1", "react-native-restart": "0.0.24", "react-native-safe-area-context": "4.3.1", + "react-native-signature-canvas": "^4.7.4", "react-native-simple-dialogs": "1.5.0", "react-native-smooth-pincode-input": "1.0.9", "react-native-svg": "12.4.3", "react-native-vector-icons": "9.2.0", "react-native-video": "5.2.1", "react-native-video-player": "0.12.0", - "react-native-webview": "11.23.0", + "react-native-webview": "^13.15.0", "react-native-zip-archive": "6.0.8", "realm": "11.8.0", "redux": "4.2.0", @@ -18044,6 +18045,14 @@ "react-native": "*" } }, + "node_modules/react-native-signature-canvas": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/react-native-signature-canvas/-/react-native-signature-canvas-4.7.4.tgz", + "integrity": "sha512-8KbZ35yS2nchunk47mQ4lz8JQyvOgLs2rOX45TzqIcFSSIsmCMvbiqLzGH0gLYNq/A5s0Xg+CupzcOfvO5pjRA==", + "peerDependencies": { + "react-native-webview": ">=13" + } + }, "node_modules/react-native-simple-dialogs": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/react-native-simple-dialogs/-/react-native-simple-dialogs-1.5.0.tgz", @@ -18176,11 +18185,11 @@ } }, "node_modules/react-native-webview": { - "version": "11.23.0", - "resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-11.23.0.tgz", - "integrity": "sha512-mGrgsMnYcQONvQy59xpBn87sKqkCsSkqIDRo+c2Ov4ISYl1j90wFBs+qViVJRWdoNHVuoCAZ4nZkJ65mhDpHhA==", + "version": "13.15.0", + "resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.15.0.tgz", + "integrity": "sha512-Vzjgy8mmxa/JO6l5KZrsTC7YemSdq+qB01diA0FqjUTaWGAGwuykpJ73MDj3+mzBSlaDxAEugHzTtkUQkQEQeQ==", "dependencies": { - "escape-string-regexp": "2.0.0", + "escape-string-regexp": "^4.0.0", "invariant": "2.2.4" }, "peerDependencies": { @@ -18189,11 +18198,14 @@ } }, "node_modules/react-native-webview/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/react-native-zip-archive": { @@ -36678,6 +36690,11 @@ "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-4.3.1.tgz", "integrity": "sha512-cEr7fknJCToTrSyDCVNg0GRdRMhyLeQa2NZwVCuzEQcWedOw/59ExomjmzCE4rxrKXs6OJbyfNtFRNyewDaHuA==" }, + "react-native-signature-canvas": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/react-native-signature-canvas/-/react-native-signature-canvas-4.7.4.tgz", + "integrity": "sha512-8KbZ35yS2nchunk47mQ4lz8JQyvOgLs2rOX45TzqIcFSSIsmCMvbiqLzGH0gLYNq/A5s0Xg+CupzcOfvO5pjRA==" + }, "react-native-simple-dialogs": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/react-native-simple-dialogs/-/react-native-simple-dialogs-1.5.0.tgz", @@ -36776,18 +36793,18 @@ "integrity": "sha512-7h288hwlvjxxBOJ1UOoUctW8auPyq22Lxltc1FIxSJAF/tYyPcnBxAtaWOxI7CGflRn51BLVlUTclXC4CB3BZA==" }, "react-native-webview": { - "version": "11.23.0", - "resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-11.23.0.tgz", - "integrity": "sha512-mGrgsMnYcQONvQy59xpBn87sKqkCsSkqIDRo+c2Ov4ISYl1j90wFBs+qViVJRWdoNHVuoCAZ4nZkJ65mhDpHhA==", + "version": "13.15.0", + "resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.15.0.tgz", + "integrity": "sha512-Vzjgy8mmxa/JO6l5KZrsTC7YemSdq+qB01diA0FqjUTaWGAGwuykpJ73MDj3+mzBSlaDxAEugHzTtkUQkQEQeQ==", "requires": { - "escape-string-regexp": "2.0.0", + "escape-string-regexp": "^4.0.0", "invariant": "2.2.4" }, "dependencies": { "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" } } }, diff --git a/packages/openchs-android/package.json b/packages/openchs-android/package.json index dae80bbab..7cca4ee86 100644 --- a/packages/openchs-android/package.json +++ b/packages/openchs-android/package.json @@ -91,13 +91,14 @@ "react-native-randombytes": "^3.6.1", "react-native-restart": "0.0.24", "react-native-safe-area-context": "4.3.1", + "react-native-signature-canvas": "^4.7.4", "react-native-simple-dialogs": "1.5.0", "react-native-smooth-pincode-input": "1.0.9", "react-native-svg": "12.4.3", "react-native-vector-icons": "9.2.0", "react-native-video": "5.2.1", "react-native-video-player": "0.12.0", - "react-native-webview": "11.23.0", + "react-native-webview": "^13.15.0", "react-native-zip-archive": "6.0.8", "realm": "11.8.0", "redux": "4.2.0", diff --git a/packages/openchs-android/src/views/form/formElement/SignatureFormElement.js b/packages/openchs-android/src/views/form/formElement/SignatureFormElement.js new file mode 100644 index 000000000..4495b67a5 --- /dev/null +++ b/packages/openchs-android/src/views/form/formElement/SignatureFormElement.js @@ -0,0 +1,157 @@ +import PropTypes from "prop-types"; +import React from "react"; +import AbstractFormElement from "./AbstractFormElement"; +import {StyleSheet, View, Image, TouchableNativeFeedback, Alert} from "react-native"; +import SignatureCanvas from "react-native-signature-canvas"; +import ValidationErrorMessage from "../ValidationErrorMessage"; +import FormElementLabelWithDocumentation from "../../common/FormElementLabelWithDocumentation"; +import Colors from "../../primitives/Colors"; +import General from "../../../utility/General"; +import _ from "lodash"; +import Icon from "react-native-vector-icons/MaterialCommunityIcons"; +import {ValidationResult} from "openchs-models"; +import { AlertMessage } from "../../common/AlertMessage"; +import FileSystem from "../../../model/FileSystem"; +import fs from 'react-native-fs'; + +class SignatureFormElement extends AbstractFormElement { + static signatureFileDirectory = FileSystem.getImagesDir(); + static propTypes = { + element: PropTypes.object.isRequired, + actionName: PropTypes.string.isRequired, + value: PropTypes.object, + validationResult: PropTypes.object, + }; + + constructor(props, context) { + super(props, context); + this.signatureRef = React.createRef(); + } + + get signatureFilename() { + return _.get(this, "props.value.answer"); + } + + updateValue(signatureValue, validationResult = null) { + if (General.isNilOrEmpty(signatureValue)) { + this.onUpdateObservations(null); + return; + } + + const [header, base64Data] = signatureValue.split(','); + const mimeType = header.match(/data:(.*?);/)[1]; + const ext = mimeType.split('/')[1]; + + const fileName = `${General.randomUUID()}.${ext}`; + const filePath = `${SignatureFormElement.signatureFileDirectory}/${fileName}`; + + fs.writeFile(filePath, base64Data, 'base64') + .then(() => { + this.onUpdateObservations(fileName); + }) + .catch((error) => { + AlertMessage(`Error saving signature: ${error.message}`, "error"); + }); + } + + onUpdateObservations(fileName) { + this.dispatchAction(this.props.actionName, { + formElement: this.props.element, + parentFormElement: this.props.parentElement, + questionGroupIndex: this.props.questionGroupIndex, + answerUUID: fileName + }); + } + + clearAnswer() { + this.updateValue(null); + } + + handleSignatureData = (signature) => { + this.updateValue(signature); + }; + + handleEmpty = () => { + this.updateValue(null, ValidationResult.failure(this.props.element.uuid, this.I18n.t("signatureRequired"))); + }; + + handleClear = () => { + this.clearAnswer(); + }; + + handleEnd = () => { + // Don't read signature on end, only when save is clicked + }; + + render() { + return ( + + + {this.signatureFilename ? ( + + + this.clearAnswer()}> + + + + ) : ( + + + + )} + + + + ); + } +} + +const styles = StyleSheet.create({ + icon: { + color: Colors.ActionButtonColor, + opacity: 0.8, + alignSelf: "center", + fontSize: 36, + }, + lineStyle: { + flex: 1, + borderColor: Colors.BlackBackground, + borderBottomWidth: StyleSheet.hairlineWidth, + opacity: 0.1, + }, + signatureContainer: { + flex: 1, + height: 360, + marginTop: 8, + backgroundColor: Colors.InputBackground, + borderWidth: 1, + borderColor: Colors.InputBorderNormal, + borderRadius: 4, + overflow: "hidden", + }, +}); + +export default SignatureFormElement; From c72ff90727082ac324b4ee8306cdcccc96355d4d Mon Sep 17 00:00:00 2001 From: Shivang Mishra Date: Mon, 23 Jun 2025 04:20:41 +0530 Subject: [PATCH 02/12] handling signature data type in FormElementGroup and Observations --- .../openchs-android/src/views/common/Observations.js | 10 +++++++++- .../openchs-android/src/views/form/FormElementGroup.js | 9 +++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/openchs-android/src/views/common/Observations.js b/packages/openchs-android/src/views/common/Observations.js index c0ee7da4b..6ae4f3b71 100644 --- a/packages/openchs-android/src/views/common/Observations.js +++ b/packages/openchs-android/src/views/common/Observations.js @@ -1,4 +1,4 @@ -import {Text, TouchableOpacity, View} from "react-native"; +import {Text, TouchableOpacity, View, Image} from "react-native"; import ListView from "deprecated-react-native-listview"; import PropTypes from 'prop-types'; import React, {Fragment} from "react"; @@ -22,6 +22,7 @@ import EncounterService from "../../service/EncounterService"; import CustomActivityIndicator from "../CustomActivityIndicator"; import PhoneCall from "../../model/PhoneCall"; import {TaskActionNames as Actions} from "../../action/task/TaskActions"; +import SignatureFormElement from "../form/formElement/SignatureFormElement"; class Observations extends AbstractComponent { static propTypes = { @@ -163,6 +164,13 @@ class Observations extends AbstractComponent { ); + } else if (renderType === Concept.dataType.Signature) { + return ( + + + + + ); } else if (Concept.dataType.Media.includes(renderType)) { const allMediaURIs = _.split(displayable.displayValue, ','); return ( diff --git a/packages/openchs-android/src/views/form/FormElementGroup.js b/packages/openchs-android/src/views/form/FormElementGroup.js index 36a97613a..dfc47032d 100644 --- a/packages/openchs-android/src/views/form/FormElementGroup.js +++ b/packages/openchs-android/src/views/form/FormElementGroup.js @@ -43,6 +43,7 @@ import SingleSelectEncounterFormElement from "./formElement/SingleSelectEncounte import MultiSelectEncounterFormElement from "./formElement/MultiSelectEncounterFormElement"; import MediaV2FormElement from "./formElement/MediaV2FormElement"; import Colors from "../primitives/Colors"; +import SignatureFormElement from "./formElement/SignatureFormElement"; class FormElementGroup extends AbstractComponent { static propTypes = { @@ -220,6 +221,14 @@ class FormElementGroup extends AbstractComponent { value={this.getSelectedAnswer(formElement.concept, new MultipleCodedValues())} validationResult={validationResult} />, uniqueKey, formElement.uuid === erroredUUID); + } else if (formElement.concept.datatype === Concept.dataType.Signature) { + return this.wrap(, uniqueKey, formElement.uuid === erroredUUID); } else if ([Concept.dataType.ImageV2].includes(formElement.concept.datatype)) { return this.wrap( Date: Fri, 8 Aug 2025 16:03:53 +0530 Subject: [PATCH 03/12] use primitive value change action for signature form element --- packages/openchs-android/src/views/form/FormElementGroup.js | 2 +- .../src/views/form/formElement/SignatureFormElement.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/openchs-android/src/views/form/FormElementGroup.js b/packages/openchs-android/src/views/form/FormElementGroup.js index dfc47032d..0bf07631b 100644 --- a/packages/openchs-android/src/views/form/FormElementGroup.js +++ b/packages/openchs-android/src/views/form/FormElementGroup.js @@ -225,7 +225,7 @@ class FormElementGroup extends AbstractComponent { return this.wrap(, uniqueKey, formElement.uuid === erroredUUID); diff --git a/packages/openchs-android/src/views/form/formElement/SignatureFormElement.js b/packages/openchs-android/src/views/form/formElement/SignatureFormElement.js index 4495b67a5..3d63d7d24 100644 --- a/packages/openchs-android/src/views/form/formElement/SignatureFormElement.js +++ b/packages/openchs-android/src/views/form/formElement/SignatureFormElement.js @@ -59,7 +59,7 @@ class SignatureFormElement extends AbstractFormElement { formElement: this.props.element, parentFormElement: this.props.parentElement, questionGroupIndex: this.props.questionGroupIndex, - answerUUID: fileName + answer: fileName, }); } From 344fafbb84be474939b2c5ab2aa999534e2564f6 Mon Sep 17 00:00:00 2001 From: Shivang Mishra Date: Mon, 1 Sep 2025 01:57:33 +0530 Subject: [PATCH 04/12] fix incorrect key in action object for signature form element --- .../src/views/form/formElement/SignatureFormElement.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/openchs-android/src/views/form/formElement/SignatureFormElement.js b/packages/openchs-android/src/views/form/formElement/SignatureFormElement.js index 3d63d7d24..7b66c8a2c 100644 --- a/packages/openchs-android/src/views/form/formElement/SignatureFormElement.js +++ b/packages/openchs-android/src/views/form/formElement/SignatureFormElement.js @@ -59,7 +59,7 @@ class SignatureFormElement extends AbstractFormElement { formElement: this.props.element, parentFormElement: this.props.parentElement, questionGroupIndex: this.props.questionGroupIndex, - answer: fileName, + value: fileName, }); } From 77f570721e9f12aa549321adbdc821793015de45 Mon Sep 17 00:00:00 2001 From: Shivang Mishra Date: Wed, 10 Sep 2025 01:04:03 +0530 Subject: [PATCH 05/12] add signature type in media path getters --- .../src/service/MediaQueueService.js | 1 + .../openchs-android/src/service/MediaService.js | 13 +++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/openchs-android/src/service/MediaQueueService.js b/packages/openchs-android/src/service/MediaQueueService.js index 18c2fca94..7e7660ee5 100644 --- a/packages/openchs-android/src/service/MediaQueueService.js +++ b/packages/openchs-android/src/service/MediaQueueService.js @@ -88,6 +88,7 @@ class MediaQueueService extends BaseService { switch (mediaQueueItem.type) { case 'Image': case 'ImageV2': + case 'Signature': return FileSystem.getImagesDir(); case 'Profile-Pics': return FileSystem.getProfilePicsDir(); diff --git a/packages/openchs-android/src/service/MediaService.js b/packages/openchs-android/src/service/MediaService.js index 02af8e153..ce4985fe4 100644 --- a/packages/openchs-android/src/service/MediaService.js +++ b/packages/openchs-android/src/service/MediaService.js @@ -138,9 +138,9 @@ class MediaService extends BaseService { // Check if the file was downloaded successfully const status = res.info().status; const size = res.info().size; - + General.logDebug("MediaService", `Download response: status=${status}, size=${size}, path=${res.path()}`); - + // If status is successful and either size is undefined (can't check) or size is reasonable if (status >= 200 && status < 300 && (size === undefined || size > MIN_FILE_SIZE_IN_BYTES)) { // Verify the file exists and has content @@ -182,7 +182,7 @@ class MediaService extends BaseService { if (error instanceof AvniError) { throw error; // Re-throw existing AvniError } - + // Create a new AvniError with detailed information createNetworkAvniErrorDuringMediaDownload(error, url); }); @@ -194,6 +194,7 @@ class MediaService extends BaseService { ['Video', FileSystem.getVideosDir], ['Image', FileSystem.getImagesDir], ['ImageV2', FileSystem.getImagesDir], + ['Signature', FileSystem.getImagesDir], ['Audio', FileSystem.getAudioDir], ['News', FileSystem.getNewsDir], ['File', FileSystem.getFileDir], @@ -260,7 +261,7 @@ class MediaService extends BaseService { } catch (error) { General.logDebug('MediaService', `Error while downloading image with s3 key ${s3Key}`); General.logDebug('MediaService', error); - + // Make sure we don't leave partial files await cleanUpPartialFiles.call(this, filePathInDevice); @@ -269,13 +270,13 @@ class MediaService extends BaseService { General.logDebug('MediaService', `Ignoring error for ${s3Key} due to ignoreFetchErrors flag`); return null; } - + // Check if this is already an AvniError (from our own code) if (error instanceof AvniError) { error.userMessage = 'unableToFetchImagesError'; throw error; } - + // Otherwise, categorize the error categorizeAndThrowAvniError(error, s3Key, type); } From acc938b931053d9bb409441550b05288299501bc Mon Sep 17 00:00:00 2001 From: Shivang Mishra Date: Wed, 10 Sep 2025 01:05:56 +0530 Subject: [PATCH 06/12] add Signature support for ExpandableMedia --- packages/openchs-android/src/views/common/ExpandableMedia.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/openchs-android/src/views/common/ExpandableMedia.js b/packages/openchs-android/src/views/common/ExpandableMedia.js index 467477373..cd7281f38 100644 --- a/packages/openchs-android/src/views/common/ExpandableMedia.js +++ b/packages/openchs-android/src/views/common/ExpandableMedia.js @@ -119,6 +119,7 @@ export default class ExpandableMedia extends AbstractFormElement { return ExpandableVideo; case 'Image' : case 'ImageV2': + case 'Signature': case 'Profile-Pics' : return ExpandableImage; case 'Audio' : From f04430c47177cd4bb09c36d5bd5d19e507d1be1c Mon Sep 17 00:00:00 2001 From: Shivang Mishra Date: Wed, 10 Sep 2025 01:06:30 +0530 Subject: [PATCH 07/12] fix signature image preview --- packages/openchs-android/src/views/common/Observations.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/openchs-android/src/views/common/Observations.js b/packages/openchs-android/src/views/common/Observations.js index 6ae4f3b71..07e8d00e9 100644 --- a/packages/openchs-android/src/views/common/Observations.js +++ b/packages/openchs-android/src/views/common/Observations.js @@ -167,8 +167,11 @@ class Observations extends AbstractComponent { } else if (renderType === Concept.dataType.Signature) { return ( - - + ); } else if (Concept.dataType.Media.includes(renderType)) { From 9a5ae49e16cf58059f712bbcf5feb00ccd39c3b5 Mon Sep 17 00:00:00 2001 From: Shivang Mishra Date: Wed, 10 Sep 2025 01:07:48 +0530 Subject: [PATCH 08/12] remove webview patch (not needed after library upgrade) --- .../react-native-webview+11.23.0.patch | 23 ------------------- 1 file changed, 23 deletions(-) delete mode 100644 packages/openchs-android/patches/react-native-webview+11.23.0.patch diff --git a/packages/openchs-android/patches/react-native-webview+11.23.0.patch b/packages/openchs-android/patches/react-native-webview+11.23.0.patch deleted file mode 100644 index 605fd7e5b..000000000 --- a/packages/openchs-android/patches/react-native-webview+11.23.0.patch +++ /dev/null @@ -1,23 +0,0 @@ -diff --git a/node_modules/react-native-webview/android/build.gradle b/node_modules/react-native-webview/android/build.gradle -index fbede17..7b48b66 100644 ---- a/node_modules/react-native-webview/android/build.gradle -+++ b/node_modules/react-native-webview/android/build.gradle -@@ -35,6 +35,7 @@ apply plugin: 'com.android.library' - apply plugin: 'kotlin-android' - - android { -+ namespace 'com.reactnativecommunity.webview' - compileSdkVersion getExtOrIntegerDefault('compileSdkVersion') - defaultConfig { - minSdkVersion getExtOrIntegerDefault('minSdkVersion') -diff --git a/node_modules/react-native-webview/android/src/main/AndroidManifest.xml b/node_modules/react-native-webview/android/src/main/AndroidManifest.xml -index b8f945d..b19015d 100644 ---- a/node_modules/react-native-webview/android/src/main/AndroidManifest.xml -+++ b/node_modules/react-native-webview/android/src/main/AndroidManifest.xml -@@ -1,5 +1,5 @@ - -+ > - - - Date: Wed, 10 Sep 2025 02:01:24 +0530 Subject: [PATCH 09/12] delete signature image file on clear value --- .../form/formElement/SignatureFormElement.js | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/openchs-android/src/views/form/formElement/SignatureFormElement.js b/packages/openchs-android/src/views/form/formElement/SignatureFormElement.js index 7b66c8a2c..b3ada697b 100644 --- a/packages/openchs-android/src/views/form/formElement/SignatureFormElement.js +++ b/packages/openchs-android/src/views/form/formElement/SignatureFormElement.js @@ -1,7 +1,7 @@ import PropTypes from "prop-types"; import React from "react"; import AbstractFormElement from "./AbstractFormElement"; -import {StyleSheet, View, Image, TouchableNativeFeedback, Alert} from "react-native"; +import {StyleSheet, View, Image, TouchableNativeFeedback} from "react-native"; import SignatureCanvas from "react-native-signature-canvas"; import ValidationErrorMessage from "../ValidationErrorMessage"; import FormElementLabelWithDocumentation from "../../common/FormElementLabelWithDocumentation"; @@ -9,7 +9,6 @@ import Colors from "../../primitives/Colors"; import General from "../../../utility/General"; import _ from "lodash"; import Icon from "react-native-vector-icons/MaterialCommunityIcons"; -import {ValidationResult} from "openchs-models"; import { AlertMessage } from "../../common/AlertMessage"; import FileSystem from "../../../model/FileSystem"; import fs from 'react-native-fs'; @@ -32,7 +31,7 @@ class SignatureFormElement extends AbstractFormElement { return _.get(this, "props.value.answer"); } - updateValue(signatureValue, validationResult = null) { + updateValue(signatureValue) { if (General.isNilOrEmpty(signatureValue)) { this.onUpdateObservations(null); return; @@ -63,7 +62,13 @@ class SignatureFormElement extends AbstractFormElement { }); } - clearAnswer() { + clearValue() { + const prevFile = this.signatureFilename; + if (prevFile) { + const prevPath = `${SignatureFormElement.signatureFileDirectory}/${prevFile}`; + fs.unlink(prevPath).catch(() => { + }); + } this.updateValue(null); } @@ -72,11 +77,11 @@ class SignatureFormElement extends AbstractFormElement { }; handleEmpty = () => { - this.updateValue(null, ValidationResult.failure(this.props.element.uuid, this.I18n.t("signatureRequired"))); + this.updateValue(null); }; handleClear = () => { - this.clearAnswer(); + this.clearValue(); }; handleEnd = () => { @@ -103,12 +108,12 @@ class SignatureFormElement extends AbstractFormElement { }} source={{uri: `file://${SignatureFormElement.signatureFileDirectory}/${this.signatureFilename}`}} /> - this.clearAnswer()}> + this.clearValue()}> ) : ( - + Date: Wed, 10 Sep 2025 02:36:20 +0530 Subject: [PATCH 10/12] add missing translation keys --- packages/openchs-android/translations/en.json | 2 ++ packages/openchs-android/translations/gu_IN.json | 2 ++ packages/openchs-android/translations/hi_IN.json | 2 ++ packages/openchs-android/translations/ka_IN.json | 2 ++ packages/openchs-android/translations/mr_IN.json | 2 ++ packages/openchs-android/translations/ta_IN.json | 4 +++- 6 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/openchs-android/translations/en.json b/packages/openchs-android/translations/en.json index e527f05fa..1d9b1876f 100644 --- a/packages/openchs-android/translations/en.json +++ b/packages/openchs-android/translations/en.json @@ -6,6 +6,8 @@ "next": "NEXT", "previous": "PREVIOUS", "save": "SAVE", + "clear": "Clear", + "signHere": "Sign here", "deleteConfirmation": "Do you want to delete all saved sessions?", "validationResult": "Validation Error", "numericValueValidation": "Is not a valid number", diff --git a/packages/openchs-android/translations/gu_IN.json b/packages/openchs-android/translations/gu_IN.json index f19c00275..722d85cac 100644 --- a/packages/openchs-android/translations/gu_IN.json +++ b/packages/openchs-android/translations/gu_IN.json @@ -3,6 +3,8 @@ "language": "gu_IN", "translations": { "____COMMENT____": "Gujarati translation for the application labels", + "clear": "સાફ કરો", + "signHere": "અહીં સહી કરો", "name": "નામ", "age": "ઉંમર", "obsKeyword": "અન્ય ડેટા", diff --git a/packages/openchs-android/translations/hi_IN.json b/packages/openchs-android/translations/hi_IN.json index 034363fee..37fc5e908 100644 --- a/packages/openchs-android/translations/hi_IN.json +++ b/packages/openchs-android/translations/hi_IN.json @@ -5,6 +5,8 @@ "____COMMENT____": "Hindi translation for the application labels", "next": "अगला >>", "previous": "<< पिछला", + "clear": "साफ़ करें", + "signHere": "यहाँ साइन करें", "deleteConfirmation": "आपका डाटा डिलीट करना है?", "validationResult": "फॉर्म में गलती है", "numberAboveHiAbsolute": "{{limit}} के बराबर या {{limit}} से कम", diff --git a/packages/openchs-android/translations/ka_IN.json b/packages/openchs-android/translations/ka_IN.json index f56149610..42dafd1b0 100644 --- a/packages/openchs-android/translations/ka_IN.json +++ b/packages/openchs-android/translations/ka_IN.json @@ -6,6 +6,8 @@ "next": "ಮುಂದೆ", "previous": "ಹಿಂದೆ", "save": "ಉಳಿಸಿ", + "clear": "ತೆರವುಗೊಳಿಸಿ", + "signHere": "ಇಲ್ಲಿ ಸಹಿ ಮಾಡಿ", "deleteConfirmation": "ಉಳಿಸಿದ ಎಲ್ಲಾ ಸೆಷನ್‌ಗಳನ್ನು ಅಳಿಸಲು ನೀವು ಬಯಸುವಿರಾ?", "validationResult": "ಕ್ರಮಬದ್ಧಗೊಳಿಸುವಿಕೆ ದೋಷ", "numericValueValidation": "ಮಾನ್ಯವಾದ ಸಂಖ್ಯೆಯಲ್ಲ", diff --git a/packages/openchs-android/translations/mr_IN.json b/packages/openchs-android/translations/mr_IN.json index 8aa8c53b3..ca57dcf91 100644 --- a/packages/openchs-android/translations/mr_IN.json +++ b/packages/openchs-android/translations/mr_IN.json @@ -3,6 +3,8 @@ "language": "mr_IN", "translations": { "____COMMENT____": "Marathi translation for the application labels", + "clear": "साफ करा", + "signHere": "येथे सही करा", "next": "पुढे >>", "previous": "<< मागे", "deleteConfirmation": "तुम्हाला सर्व डाटा डीलीट करावयाचा आहे का?", diff --git a/packages/openchs-android/translations/ta_IN.json b/packages/openchs-android/translations/ta_IN.json index 85ee530cc..9722cd98c 100755 --- a/packages/openchs-android/translations/ta_IN.json +++ b/packages/openchs-android/translations/ta_IN.json @@ -6,6 +6,8 @@ "next": "அடுத்து", "previous": "முந்தைய", "save": "சேமிக்க", + "clear": "அழிக்க", + "signHere": "இங்கே கையெழுத்திடுங்கள்", "deleteConfirmation": "சேமித்த அனைத்து அமர்வுகளையும் நீக்க விரும்புகிறீர்களா?", "validationResult": "சரிபார்த்தல் பிழை", "numericValueValidation": "சரியான எண் அல்ல", @@ -249,7 +251,7 @@ "RecentVisits": "சந்திப்புகள் ", "cannotChangeUserTitle": "பல பயனர்கள் உள்நுழைய முடியாது", "cannotChangeUserDesc": "' {{oldUser}} 'பயனர் முன்னதாகவே உள்நுழைந்துள்ளார்.' நீங்கள் {{newUser}} தொடரவேண்டுமானால் {{oldUser}} ஒத்திசைக்கப்படாத தகவல்கள் அழிந்துவிடும். இந்தத் தகவல்களை நீக்கி உள்நுழைய வேண்டுமா?", - "clearDataAndLogin": "தரவு நீக்கு மற்றும் உள்நுழைவு", + "clearDataAndLogin": "தரவை நீக்கு மற்றும் உள்நுழைவு", "Dashboard": "முகப்புத்திரை ", "Sync": "ஒத்திசைவு", "enrolmentDetails": "சேர்க்கை விவரங்கள்", From 63311c0a47e6a1b723158e4c3e60dec4736bf2c4 Mon Sep 17 00:00:00 2001 From: Shivang Mishra Date: Wed, 10 Sep 2025 03:57:41 +0530 Subject: [PATCH 11/12] fix scroll view interference --- .../src/views/BeneficiaryIdentificationPage.js | 1 + .../views/familyfolder/FamilyRegisterFormView.js | 1 + .../src/views/form/FormElementGroup.js | 2 ++ .../views/form/formElement/SignatureFormElement.js | 13 +++++++++++-- .../src/views/individual/IndividualEncounterView.js | 1 + .../src/views/individual/PersonRegisterFormView.js | 1 + .../src/views/program/ChecklistItemView.js | 1 + .../views/program/ManualProgramEligibilityView.js | 1 + .../src/views/program/ProgramEncounterCancelView.js | 5 +++-- .../src/views/program/ProgramEncounterView.js | 1 + .../src/views/program/ProgramFormComponent.js | 1 + .../src/views/subject/SubjectRegisterFormView.js | 1 + .../openchs-android/src/views/task/TaskFormView.js | 1 + 13 files changed, 26 insertions(+), 4 deletions(-) diff --git a/packages/openchs-android/src/views/BeneficiaryIdentificationPage.js b/packages/openchs-android/src/views/BeneficiaryIdentificationPage.js index 492a73997..a15669aab 100644 --- a/packages/openchs-android/src/views/BeneficiaryIdentificationPage.js +++ b/packages/openchs-android/src/views/BeneficiaryIdentificationPage.js @@ -64,6 +64,7 @@ class BeneficiaryIdentificationPage extends AbstractComponent { this.previous()}/> , uniqueKey, formElement.uuid === erroredUUID); } else if ([Concept.dataType.ImageV2].includes(formElement.concept.datatype)) { return this.wrap( { + this.props.scrollRef?.current?.setNativeProps( + {scrollEnabled: false} + ); + }; handleEnd = () => { - // Don't read signature on end, only when save is clicked + this.props.scrollRef?.current?.setNativeProps( + {scrollEnabled: true} + ); }; render() { @@ -113,9 +121,10 @@ class SignatureFormElement extends AbstractFormElement { ) : ( - + {_.get(this.state, 'timerState.displayQuestions', true) && {_.get(this.state, 'timerState.displayQuestions', true) && this.onAppHeaderBack()} displayHomePressWarning={true}/> obs && obs.isPhoneNumberVerificationRequired && obs.isPhoneNumberVerificationRequired(this.state.filteredFormElements)); - + return { completed: (state, decisions, ruleValidationErrors, checklists, nextScheduledVisits) => { // Basic null check for state @@ -102,7 +102,7 @@ class ProgramEncounterCancelView extends AbstractComponent { console.error('ProgramEncounterCancelView.getNextParams.completed: state or state.programEncounter is undefined'); return; } - + const onSaveCallback = (source) => this.onSaveCallback(source, state.programEncounter); const headerMessage = this._header(state.programEncounter); const form = this.getCancelEncounterForm(); @@ -167,6 +167,7 @@ class ProgramEncounterCancelView extends AbstractComponent { {_.get(this.state, 'timerState.displayQuestions', true) && {_.get(this.props.state, 'timerState.displayQuestions', true) && {_.get(this.state, 'timerState.displayQuestions', true) && this.onAppHeaderBack()} displayHomePressWarning={true}/> Date: Wed, 10 Sep 2025 04:21:18 +0530 Subject: [PATCH 12/12] address coderabbit nitpicks --- .../src/views/form/formElement/SignatureFormElement.js | 3 +-- .../src/views/program/ManualProgramEligibilityView.js | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/openchs-android/src/views/form/formElement/SignatureFormElement.js b/packages/openchs-android/src/views/form/formElement/SignatureFormElement.js index fd0b10465..b058fe58e 100644 --- a/packages/openchs-android/src/views/form/formElement/SignatureFormElement.js +++ b/packages/openchs-android/src/views/form/formElement/SignatureFormElement.js @@ -25,11 +25,10 @@ class SignatureFormElement extends AbstractFormElement { constructor(props, context) { super(props, context); - this.signatureRef = React.createRef(); } get signatureFilename() { - return _.get(this, "props.value.answer"); + return this.props.value?.getValue?.() ?? this.props.value?.answer; } updateValue(signatureValue) { diff --git a/packages/openchs-android/src/views/program/ManualProgramEligibilityView.js b/packages/openchs-android/src/views/program/ManualProgramEligibilityView.js index 7cc3b44c7..404f1bb88 100644 --- a/packages/openchs-android/src/views/program/ManualProgramEligibilityView.js +++ b/packages/openchs-android/src/views/program/ManualProgramEligibilityView.js @@ -67,7 +67,6 @@ class ManualProgramEligibilityView extends AbstractComponent { this.onAppHeaderBack()} displayHomePressWarning={true}/>