Skip to content

Conversation

@ShivangMishra
Copy link

@ShivangMishra ShivangMishra commented Jun 22, 2025

Demo Video

https://drive.google.com/file/d/1LW_uWRlkHzQskqolLWLapGCGrwldi6Xt/view?usp=sharing

Note

  • I have not tested sync functionality because it depends on AWS and I didn't have access to it in dev env, but it should work fine because this component is compatible with MediaQueueService.

Depends on

Summary by CodeRabbit

  • New Features

    • Capture and save user signatures in forms with an inline signature pad; preview, clear, and show thumbnails in observation and media views.
  • Chores

    • Added signature support dependency and updated webview for compatibility.
    • Signatures are stored and handled alongside app images and supported by existing media flows.
  • Localization

    • Added "Clear" and "Sign here" translations for multiple languages; minor Tamil text tweak.

@ShivangMishra ShivangMishra force-pushed the dmp-2025-signature-capture branch from d03763b to c72ff90 Compare July 15, 2025 20:27
@coderabbitai
Copy link

coderabbitai bot commented Aug 31, 2025

Walkthrough

Introduces a Signature data type across the Android app: adds a Signature form element using react-native-signature-canvas, saves signatures as image files, renders previews in observations/media views, maps Signature to Images directory in services, passes scroll refs for input handling, updates translations, and adjusts webview dependency/patch.

Changes

Cohort / File(s) Summary
Dependencies & WebView patch
packages/openchs-android/package.json, packages/openchs-android/patches/react-native-webview+11.23.0.patch
Add react-native-signature-canvas@^4.7.4. Bump react-native-webview to ^13.15.0. Patch Android namespace/manifest for webview.
Signature form element (new)
packages/openchs-android/src/views/form/formElement/SignatureFormElement.js
New component to capture signature via canvas, store as file (RNFS), preview, clear, and validate. Exposes static directory and handlers; integrates with form updates; toggles scroll via scrollRef.
Form integration & scroll ref propagation
packages/openchs-android/src/views/form/FormElementGroup.js, .../src/views/*/*View.js (BeneficiaryIdentificationPage.js, FamilyRegisterFormView.js, IndividualEncounterView.js, PersonRegisterFormView.js, ProgramEncounterCancelView.js, ProgramEncounterView.js, ProgramFormComponent.js, SubjectRegisterFormView.js, TaskFormView.js)
Add Signature render branch in FormElementGroup. Add optional scrollRef prop and pass it down. Pass scrollRef from multiple views’ ScrollViews into FormElementGroup.
Observation/media rendering
packages/openchs-android/src/views/common/Observations.js, packages/openchs-android/src/views/common/ExpandableMedia.js
Observations: render Signature as 100x100 Image from local file URI. ExpandableMedia: route Signature to ExpandableImage.
Media services
packages/openchs-android/src/service/MediaService.js, packages/openchs-android/src/service/MediaQueueService.js
Map Signature to Images directory (path resolution and queue). Add userMessage when rethrowing existing AvniError in image download.
Translations
packages/openchs-android/translations/*.json (en.json, gu_IN.json, hi_IN.json, ka_IN.json, mr_IN.json, ta_IN.json)
Add keys: clear, signHere (localized). Update ta_IN clearDataAndLogin text.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant View as Parent View (ScrollView)
  participant FEG as FormElementGroup
  participant SFE as SignatureFormElement
  participant FS as FileSystem (RNFS)
  participant Store as Form State/Dispatcher
  participant Obs as Observations View

  Note over View,FEG: Scroll ref provided to manage canvas gestures
  User->>SFE: Draws signature
  SFE->>SFE: handleBegin() → disable parent scroll
  SFE->>SFE: handleSignatureData(dataURL)
  SFE->>FS: Write base64 image (UUID.ext) to disk
  FS-->>SFE: File path/ok
  SFE->>Store: Dispatch update with filename
  SFE->>SFE: handleEnd() → enable parent scroll

  Note over Store,Obs: Later, rendering existing observation
  Obs->>FS: Build file:// URI from images dir + filename
  FS-->>Obs: Local URI
  Obs-->>User: Show signature preview image
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Pre-merge checks (3 passed)

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title “#1712
Description Check ✅ Passed The current description succinctly lists the dependent server, models, and webapp pull requests, which are directly relevant to the successful integration of the signature capture feature and therefore are related to the changeset. Although it lacks a summary of the specific client-side changes, it correctly informs reviewers of the necessary external dependencies. Because it is not off-topic and does relate to the pull request’s integration requirements, it satisfies the description check.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

Poem

I dipped my paw in inky streams,
Signed my name between your themes—
A canvas squeaks, a file appears,
Stored with care among our peers.
Scrolls hush quiet while I write,
“Sign here,” it says—what sheer delight!
Hop, save, preview—looks just right. 🐇🖊️

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ShivangMishra ShivangMishra marked this pull request as ready for review September 1, 2025 12:24
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
packages/openchs-android/src/views/common/Observations.js (1)

25-25: Avoid coupling to a UI component for a filesystem constant

Import FileSystem directly instead of pulling SignatureFormElement just to access its static directory.

- import SignatureFormElement from "../form/formElement/SignatureFormElement";
+ import FileSystem from "../../model/FileSystem";
packages/openchs-android/src/views/form/formElement/SignatureFormElement.js (1)

31-33: Use the wrapper API to read the value

Prefer the wrapper’s getValue() to stay consistent with the rest of the codebase; fall back to answer if needed.

-    get signatureFilename() {
-        return _.get(this, "props.value.answer");
-    }
+    get signatureFilename() {
+        return (this.props.value && typeof this.props.value.getValue === 'function')
+            ? this.props.value.getValue()
+            : _.get(this, "props.value.answer");
+    }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c731b67 and 344fafb.

⛔ Files ignored due to path filters (1)
  • packages/openchs-android/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (4)
  • packages/openchs-android/package.json (1 hunks)
  • packages/openchs-android/src/views/common/Observations.js (3 hunks)
  • packages/openchs-android/src/views/form/FormElementGroup.js (2 hunks)
  • packages/openchs-android/src/views/form/formElement/SignatureFormElement.js (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/openchs-android/src/views/form/formElement/SignatureFormElement.js (1)
packages/openchs-android/src/model/FileSystem.js (1)
  • FileSystem (6-143)
🔇 Additional comments (3)
packages/openchs-android/package.json (1)

94-101: Peer dependencies are compatible – RN 0.72.8 satisfies both libraries’ requirements (signature-canvas needs webview ≥13 and webview 13.15.0 supports any React Native version), no further changes needed.

packages/openchs-android/src/views/form/FormElementGroup.js (2)

46-46: Import of SignatureFormElement — LGTM

Import path and placement are consistent with existing elements.


224-231: Signature element wiring — LGTM, confirm model support at runtime

Rendering branch correctly mirrors other primitive elements. Please ensure the app ships with an openchs-models version that defines Concept.dataType.Signature to avoid this branch being unreachable.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/openchs-android/src/service/MediaService.js (1)

145-171: Do not reject small-but-valid images (signatures can be <1KB)

The size threshold (MIN_FILE_SIZE_IN_BYTES=1024) can incorrectly treat valid, tiny PNGs as corrupt, breaking downloads and re-download logic.

Use file existence and stat > 0 instead of Content-Length thresholds:

-                // 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)) {
+                // If status is successful, verify on-disk size > 0
+                if (status >= 200 && status < 300) {
                     // Verify the file exists and has content
                     return fs.stat(targetFilePath)
                         .then(stats => {
-                            if (stats.size > 0) {
+                            if (stats.size > 0) {
                                 General.logDebug("MediaService", `The file saved to: ${res.path()} with size ${stats.size}`);
                                 return targetFilePath;
                             } else {
                                 General.logDebug("MediaService", `File exists but has zero size: ${targetFilePath}`);
                                 return fs.unlink(targetFilePath).then(() => {
                                     throw new Error('Downloaded file has zero size');
                                 });
                             }
                         })
                         .catch(statError => {
                             General.logDebug("MediaService", `Error checking file stats: ${statError.message}`);
                             return fs.unlink(targetFilePath).then(() => {
                                 throw new Error(`Error verifying downloaded file: ${statError.message}`);
                             });
                         });
                 } else {
                     // If the file is empty or too small, delete it and throw an error
                     return fs.unlink(targetFilePath).then(() => {
                         createMediaDownloadAvniError(res, url);
                     });
                 }
-                if (fileStats.size <= MIN_FILE_SIZE_IN_BYTES) {
+                if (fileStats.size <= 0) {
                     // File exists but is likely an empty placeholder, delete and re-download
                     await fs.unlink(filePathInDevice);
                     return await this.downloadMediaFromS3ToPath(s3Key, filePathInDevice, ignoreFetchErrors);
                 }

Also applies to: 250-255

🧹 Nitpick comments (1)
packages/openchs-android/src/service/MediaQueueService.js (1)

87-102: Add a default case to guard against unknown media types

Without a default, getDirByType can return undefined, producing paths like "undefined/filename" downstream.

Apply:

     getDirByType(mediaQueueItem) {
         switch (mediaQueueItem.type) {
             case 'Image':
             case 'ImageV2':
+            case 'Signature':
-            case 'Signature':
                 return FileSystem.getImagesDir();
             case 'Profile-Pics':
                 return FileSystem.getProfilePicsDir();
             case 'Video':
                 return FileSystem.getVideosDir();
             case 'Audio':
                 return FileSystem.getAudioDir();
             case 'File':
                 return FileSystem.getFileDir();
+            default:
+                throw new Error(`Unhandled media type: ${mediaQueueItem.type}`);
         }
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 344fafb and 9a5ae49.

📒 Files selected for processing (5)
  • packages/openchs-android/patches/react-native-webview+11.23.0.patch (0 hunks)
  • packages/openchs-android/src/service/MediaQueueService.js (1 hunks)
  • packages/openchs-android/src/service/MediaService.js (5 hunks)
  • packages/openchs-android/src/views/common/ExpandableMedia.js (1 hunks)
  • packages/openchs-android/src/views/common/Observations.js (3 hunks)
💤 Files with no reviewable changes (1)
  • packages/openchs-android/patches/react-native-webview+11.23.0.patch
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/openchs-android/src/views/common/Observations.js
🧰 Additional context used
🧬 Code graph analysis (1)
packages/openchs-android/src/service/MediaService.js (1)
packages/openchs-android/src/model/FileSystem.js (1)
  • FileSystem (6-143)
🔇 Additional comments (3)
packages/openchs-android/src/views/common/ExpandableMedia.js (1)

116-125: Signature routed to ExpandableImage — looks good

Consistent with the rest of the stack; no issues spotted.

packages/openchs-android/src/service/MediaService.js (2)

192-208: Map Signature to Images directory — aligned with queue/service usage

Matches MediaQueueService and UI routing. Good.


274-282: Error message key still image-specific

This branch sets error.userMessage = 'unableToFetchImagesError' even for non-image types (e.g., Audio, File). Consider a media-agnostic key or mapping by type.

Would you like me to scan i18n keys and propose a safe mapping per media type?

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
packages/openchs-android/src/views/form/formElement/SignatureFormElement.js (1)

34-54: Harden data-URL parsing and delete previous file on save (prevents crashes and storage leaks).

Header.match can be null and will crash; also saving a new signature should clean up the previous file.

-    updateValue(signatureValue) {
-        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");
-            });
-    }
+    updateValue(signatureValue) {
+        if (General.isNilOrEmpty(signatureValue)) {
+            this.onUpdateObservations(null);
+            return;
+        }
+        const parts = String(signatureValue).split(',');
+        const header = parts[0];
+        const base64Data = parts[1];
+        const match = header && header.match(/data:(.*?);/);
+        if (!match || !base64Data) {
+            AlertMessage(this.I18n.t("unexpectedSignatureFormat") || "Unexpected signature data format", "error");
+            return;
+        }
+        const mimeType = match[1];
+        const ext = (mimeType && mimeType.split('/')[1]) || 'png';
+
+        const fileName = `${General.randomUUID()}.${ext}`;
+        const filePath = `${SignatureFormElement.signatureFileDirectory}/${fileName}`;
+        const prevFile = this.signatureFilename;
+
+        fs.writeFile(filePath, base64Data, 'base64')
+            .then(async () => {
+                if (prevFile && prevFile !== fileName) {
+                    const prevPath = `${SignatureFormElement.signatureFileDirectory}/${prevFile}`;
+                    await fs.unlink(prevPath).catch(() => {});
+                }
+                this.onUpdateObservations(fileName);
+            })
+            .catch((error) => {
+                AlertMessage(`Error saving signature: ${error.message}`, "error");
+            });
+    }
🧹 Nitpick comments (4)
packages/openchs-android/src/views/form/formElement/SignatureFormElement.js (4)

65-73: Deleting on clear — good; optionally log unlink errors.

Helps diagnose file-system issues if cleanup fails.

-            fs.unlink(prevPath).catch(() => {
-            });
+            fs.unlink(prevPath).catch((e) => {
+                General.logError("SignatureFormElement", `Failed to delete previous signature: ${e.message}`);
+            });

111-113: Add accessibility props to the clear button.

Improves screen-reader usability.

-                        <TouchableNativeFeedback onPress={() => this.clearValue()}>
+                        <TouchableNativeFeedback
+                            accessibilityRole="button"
+                            accessibilityLabel={this.I18n.t("clear") || "Clear"}
+                            onPress={() => this.clearValue()}>

105-110: Use “contain” for more predictable image preview scaling.

Prevents potential clipping with “center”.

-                        <Image
-                            resizeMode="center"
+                        <Image
+                            resizeMode="contain"

17-17: Consider storage hardening for PII.

Signatures are sensitive; consider encrypting at rest or using app-internal storage if feasible, and ensure backups/sync respect privacy policies.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9a5ae49 and 66d28e8.

📒 Files selected for processing (1)
  • packages/openchs-android/src/views/form/formElement/SignatureFormElement.js (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/openchs-android/src/views/form/formElement/SignatureFormElement.js (1)
packages/openchs-android/src/model/FileSystem.js (1)
  • FileSystem (6-143)
🔇 Additional comments (1)
packages/openchs-android/src/views/form/formElement/SignatureFormElement.js (1)

17-18: Defer directory path resolution
Replace the static field

static signatureFileDirectory = FileSystem.getImagesDir();

with a runtime getter, e.g.:

static get signatureFileDirectory() {
  return FileSystem.getImagesDir();
}

so that getImagesDir() is invoked only after FileSystem.init() has run.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
packages/openchs-android/translations/en.json (1)

9-10: Casing consistency for buttons

"save" is "SAVE" but "clear" is "Clear". Align casing per UI convention.

If you want all primary actions uppercase:

-    "clear": "Clear",
+    "clear": "CLEAR",
packages/openchs-android/translations/ta_IN.json (2)

9-10: Tamil: minor wording option

"இங்கே கையெழுத்திடுங்கள்" is fine. Optional: prefer more native "இங்கே கையொப்பமிடுங்கள்".


254-254: Improve imperative phrasing

Use a verb form: "Delete data and login" → "தரவை நீக்கி உள்நுழையவும்".

-    "clearDataAndLogin": "தரவை நீக்கு மற்றும் உள்நுழைவு",
+    "clearDataAndLogin": "தரவை நீக்கி உள்நுழையவும்",
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 66d28e8 and be4fa7c.

📒 Files selected for processing (6)
  • packages/openchs-android/translations/en.json (1 hunks)
  • packages/openchs-android/translations/gu_IN.json (1 hunks)
  • packages/openchs-android/translations/hi_IN.json (1 hunks)
  • packages/openchs-android/translations/ka_IN.json (1 hunks)
  • packages/openchs-android/translations/mr_IN.json (1 hunks)
  • packages/openchs-android/translations/ta_IN.json (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/openchs-android/translations/hi_IN.json
🔇 Additional comments (3)
packages/openchs-android/translations/ka_IN.json (1)

9-10: Kannada additions look correct

"clear" and "signHere" read well and match tone.

packages/openchs-android/translations/mr_IN.json (1)

6-7: Marathi additions LGTM

Accurate and idiomatic.

packages/openchs-android/translations/gu_IN.json (1)

6-7: Gujarati additions LGTM

Translations are correct and consistent.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
packages/openchs-android/src/views/form/formElement/SignatureFormElement.js (2)

45-52: Optional: delete previous file after successful save to prevent orphans

You currently delete on “clear” (good), but if the element ever overwrites without clearing, the old file remains. Clean up the previous file post-write.

         const fileName = `${General.randomUUID()}.${ext}`;
         const filePath = `${SignatureFormElement.signatureFileDirectory}/${fileName}`;
+        const prevFile = this.signatureFilename;
 
         fs.writeFile(filePath, base64Data, 'base64')
-            .then(() => {
-                this.onUpdateObservations(fileName);
-            })
+            .then(async () => {
+                if (prevFile && prevFile !== fileName) {
+                    const prevPath = `${SignatureFormElement.signatureFileDirectory}/${prevFile}`;
+                    await fs.unlink(prevPath).catch(() => {});
+                }
+                this.onUpdateObservations(fileName);
+            })
             .catch((error) => {
                 AlertMessage(`Error saving signature: ${error.message}`, "error");
             });

41-44: Guard against malformed signature data to avoid crashes

header.match(...)[1] will throw if signatureValue isn’t a well-formed data URL (null match or missing comma). Add defensive parsing and a safe fallback ext.

-        const [header, base64Data] = signatureValue.split(',');
-        const mimeType = header.match(/data:(.*?);/)[1];
-        const ext = mimeType.split('/')[1];
+        const [header, base64Data] = (signatureValue || '').split(',');
+        const match = header && header.match(/data:(.*?);/);
+        if (!match || !base64Data) {
+            AlertMessage(this.I18n.t("unexpectedSignatureFormat") || "Unexpected signature data format", "error");
+            return;
+        }
+        const mimeType = match[1];
+        const ext = (mimeType.split('/')[1] || 'png').toLowerCase();
🧹 Nitpick comments (3)
packages/openchs-android/src/views/form/formElement/SignatureFormElement.js (1)

26-29: Unused ref: remove or wire up

this.signatureRef isn’t used. Drop it or use it (e.g., to trigger canvas clear). Keeping dead refs adds noise.

     constructor(props, context) {
         super(props, context);
-        this.signatureRef = React.createRef();
     }
packages/openchs-android/src/views/program/ManualProgramEligibilityView.js (1)

9-11: Wire a real ScrollView ref; current scrollRef prop is undefined

You pass scrollRef={this.scrollRef} but never define it nor attach it to a ScrollView. Signature scroll lock won’t work here.

-import {View} from "react-native";
+import {View, ScrollView} from "react-native";
     constructor(props, context) {
         super(props, context, Reducers.reducerKeys.manualProgramEligibility);
+        this.scrollRef = React.createRef();
     }
                 <CHSContent ref="scroll">
-                    <AppHeader title={this.I18n.t('manualProgramEligibility')} func={() => this.onAppHeaderBack()} displayHomePressWarning={true}/>
-                    <View style={{backgroundColor: '#ffffff', flexDirection: 'column'}}>
+                    <ScrollView ref={this.scrollRef} keyboardShouldPersistTaps="handled">
+                    <AppHeader title={this.I18n.t('manualProgramEligibility')} func={() => this.onAppHeaderBack()} displayHomePressWarning={true}/>
+                    <View style={{backgroundColor: '#ffffff', flexDirection: 'column'}}>
                         <FormElementGroup group={this.state.formElementGroup}
                                           scrollRef={this.scrollRef}
                                           observationHolder={new ObservationsHolder(this.state.subjectProgramEligibility.observations)}
                                           actions={Actions}
                                           validationResults={this.state.validationResults}
                                           filteredFormElements={this.state.filteredFormElements}
                                           formElementsUserState={this.state.formElementsUserState}
                                           dataEntryDate={this.state.subjectProgramEligibility.checkDate}
                                           onValidationError={(x, y) => this.scrollToPosition(x, y)}
                         />
                         <WizardButtons
                             previous={{
                                 visible: !this.state.wizard.isFirstPage(),
                                 func: () => this.previous(),
                                 label: this.I18n.t('previous')
                             }}
                             next={{
                                 func: () => this.next(),
                                 label: this.I18n.t('next')
                             }}
                         />
                     </View>
+                    </ScrollView>

To verify parity with other views, check that this mirrors the pattern used in SubjectRegisterFormView.

packages/openchs-android/src/views/program/ChecklistItemView.js (1)

96-96: scrollRef passthrough — LGTM; consider keyboard tap handling

Looks good. Optional: align with other screens by adding keyboardShouldPersistTaps="handled" on the ScrollView to avoid focus-loss glitches during signature entry.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between be4fa7c and 63311c0.

📒 Files selected for processing (13)
  • packages/openchs-android/src/views/BeneficiaryIdentificationPage.js (1 hunks)
  • packages/openchs-android/src/views/familyfolder/FamilyRegisterFormView.js (1 hunks)
  • packages/openchs-android/src/views/form/FormElementGroup.js (3 hunks)
  • packages/openchs-android/src/views/form/formElement/SignatureFormElement.js (1 hunks)
  • packages/openchs-android/src/views/individual/IndividualEncounterView.js (1 hunks)
  • packages/openchs-android/src/views/individual/PersonRegisterFormView.js (1 hunks)
  • packages/openchs-android/src/views/program/ChecklistItemView.js (1 hunks)
  • packages/openchs-android/src/views/program/ManualProgramEligibilityView.js (1 hunks)
  • packages/openchs-android/src/views/program/ProgramEncounterCancelView.js (2 hunks)
  • packages/openchs-android/src/views/program/ProgramEncounterView.js (1 hunks)
  • packages/openchs-android/src/views/program/ProgramFormComponent.js (1 hunks)
  • packages/openchs-android/src/views/subject/SubjectRegisterFormView.js (1 hunks)
  • packages/openchs-android/src/views/task/TaskFormView.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/openchs-android/src/views/form/FormElementGroup.js
🧰 Additional context used
🧬 Code graph analysis (1)
packages/openchs-android/src/views/form/formElement/SignatureFormElement.js (1)
packages/openchs-android/src/model/FileSystem.js (1)
  • FileSystem (6-143)
🔇 Additional comments (8)
packages/openchs-android/src/views/subject/SubjectRegisterFormView.js (1)

141-146: LGTM: scrollRef correctly passed through

Good pattern: you create this.scrollRef, attach it to ScrollView, and forward it to FormElementGroup for signature scroll control.

packages/openchs-android/src/views/individual/PersonRegisterFormView.js (1)

143-143: scrollRef passthrough — LGTM

Matches the established pattern; ScrollView already uses keyboardShouldPersistTaps="handled".

packages/openchs-android/src/views/BeneficiaryIdentificationPage.js (1)

67-67: scrollRef passthrough — LGTM

Correctly forwards the container ref to the form group.

packages/openchs-android/src/views/individual/IndividualEncounterView.js (1)

198-198: scrollRef passthrough — LGTM

Consistent with other views; enables SignatureFormElement to manage scroll state.

packages/openchs-android/src/views/program/ProgramFormComponent.js (1)

135-135: scrollRef passthrough — LGTM

Good addition; no issues spotted.

packages/openchs-android/src/views/task/TaskFormView.js (1)

76-76: scrollRef passthrough — LGTM

Matches the project-wide integration for signature capture.

packages/openchs-android/src/views/familyfolder/FamilyRegisterFormView.js (1)

55-55: scrollRef passthrough — LGTM

Wiring looks correct and consistent.

packages/openchs-android/src/views/program/ProgramEncounterView.js (1)

217-227: Approve code changes

FormElementGroup declares scrollRef in its propTypes and forwards it to SignatureFormElement, which safely calls current.setNativeProps({scrollEnabled: false/true}) on gesture begin/end. No further changes needed.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
packages/openchs-android/src/views/form/formElement/SignatureFormElement.js (1)

40-43: Guard data-URL parsing to avoid crashes on unexpected input

A malformed signatureValue will throw if match returns null. Add minimal checks.

-        const [header, base64Data] = signatureValue.split(',');
-        const mimeType = header.match(/data:(.*?);/)[1];
-        const ext = mimeType.split('/')[1];
+        const parts = typeof signatureValue === "string" ? signatureValue.split(",") : [];
+        const header = parts[0];
+        const base64Data = parts[1];
+        const match = header && header.match(/data:(.*?);/);
+        if (!match || !base64Data) {
+            AlertMessage(this.I18n?.t?.("unexpectedSignatureFormat") || "Unexpected signature data format", "error");
+            return;
+        }
+        const mimeType = match[1];
+        const ext = (mimeType.split("/")[1]) || "png";
🧹 Nitpick comments (7)
packages/openchs-android/src/views/form/formElement/SignatureFormElement.js (7)

18-24: PropTypes missing for used props

parentElement and questionGroupIndex are used in onUpdateObservations but not declared in propTypes.

     static propTypes = {
         element: PropTypes.object.isRequired,
         actionName: PropTypes.string.isRequired,
         value: PropTypes.object,
         validationResult: PropTypes.object,
         scrollRef: PropTypes.object,
+        parentElement: PropTypes.object,
+        questionGroupIndex: PropTypes.number,
     };

Also applies to: 58-63


10-10: Remove unused import

lodash is imported but not used.

-import _ from "lodash";

51-53: Localize error message

Use I18n so the error is translated.

-                AlertMessage(`Error saving signature: ${error.message}`, "error");
+                AlertMessage(this.I18n.t("errorSavingSignature", { message: error.message }) || `Error saving signature: ${error.message}`, "error");

26-28: Define ref or drop it

ref={this.signatureRef} assumes signatureRef exists. If AbstractFormElement doesn’t set it, define it here.

     constructor(props, context) {
         super(props, context);
+        this.signatureRef = React.createRef();
     }

Also applies to: 124-131


87-96: Ensure scroll is re-enabled even on unmount

If the component unmounts while drawing, scroll may remain disabled. Add optional chaining and re-enable on unmount.

-        this.props.scrollRef?.current?.setNativeProps(
+        this.props.scrollRef?.current?.setNativeProps?.(
             {scrollEnabled: false}
         );
...
-        this.props.scrollRef?.current?.setNativeProps(
+        this.props.scrollRef?.current?.setNativeProps?.(
             {scrollEnabled: true}
         );

Add this method:

componentWillUnmount() {
    this.props.scrollRef?.current?.setNativeProps?.({ scrollEnabled: true });
}

118-121: Improve accessibility for the clear action

Provide an accessible label and role for the clear button.

-                        <TouchableNativeFeedback onPress={() => this.clearValue()}>
+                        <TouchableNativeFeedback
+                            onPress={() => this.clearValue()}
+                            accessibilityRole="button"
+                            accessibilityLabel={this.I18n.t("clear")}
+                        >
                             <Icon name={"backspace"} style={[styles.icon]} />
                         </TouchableNativeFeedback>

17-17: Consider a dedicated, private directory for signatures

Signatures can be sensitive. Storing under the general Images directory may complicate retention. Consider a dedicated subdir (e.g., FileSystem.getSignaturesDir()) and/or internal app storage; apply retention/deletion policy aligned with org requirements.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 63311c0 and 5406368.

📒 Files selected for processing (1)
  • packages/openchs-android/src/views/form/formElement/SignatureFormElement.js (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/openchs-android/src/views/form/formElement/SignatureFormElement.js (1)
packages/openchs-android/src/model/FileSystem.js (1)
  • FileSystem (6-143)

@ombhardwajj
Copy link
Member

This closes avniproject/avni-product#1712

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants