diff --git a/.github/workflows/build-distribute.yml b/.github/workflows/build-distribute.yml new file mode 100644 index 000000000..bf53cde10 --- /dev/null +++ b/.github/workflows/build-distribute.yml @@ -0,0 +1,136 @@ +name: Build and Distribute + +on: + workflow_call: + inputs: + branch: + required: true + type: string + environment: + required: true + type: string + flavor: + required: true + type: string + build_type: + required: true + type: string + version: + required: true + type: string + version_code: + required: true + type: string + +jobs: + build: + runs-on: ubuntu-latest + environment: ${{ inputs.environment }} + + permissions: + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ inputs.branch }} + + - name: Cache NDK + uses: actions/cache@v4 + with: + path: /usr/local/lib/android/sdk/ndk + key: ubuntu-latest-ndk-r29 + + - name: Cache CMake + uses: actions/cache@v4 + with: + path: ~/.cmake + key: ubuntu-latest-cmake-3.31.1 + + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: zulu + java-version: '17' + + - name: Set up Android SDK + uses: android-actions/setup-android@v2 + + - name: Set up Android NDK + uses: nttld/setup-ndk@v1.5.0 + with: + ndk-version: r29 + link-to-sdk: true + + - name: Install CMake + uses: jwlawson/actions-setup-cmake@v1 + with: + cmake-version: '3.31.1' + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.3' + bundler-cache: true + + - name: Verify Ruby + run: ruby -v + + - name: Decode google-services.json + run: | + echo "${{ secrets.GOOGLE_SERVICES_JSON_GENERIC }}" | base64 --decode > app/google-services.json + + - name: Decode debug keystore + if: inputs.build_type == 'debug' + run: | + echo "${{ secrets.DEBUG_KEYSTORE_FILE }}" | base64 --decode > debug-keystore.jks + echo "Keystore file size: $(stat --format=%s debug-keystore.jks) bytes" + keytool -list -keystore debug-keystore.jks -storepass android -alias debugkey 2>&1 | head -5 + + - name: Decode Firebase credentials + if: inputs.build_type == 'debug' + run: | + echo "${{ secrets.FIREBASE_CREDENTIALS_JSON }}" | base64 --decode > firebase_credentials.json + + - name: Decode Google Play JSON key + if: inputs.build_type == 'release' + run: | + echo "${{ secrets.GOOGLE_PLAY_JSON_KEY }}" | base64 --decode > fastlane/google_play_service_account.json + + - name: Decode keystore + if: inputs.build_type == 'release' + run: | + echo "${{ secrets.KEYSTORE_FILE }}" | base64 --decode > keystore.jks + + - name: Create local.properties + run: echo "sdk.dir=$ANDROID_SDK_ROOT" > local.properties + + - name: Build and Distribute + env: + # Inputs as env vars to avoid shell injection (inputs.version is free-text) + INPUT_FLAVOR: ${{ inputs.flavor }} + INPUT_VERSION: ${{ inputs.version }} + INPUT_VERSION_CODE: ${{ inputs.version_code }} + INPUT_BUILD_TYPE: ${{ inputs.build_type }} + # Native build env vars (must match CMakeLists.txt) + ENCRYPTED_PASS_KEY: ${{ secrets.ENCRYPTED_PASS_KEY }} + ABHA_CLIENT_SECRET: ${{ secrets.ABHA_CLIENT_SECRET }} + ABHA_CLIENT_ID: ${{ secrets.ABHA_CLIENT_ID }} + BASE_TMC_URL: ${{ vars.BASE_TMC_URL }} + BASE_ABHA_URL: ${{ vars.BASE_ABHA_URL }} + ABHA_TOKEN_URL: ${{ vars.ABHA_TOKEN_URL }} + ABHA_AUTH_URL: ${{ vars.ABHA_AUTH_URL }} + CHAT_URL: ${{ vars.CHAT_URL }} + # Signing env vars (used by Fastlane for release builds) + KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} + KEY_ALIAS: ${{ secrets.KEY_ALIAS }} + KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} + # Firebase env vars + FIREBASE_APP_ID: ${{ secrets.FIREBASE_APP_ID }} + run: | + if [ "$INPUT_BUILD_TYPE" = "debug" ]; then + bundle exec fastlane build_and_distribute_debug flavor:"$INPUT_FLAVOR" version_name:"$INPUT_VERSION" version_code:"$INPUT_VERSION_CODE" + else + bundle exec fastlane build_and_distribute_release flavor:"$INPUT_FLAVOR" version_name:"$INPUT_VERSION" version_code:"$INPUT_VERSION_CODE" + fi diff --git a/.github/workflows/flw-android-build.yml b/.github/workflows/flw-android-build.yml new file mode 100644 index 000000000..6bb55ce7d --- /dev/null +++ b/.github/workflows/flw-android-build.yml @@ -0,0 +1,117 @@ +name: FLW Android Build + +on: + workflow_dispatch: + inputs: + project: + description: "Project to build" + required: true + type: choice + options: + - sakshamStag + - sakshamUat + - saksham + - xushrukha + - niramay + - mitaninStag + - mitaninUat + - mitanin + + branch: + description: "Git branch to build" + required: true + default: main + type: string + + version: + description: "Version name (example: 2.5.0)" + required: true + type: string + + versionCode: + description: "Version code (example: 16)" + required: true + type: string + +jobs: + resolve-config: + runs-on: ubuntu-latest + permissions: {} + outputs: + flavor: ${{ steps.resolve.outputs.flavor }} + environment: ${{ steps.resolve.outputs.environment }} + build_type: ${{ steps.resolve.outputs.build_type }} + steps: + - name: Resolve project configuration + id: resolve + run: | + case "${{ inputs.project }}" in + sakshamStag) + echo "flavor=SakshamStag" >> $GITHUB_OUTPUT + echo "environment=SAKSHAM_STAG" >> $GITHUB_OUTPUT + echo "build_type=debug" >> $GITHUB_OUTPUT + ;; + sakshamUat) + echo "flavor=SakshamUat" >> $GITHUB_OUTPUT + echo "environment=SAKSHAM_UAT" >> $GITHUB_OUTPUT + echo "build_type=debug" >> $GITHUB_OUTPUT + ;; + saksham) + echo "flavor=Saksham" >> $GITHUB_OUTPUT + echo "environment=SAKSHAM_PRODUCTION" >> $GITHUB_OUTPUT + echo "build_type=release" >> $GITHUB_OUTPUT + ;; + xushrukha) + echo "flavor=Xushrukha" >> $GITHUB_OUTPUT + echo "environment=XUSHRUKHA_PRODUCTION" >> $GITHUB_OUTPUT + echo "build_type=release" >> $GITHUB_OUTPUT + ;; + niramay) + echo "flavor=Niramay" >> $GITHUB_OUTPUT + echo "environment=NIRAMAY_PRODUCTION" >> $GITHUB_OUTPUT + echo "build_type=release" >> $GITHUB_OUTPUT + ;; + mitaninStag) + echo "flavor=MitaninStag" >> $GITHUB_OUTPUT + echo "environment=MITANIN_STAG" >> $GITHUB_OUTPUT + echo "build_type=debug" >> $GITHUB_OUTPUT + ;; + mitaninUat) + echo "flavor=MitaninUat" >> $GITHUB_OUTPUT + echo "environment=MITANIN_UAT" >> $GITHUB_OUTPUT + echo "build_type=debug" >> $GITHUB_OUTPUT + ;; + mitanin) + echo "flavor=Mitanin" >> $GITHUB_OUTPUT + echo "environment=MITANIN_PRODUCTION" >> $GITHUB_OUTPUT + echo "build_type=release" >> $GITHUB_OUTPUT + ;; + *) + echo "::error::Unknown project: ${{ inputs.project }}" + exit 1 + ;; + esac + + - name: Display resolved configuration + run: | + echo "Project: ${{ inputs.project }}" + echo "Flavor: ${{ steps.resolve.outputs.flavor }}" + echo "Environment: ${{ steps.resolve.outputs.environment }}" + echo "Build Type: ${{ steps.resolve.outputs.build_type }}" + echo "Branch: ${{ inputs.branch }}" + echo "Version: ${{ inputs.version }}" + echo "Version Code: ${{ inputs.versionCode }}" + + flw-build: + needs: resolve-config + permissions: + contents: read + uses: ./.github/workflows/build-distribute.yml + with: + branch: ${{ inputs.branch }} + environment: ${{ needs.resolve-config.outputs.environment }} + flavor: ${{ needs.resolve-config.outputs.flavor }} + build_type: ${{ needs.resolve-config.outputs.build_type }} + version: ${{ inputs.version }} + version_code: ${{ inputs.versionCode }} + secrets: inherit diff --git a/.gitignore b/.gitignore index 754ddaff4..69d935d49 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,16 @@ /.gradle/ *.bin *.lock +!Gemfile.lock /app/build/* /app/release/* +/local.properties +/secrets.properties +**/google-services.json +app/src/main/res/raw/wildcard.crt +app/src/main/cpp/native-lib.cpp +keystore.properties +app/keystore/* +keystore.properties +debug-keystore.jks +debug-keystore-base64.txt diff --git a/.gradle/buildOutputCleanup/cache.properties b/.gradle/buildOutputCleanup/cache.properties deleted file mode 100644 index c7d36898b..000000000 --- a/.gradle/buildOutputCleanup/cache.properties +++ /dev/null @@ -1,2 +0,0 @@ -#Thu Aug 24 13:03:32 IST 2023 -gradle.version=8.0 diff --git a/.gradle/vcs-1/gc.properties b/.gradle/vcs-1/gc.properties deleted file mode 100644 index e69de29bb..000000000 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..2c1a28f15 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +amrit@piramalswasthya.org. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/FirebaseAppDistributionConfig/groups.txt b/FirebaseAppDistributionConfig/groups.txt new file mode 100644 index 000000000..b51c52636 --- /dev/null +++ b/FirebaseAppDistributionConfig/groups.txt @@ -0,0 +1 @@ +trusted-testers \ No newline at end of file diff --git a/FirebaseAppDistributionConfig/release_notes.txt b/FirebaseAppDistributionConfig/release_notes.txt new file mode 100644 index 000000000..a41f18007 --- /dev/null +++ b/FirebaseAppDistributionConfig/release_notes.txt @@ -0,0 +1 @@ +cuIn this version, we improved the user experience and fixed some bugs. diff --git a/Gemfile b/Gemfile new file mode 100644 index 000000000..cdd3a6b34 --- /dev/null +++ b/Gemfile @@ -0,0 +1,6 @@ +source "https://rubygems.org" + +gem "fastlane" + +plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') +eval_gemfile(plugins_path) if File.exist?(plugins_path) diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..f288702d2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md index 7c93b0bd7..7a21f6383 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,161 @@ -# **Generating APK:** +# FLW Mobile App - - Information for the steps followed during the generation of APK’s for Sakhi Application from code +[![DeepWiki](https://img.shields.io/badge/DeepWiki-PSMRI%2FFLW--Mobile--App-blue)](https://deepwiki.com/PSMRI/FLW-Mobile-App) -**Pre-Requesties:** - - Clone the code repo into android studio and build the application +## Overview -**Environments**: We can generate the application for below environments based on requirements - - Amrit Demo - - UAT - - Production +The FLW Mobile App is designed for healthcare programs and consultation services rendered by ASHAs’ to serve pregnant women, mothers, and newborns in India. This eliminates pen and paperwork by ASHAs and allows them to enter beneficiaries data in a digital +process with increased ease and accuracy of data. We named it as SAKHI for Bihar State and Utprerona for +Assam State. -**Configurations:** - - In AppModule class there is a variable declared for base URL pointing to amrit demo or uat or production, we can use the URL we need and comment the rest for required APK +## Functional Modules + + +1. Household List + +2. New Household Registration + +3. All Beneficiaries List + +4. Beneficiary Registration + +5. Eligible Couple List + +6. Mother Care + + • Pregnancy List (PMSMA Form: Pradhan Mantri Surakshit Matritva Abhiyan) + + • Delivery stage List + + • PNC Mother List + + • Reproductive Age List + +7. Child Care + + • Infant List + + • Child List + + • Adolescent List + +8. NCD + + • NCD List + + • NCD Eligible List + + • NCD Priority List + + • NCD Non-Eligible List + +9. Immunization due List + +10. HRP Cases + +11. General OP Care List + +12. Menopause Stage List + +13. Death Reports + +14. Village Level Forms - `object AppModule { +15. ASHA Dashboard + + • Total ANC Women + + • Delivery due list + + • Total Delivery Women + + • Total PNC Women + +16. ASHA To-do List (Scheduler) + + +## Features + +- **User Authentication**: Secure login. +- **Real-time Data**: Access to up-to-date information about beneficiaries. +- **User-Friendly Interface**: Intuitive design for easy navigation. +- **Offline Access**: Ability to use the app without an internet connection. +- **Multilingual Support**: Ability to use app in different languages like English, Hindi, Assamese. + +## Technologies & Tools Used + +- **IDE**: Android Studio. +- **Database**: Room +- **Languages**: XML, Kotlin, SQL +- **Architecture & Architectural Components**: MVVM, Android Architectural Components +- **SDK**: Android SDK 23-34 + +## Installation + +Make sure you have the following installed: + +- [Android Studio](https://developer.android.com/studio) + +To run this project, Follow these steps: + +1. Clone the repository to your local machine, + using: `git clone https://github.com/PSMRI/FLW-Mobile-App`. +2. Open Android Studio. +3. Click on 'Open an existing Android Studio project'. +4. Navigate to the directory where you cloned the project and select the root folder. +5. Wait for Android Studio to sync the project and download the dependencies. +6. Once the sync is done, select build variant you want to work on like uatDebug, statingDebug or productionDebug +7. create folder in \app\src named production, uat or staging as per build variant you want to work and add google JSON file in it. +7. Clean Project and Rebuild and run project +8. you can run the project on an emulator or a physical device. +9. Try to login with valid Credentials if everything is fine you able to login successfully + +### Prerequisites + +- **Secrets**: Set the following secrets in your GitHub repository: + - `ENCODED_AES_KEY` + - `ENCODED_AES_IV` + - `GOOGLE_SERVICES_JSON_NIRAMAY_PRODUCTION` + - `GOOGLE_SERVICES_JSON_XUSHRUKHA_PRODUCTION` + - `GOOGLE_SERVICES_JSON_GENERIC` + - `FIREBASE_CREDENTIALS_JSON_SAKSHAM_ASSAM` + - `FIREBASE_CREDENTIALS_JSON_UTPRERONA_NIRAMAY` + - `FIREBASE_CREDENTIALS_JSON_UTPRERONA_XUSHRUKHA` + - `GOOGLE_PLAY_JSON_KEY` + - `KEYSTORE_FILE` + - `KEYSTORE_PASSWORD` + - `KEY_ALIAS` + - `KEY_PASSWORD` + - `FIREBASE_APP_ID` + - `BASE_TMC_URL` + - `ABHA_CLIENT_ID` + - `ABHA_CLIENT_SECRET` + - `BASE_ABHA_URL` + - `ABHA_TOKEN_URL` + - `ABHA_AUTH_URL` + - `CHAT_URL` + +- **Environment Variables**: + - `environment` (e.g., `NIRAMAY_PRODUCTION`, `XUSHRUKHA_PRODUCTION`, or other environments) + +- **Build Configuration**: + - `variant` (e.g., `Saksham`, `Niramay`, `Xushrukha`) + - `build_type` (e.g., `debug`, `release`) + +- **Files**: + - `google-services.json` for various environments + - `firebase_credentials.json` for different variants + - `google_play_service_account.json` for release builds + - `keystore.jks` + + + + +**Configurations:** + + - In App level build.gradle file productFlavors added. Based on which varient you want to work select build varient - private const val baseTmcUrl = - // "https://assamtmc.piramalswasthya.org/" - // "http://uatamrit.piramalswasthya.org:8080/" - https://amritdemo.piramalswasthya.org/ - ` - In IconDataset class we have the list of modules that we show on UI, we can comment, or un-comment modules based on requirement - In AllBenFragment class we have a Boolean `showAbha` which can be used to toggle the visibility of abha button on beneficiary cards @@ -46,14 +181,13 @@ + https://developer.android.com/studio/publish/app-signing + Once the APK is generated android studio will notify with the location of APK, - generally in release folder - - + generally in release folder - +## Filing Issues - +If you encounter any issues, bugs, or have feature requests, please file them in the [main AMRIT repository](https://github.com/PSMRI/AMRIT/issues). Centralizing all feedback helps us streamline improvements and address concerns efficiently. - +## Join Our Community - \ No newline at end of file +We’d love to have you join our community discussions and get real-time support! +Join our [Discord server](https://discord.gg/FVQWsf5ENS) to connect with contributors, ask questions, and stay updated. diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 000000000..e89122c2a --- /dev/null +++ b/app/.gitignore @@ -0,0 +1,4 @@ +/build +/release +.cxx +.google-services.json \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 9f822daeb..65deedbef 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,5 +1,4 @@ plugins { - id 'com.android.application' id 'org.jetbrains.kotlin.android' id 'androidx.navigation.safeargs.kotlin' @@ -7,75 +6,186 @@ plugins { id 'com.google.gms.google-services' id 'com.google.firebase.crashlytics' id 'kotlin-kapt' + id 'kotlin-parcelize' + id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin' +} +apply from: rootProject.file('versioning.gradle') + +def keystoreProperties = new Properties() +def keystorePropertiesFile = rootProject.file("keystore.properties") +if (keystorePropertiesFile.exists()) { + keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) } android { namespace 'org.piramalswasthya.sakhi' - compileSdk 34 + compileSdk 35 defaultConfig { applicationId "org.piramalswasthya.sakhi" - minSdk 21 - targetSdk 33 - versionCode 2 - versionName "2.0" - resConfigs "en", "hi", "as" + minSdk 25 + targetSdk 35 + versionCode buildVersionCode() + versionName buildVersionName() + resourceConfigurations += ['en', 'hi', 'as'] testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + + } + + splits { + abi { + enable true + reset() + include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' + universalApk true + } + } + + bundle { + language { + enableSplit = false + } + } + + signingConfigs { + release { + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null + storePassword keystoreProperties['storePassword'] + } } buildTypes { release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + minifyEnabled true + shrinkResources true + debuggable false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), + 'proguard-rules.pro' + signingConfig signingConfigs.release + } + + debug { + // uses default debug keystore; no changes needed } } + flavorDimensions "default" + + productFlavors { + + sakshamStag { + applicationIdSuffix ".saksham.stag" + applicationId "org.piramalswasthya.sakhi" + resValue "string", "app_native_name", "উৎপ্ৰেৰণা-Stag" + } + + sakshamUat { + applicationIdSuffix ".saksham.uat" + applicationId "org.piramalswasthya.sakhi" + resValue "string", "app_native_name", "উৎপ্ৰেৰণা-Uat" + } + + saksham { + applicationIdSuffix ".saksham" + applicationId "org.piramalswasthya.sakhi" + resValue "string", "app_native_name", "উৎপ্ৰেৰণা" + } + + xushrukha { + applicationIdSuffix ".xushrukha" + applicationId "org.piramalswasthya.sakhi" + resValue "string", "app_native_name", "Xushrukha - শুশ্ৰূষা" + } + + niramay { + applicationIdSuffix ".niramay" + applicationId "org.piramalswasthya.sakhi" + resValue "string", "app_native_name", "উৎপ্ৰেৰণা" + } + + mitaninStag { + applicationIdSuffix ".mitanin.stag" + applicationId "org.piramalswasthya.sakhi" + resValue "string", "app_native_name", "मितानिन-Stag" + } + + mitaninUat { + applicationIdSuffix ".mitanin.uat" + applicationId "org.piramalswasthya.sakhi" + resValue "string", "app_native_name", "मितानिन-Uat" + } + + mitanin { + applicationIdSuffix ".mitanin" + applicationId "org.piramalswasthya.sakhi" + resValue "string", "app_native_name", "मितानिन" + } + + } + compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } + kapt { + correctErrorTypes true + } + kotlinOptions { jvmTarget = '1.8' } - dataBinding{ - enabled=true + dataBinding { + enabled = true } - viewBinding{ - enabled=true + viewBinding { + enabled = true } buildFeatures { viewBinding true } + + externalNativeBuild { + cmake { + path file("src/main/cpp/CMakeLists.txt") + } + } + ndkVersion '29.0.14033849' + } dependencies { implementation 'androidx.legacy:legacy-support-v4:1.0.0' - implementation 'androidx.hilt:hilt-common:1.0.0' - implementation platform('com.google.firebase:firebase-bom:31.2.2') + implementation platform('com.google.firebase:firebase-bom:32.7.0') implementation 'com.google.firebase:firebase-crashlytics-ktx' implementation 'com.google.firebase:firebase-analytics-ktx' - implementation 'androidx.room:room-testing:2.6.0' - implementation 'androidx.test.ext:junit-ktx:1.1.5' - - def nav_version = "2.5.3" - def frag_version = "1.5.5" - def room_version = "2.5.0" - def lifecycle_version = "2.5.1" - def coroutine_version = "1.6.4" - def work_version = "2.8.0" - def hilt_version = "2.42" - def retrofit_version = "2.9.0" - def moshi_version = "1.13.0" - def okhttp_version = "4.10.0" + implementation 'com.google.firebase:firebase-config-ktx' + implementation 'androidx.test.ext:junit-ktx:1.2.1' + implementation "androidx.security:security-crypto:1.0.0" + implementation 'com.google.crypto.tink:tink-android:1.18.0' + def nav_version = '2.5.3' + def frag_version = '1.8.8' + def room_version = "2.6.1" + def lifecycle_version = '2.6.2'//'2.9.1' + def coroutine_version = '1.7.3' + def work_version = '2.10.2' + def hilt_version = "2.48" + def retrofit_version = '2.9.0' + def moshi_version = '1.15.1' + def okhttp_version = '4.12.0' def timber_version = "5.0.1" - def gson_version = "2.9.0" + def gson_version = "2.11.0" + + implementation 'androidx.core:core-ktx:1.12.0' + implementation 'androidx.appcompat:appcompat:1.7.1' + implementation 'com.google.android.material:material:1.12.0' + implementation 'androidx.constraintlayout:constraintlayout:2.2.1' + implementation 'androidx.recyclerview:recyclerview:1.4.0' - implementation 'androidx.core:core-ktx:1.9.0' - implementation 'androidx.appcompat:appcompat:1.6.1' - implementation 'com.google.android.material:material:1.8.0' - implementation 'androidx.constraintlayout:constraintlayout:2.1.4' - implementation "androidx.recyclerview:recyclerview:1.3.1" + //Paging + implementation "androidx.paging:paging-runtime-ktx:3.2.1" //Navigation implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" @@ -88,14 +198,24 @@ dependencies { implementation "androidx.room:room-runtime:$room_version" implementation "androidx.room:room-ktx:$room_version" kapt "androidx.room:room-compiler:$room_version" + implementation "androidx.room:room-testing:$room_version" + implementation "androidx.room:room-paging:$room_version" + + // implementation "org.jetbrains.kotlin:kotlin-metadata-jvm:2.2.0" + + //firebase messaging + implementation 'com.google.firebase:firebase-messaging:21.0.1' +// implementation platform('com.google.firebase:firebase-bom:28.4.0') + implementation 'com.google.firebase:firebase-analytics-ktx' //Lifecycle implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4' + //Coroutine implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version" //Work Manager @@ -104,8 +224,9 @@ dependencies { //Hilt implementation "com.google.dagger:hilt-android:$hilt_version" kapt "com.google.dagger:hilt-compiler:$hilt_version" - implementation 'androidx.hilt:hilt-work:1.0.0' - kapt 'androidx.hilt:hilt-compiler:1.0.0' + implementation 'androidx.hilt:hilt-work:1.1.0' + kapt "androidx.hilt:hilt-compiler:1.1.0" + implementation 'androidx.hilt:hilt-common:1.1.0' //retrofit implementation "com.squareup.retrofit2:retrofit:$retrofit_version" @@ -134,21 +255,42 @@ dependencies { //Flexbox implementation 'com.google.android.flexbox:flexbox:3.0.0' -// //Encryption -//// implementation 'javax.xml.bind:jaxb-api:2.3.0' -// implementation('javax.xml.bind:jaxb-api:2.3.0') -// implementation('javax.activation:activation:1.1') -// implementation('org.glassfish.jaxb:jaxb-runtime:2.3.0') -// implementation 'org.bouncycastle:bcpkix-jdk15on:1.68' - testImplementation "com.google.truth:truth:1.1.3" - androidTestImplementation 'androidx.test.ext:junit:1.1.5' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + + implementation("com.google.firebase:firebase-messaging:24.0.0") + + // Unit Testing + testImplementation 'junit:junit:4.13.2' + testImplementation 'com.google.truth:truth:1.4.4' + testImplementation 'io.mockk:mockk:1.13.5' + testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4' + testImplementation 'androidx.arch.core:core-testing:2.2.0' + testImplementation 'app.cash.turbine:turbine:0.12.1' + testImplementation "com.google.dagger:hilt-android-testing:$hilt_version" + kaptTest "com.google.dagger:hilt-compiler:$hilt_version" + testImplementation "com.squareup.okhttp3:mockwebserver:$okhttp_version" + testImplementation "androidx.work:work-testing:$work_version" + + // Instrumented Testing + androidTestImplementation 'androidx.test.ext:junit:1.2.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' + androidTestImplementation "com.google.dagger:hilt-android-testing:$hilt_version" + kaptAndroidTest "com.google.dagger:hilt-compiler:$hilt_version" + androidTestImplementation 'io.mockk:mockk-android:1.13.8' //Image compressor implementation 'id.zelory:compressor:3.0.1' - implementation 'androidx.legacy:legacy-support-v4:1.0.0' -} \ No newline at end of file + //Dimens Library + implementation 'com.intuit.sdp:sdp-android:1.1.1' + + //In App Update + //implementation 'com.google.android.play:core:1.10.3' + implementation 'com.google.android.play:app-update:2.1.0' + + implementation "net.zetetic:sqlcipher-android:4.13.0" + implementation "androidx.sqlite:sqlite:2.4.0" +} + diff --git a/app/google-services.json b/app/google-services.json deleted file mode 100644 index 66ae355ee..000000000 --- a/app/google-services.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "project_info": { - "project_number": "289134290963", - "project_id": "sakhi-1e7d2", - "storage_bucket": "sakhi-1e7d2.appspot.com" - }, - "client": [ - { - "client_info": { - "mobilesdk_app_id": "1:289134290963:android:6cd51e6329b3816144f7e6", - "android_client_info": { - "package_name": "org.piramalswasthya.sakhi" - } - }, - "oauth_client": [ - { - "client_id": "289134290963-lskch73k1ujvh2ca3vgs09i2l7rqa4pd.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyBHMkDJkxWgtIZN_T0fQjh5EXFvvY-9DiA" - } - ], - "services": { - "appinvite_service": { - "other_platform_oauth_client": [ - { - "client_id": "289134290963-lskch73k1ujvh2ca3vgs09i2l7rqa4pd.apps.googleusercontent.com", - "client_type": 3 - } - ] - } - } - } - ], - "configuration_version": "1" -} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 481bb4348..d02f93221 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -18,4 +18,118 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +#-renamesourcefileattribute SourceFile +-keep class com.squareup.moshi.** { *; } +-keep class retrofit2.** { *; } +-keep class okhttp3.** { *; } +-keep class androidx.legacy.** { *; } +-keep class com.google.firebase.** { *; } +-keep class com.google.android.gms.** { *; } +-keep class androidx.lifecycle.** { *; } +-keepclassmembers class androidx.lifecycle.** { *; } +-keep class androidx.work.** { *; } +-keep class androidx.startup.** { *; } +-keep class org.bouncycastle.** { *; } +-keep class java.util.Locale { *; } +# Prevent removal of resources used via reflection or dynamic access +-keepclassmembers class ** { + public static ; +} +# Keep all string resource IDs and names +-keepclassmembers class **.R$string { + *; +} + +# --- ContextWrapper --- +-keep class org.piramalswasthya.sakhi.** extends android.content.ContextWrapper { *; } + +# --- Assemblers --- +-keep class org.piramalswasthya.sakhi.**Assembler { *; } +-keepnames class org.piramalswasthya.sakhi.**Assembler + +# --- Hilt / DI Support --- +-keep @dagger.hilt.EntryPoint interface * { *; } +-keep class dagger.hilt.EntryPoints { *; } +-keep class dagger.hilt.EntryPoint { *; } +-keep class dagger.hilt.android.EntryPointAccessors { *; } +-keep class **_HiltModules* { *; } +-keep class *Module* { *; } + +# --- Annotations --- +-keep @androidx.annotation.Keep class * { *; } +-keepclassmembers class * { + @androidx.annotation.Keep *; +} + +# --- Reflection Safety --- +-keep class org.piramalswasthya.sakhi.** { *; } +-keepnames class org.piramalswasthya.sakhi.** + +-dontwarn org.conscrypt.** +-dontwarn org.bouncycastle.** +-dontwarn org.openjsse.** +-dontnote com.google.gson.** +-keepattributes Signature +-keepattributes *Annotation* + +# Application classes that will be serialized/deserialized over Gson +-keep class com.google.gson.examples.android.model.** { *; } +-keepclassmembers class org.piramalswasthya.**.databinding.** { + public ; +} +-keep class org.piramalswasthya.sakhi.model.** {*;} +-keep class org.piramalswasthya.sakhi.network.** {*;} +-keep class org.piramalswasthya.sakhi.repositories.** {*;} +-dontusemixedcaseclassnames +-verbose +-optimizations !code/simplification/arithmetic,!field/*,!class/merging/* + +# Keep all members in kotlinx.coroutines +-keep class kotlinx.coroutines.** { *; } + +# Keep all methods in kotlinx.coroutines +-keepclassmembers class kotlinx.coroutines.** { *; } + +# Keep Kotlin standard library functions +-keep class kotlin.** { *; } +-keepclassmembers class kotlin.** { *; } + +# Keep coroutines continuation classes and methods +-keepclassmembers class kotlin.coroutines.Continuation { + *; +} + +# Prevent warning for kotlinx.coroutines internal classes +-dontwarn kotlinx.coroutines.internal.** + +# Prevent warning for kotlinx.coroutines implementation details +-dontwarn kotlinx.coroutines.** + +# Keep all classes in the JNI package +-keep class org.piramalswasthya.sakhi.jni.** { *; } + +# Keep all native methods +-keepclasseswithmembernames class * { + native ; +} + +-assumenosideeffects class android.util.Log { + public static *** d(...); + public static *** v(...); + public static *** i(...); + public static *** w(...); + public static *** e(...); +} + +# Timber calls are NOT stripped — SyncLogTree (planted in all builds) +# intercepts sync-related logs for the Sync Dashboard logs tab. +# DebugTree is only planted in debug builds, so release logs only +# go to SyncLogManager (in-memory), never to logcat. +# +# -assumenosideeffects class timber.log.Timber { +# public static *** d(...); +# public static *** v(...); +# public static *** i(...); +# public static *** w(...); +# } + diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json deleted file mode 100644 index bb7473105..000000000 --- a/app/release/output-metadata.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "version": 3, - "artifactType": { - "type": "APK", - "kind": "Directory" - }, - "applicationId": "org.piramalswasthya.sakhi", - "variantName": "release", - "elements": [ - { - "type": "SINGLE", - "filters": [], - "attributes": [], - "versionCode": 2, - "versionName": "2.0", - "outputFile": "app-release.apk" - } - ], - "elementType": "File" -} \ No newline at end of file diff --git a/app/src/debug/res/drawable-anydpi/ic_females.xml b/app/src/debug/res/drawable-anydpi/ic_females.xml new file mode 100644 index 000000000..ec6c8fb49 --- /dev/null +++ b/app/src/debug/res/drawable-anydpi/ic_females.xml @@ -0,0 +1,39 @@ + + + + + + + + diff --git a/app/src/debug/res/drawable-mdpi/ic_edit.xml b/app/src/debug/res/drawable-mdpi/ic_edit.xml index 9b586c386..b713ffe6c 100644 --- a/app/src/debug/res/drawable-mdpi/ic_edit.xml +++ b/app/src/debug/res/drawable-mdpi/ic_edit.xml @@ -4,10 +4,7 @@ android:viewportHeight="24.0" android:viewportWidth="24.0" android:width="24dp" - android:tint="#333333" - android:alpha="0.6" - - > + android:tint="#FFFFFF"> diff --git a/app/src/debug/res/drawable-mdpi/logo_circle_green.png b/app/src/debug/res/drawable-mdpi/logo_circle_green.png new file mode 100644 index 000000000..3db5b3cbd Binary files /dev/null and b/app/src/debug/res/drawable-mdpi/logo_circle_green.png differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 212c9f3b1..d788267cd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -4,12 +4,21 @@ + - - + + + + + + + + + android:exported="false" + android:windowSoftInputMode="adjustResize"> @@ -40,6 +53,18 @@ android:exported="false" android:label="@string/title_activity_home" android:screenOrientation="portrait" + android:windowSoftInputMode="adjustPan" + tools:ignore="LockedOrientationActivity"> + + + @@ -62,6 +87,18 @@ android:value="" /> + + + + + + + + + + + + - - \ No newline at end of file + + + + + diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt new file mode 100644 index 000000000..048367a74 --- /dev/null +++ b/app/src/main/cpp/CMakeLists.txt @@ -0,0 +1,54 @@ +cmake_minimum_required(VERSION 3.11) +project(Sakhi LANGUAGES CXX) + +# Fetch environment variables +set(ENCRYPTED_PASS_KEY "$ENV{ENCRYPTED_PASS_KEY}") +set(ABHA_CLIENT_SECRET "$ENV{ABHA_CLIENT_SECRET}") +set(ABHA_CLIENT_ID "$ENV{ABHA_CLIENT_ID}") +set(BASE_TMC_URL "$ENV{BASE_TMC_URL}") +set(BASE_ABHA_URL "$ENV{BASE_ABHA_URL}") +set(ABHA_TOKEN_URL "$ENV{ABHA_TOKEN_URL}") +set(ABHA_AUTH_URL "$ENV{ABHA_AUTH_URL}") +set(CHAT_URL "$ENV{CHAT_URL}") + +# Pass the values to the compiler using add_definitions +add_definitions( + -DENCRYPTED_PASS_KEY=\"${ENCRYPTED_PASS_KEY}\" + -DABHA_CLIENT_SECRET=\"${ABHA_CLIENT_SECRET}\" + -DABHA_CLIENT_ID=\"${ABHA_CLIENT_ID}\" + -DBASE_TMC_URL=\"${BASE_TMC_URL}\" + -DBASE_ABHA_URL=\"${BASE_ABHA_URL}\" + -DABHA_TOKEN_URL=\"${ABHA_TOKEN_URL}\" + -DABHA_AUTH_URL=\"${ABHA_AUTH_URL}\" + -DCHAT_URL=\"${CHAT_URL}\" + -DIS_DEVELOPMENT=0 +) +#DIS_DEVELOPMENT=0 -For Production use 0 , For Development use 1 + +# Define the library name +set(LIBRARY_NAME "sakhi") + +# Add the native-lib.cpp as the source file for the shared library +add_library( + ${LIBRARY_NAME} + SHARED + native-lib.cpp +) + +# Find the log library +find_library( + log-lib + log +) + +# Specifies libraries CMake should link to your target library. +target_link_libraries( + ${LIBRARY_NAME} + ${log-lib} +) + +# 16 KB page size alignment for Google Play compliance (Android 15+) +target_link_options(${LIBRARY_NAME} PRIVATE "-Wl,-z,max-page-size=16384") + +# Include directories +# include_directories(${CMAKE_SOURCE_DIR}/include) diff --git a/app/src/main/cpp/aes.cpp b/app/src/main/cpp/aes.cpp new file mode 100644 index 000000000..4481f7b24 --- /dev/null +++ b/app/src/main/cpp/aes.cpp @@ -0,0 +1,572 @@ +/* + +This is an implementation of the AES algorithm, specifically ECB, CTR and CBC mode. +Block size can be chosen in aes.h - available choices are AES128, AES192, AES256. + +The implementation is verified against the test vectors in: + National Institute of Standards and Technology Special Publication 800-38A 2001 ED + +ECB-AES128 +---------- + + plain-text: + 6bc1bee22e409f96e93d7e117393172a + ae2d8a571e03ac9c9eb76fac45af8e51 + 30c81c46a35ce411e5fbc1191a0a52ef + f69f2445df4f9b17ad2b417be66c3710 + + key: + 2b7e151628aed2a6abf7158809cf4f3c + + resulting cipher + 3ad77bb40d7a3660a89ecaf32466ef97 + f5d3d58503b9699de785895a96fdbaaf + 43b1cd7f598ece23881b00e3ed030688 + 7b0c785e27e8ad3f8223207104725dd4 + + +NOTE: String length must be evenly divisible by 16byte (str_len % 16 == 0) + You should pad the end of the string with zeros if this is not the case. + For AES192/256 the key size is proportionally larger. + +*/ + + +/*****************************************************************************/ +/* Includes: */ +/*****************************************************************************/ +#include // CBC mode, for memset +#include "aes.h" + +/*****************************************************************************/ +/* Defines: */ +/*****************************************************************************/ +// The number of columns comprising a state in AES. This is a constant in AES. Value=4 +#define Nb 4 + +#if defined(AES256) && (AES256 == 1) + #define Nk 8 + #define Nr 14 +#elif defined(AES192) && (AES192 == 1) + #define Nk 6 + #define Nr 12 +#else + #define Nk 4 // The number of 32 bit words in a key. + #define Nr 10 // The number of rounds in AES Cipher. +#endif + +// jcallan@github points out that declaring Multiply as a function +// reduces code size considerably with the Keil ARM compiler. +// See this link for more information: https://github.com/kokke/tiny-AES-C/pull/3 +#ifndef MULTIPLY_AS_A_FUNCTION + #define MULTIPLY_AS_A_FUNCTION 0 +#endif + + + + +/*****************************************************************************/ +/* Private variables: */ +/*****************************************************************************/ +// state - array holding the intermediate results during decryption. +typedef uint8_t state_t[4][4]; + + + +// The lookup-tables are marked const so they can be placed in read-only storage instead of RAM +// The numbers below can be computed dynamically trading ROM for RAM - +// This can be useful in (embedded) bootloader applications, where ROM is often limited. +static const uint8_t sbox[256] = { + //0 1 2 3 4 5 6 7 8 9 A B C D E F + 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, + 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, + 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, + 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, + 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, + 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, + 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, + 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, + 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, + 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, + 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 }; + +#if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) +static const uint8_t rsbox[256] = { + 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, + 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, + 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, + 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, + 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, + 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, + 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, + 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, + 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, + 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, + 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, + 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, + 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, + 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, + 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d }; +#endif + +// The round constant word array, Rcon[i], contains the values given by +// x to the power (i-1) being powers of x (x is denoted as {02}) in the field GF(2^8) +static const uint8_t Rcon[11] = { + 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 }; + +/* + * Jordan Goulder points out in PR #12 (https://github.com/kokke/tiny-AES-C/pull/12), + * that you can remove most of the elements in the Rcon array, because they are unused. + * + * From Wikipedia's article on the Rijndael key schedule @ https://en.wikipedia.org/wiki/Rijndael_key_schedule#Rcon + * + * "Only the first some of these constants are actually used – up to rcon[10] for AES-128 (as 11 round keys are needed), + * up to rcon[8] for AES-192, up to rcon[7] for AES-256. rcon[0] is not used in AES algorithm." + */ + + +/*****************************************************************************/ +/* Private functions: */ +/*****************************************************************************/ +/* +static uint8_t getSBoxValue(uint8_t num) +{ + return sbox[num]; +} +*/ +#define getSBoxValue(num) (sbox[(num)]) + +// This function produces Nb(Nr+1) round keys. The round keys are used in each round to decrypt the states. +static void KeyExpansion(uint8_t* RoundKey, const uint8_t* Key) +{ + unsigned i, j, k; + uint8_t tempa[4]; // Used for the column/row operations + + // The first round key is the key itself. + for (i = 0; i < Nk; ++i) + { + RoundKey[(i * 4) + 0] = Key[(i * 4) + 0]; + RoundKey[(i * 4) + 1] = Key[(i * 4) + 1]; + RoundKey[(i * 4) + 2] = Key[(i * 4) + 2]; + RoundKey[(i * 4) + 3] = Key[(i * 4) + 3]; + } + + // All other round keys are found from the previous round keys. + for (i = Nk; i < Nb * (Nr + 1); ++i) + { + { + k = (i - 1) * 4; + tempa[0]=RoundKey[k + 0]; + tempa[1]=RoundKey[k + 1]; + tempa[2]=RoundKey[k + 2]; + tempa[3]=RoundKey[k + 3]; + + } + + if (i % Nk == 0) + { + // This function shifts the 4 bytes in a word to the left once. + // [a0,a1,a2,a3] becomes [a1,a2,a3,a0] + + // Function RotWord() + { + const uint8_t u8tmp = tempa[0]; + tempa[0] = tempa[1]; + tempa[1] = tempa[2]; + tempa[2] = tempa[3]; + tempa[3] = u8tmp; + } + + // SubWord() is a function that takes a four-byte input word and + // applies the S-box to each of the four bytes to produce an output word. + + // Function Subword() + { + tempa[0] = getSBoxValue(tempa[0]); + tempa[1] = getSBoxValue(tempa[1]); + tempa[2] = getSBoxValue(tempa[2]); + tempa[3] = getSBoxValue(tempa[3]); + } + + tempa[0] = tempa[0] ^ Rcon[i/Nk]; + } +#if defined(AES256) && (AES256 == 1) + if (i % Nk == 4) + { + // Function Subword() + { + tempa[0] = getSBoxValue(tempa[0]); + tempa[1] = getSBoxValue(tempa[1]); + tempa[2] = getSBoxValue(tempa[2]); + tempa[3] = getSBoxValue(tempa[3]); + } + } +#endif + j = i * 4; k=(i - Nk) * 4; + RoundKey[j + 0] = RoundKey[k + 0] ^ tempa[0]; + RoundKey[j + 1] = RoundKey[k + 1] ^ tempa[1]; + RoundKey[j + 2] = RoundKey[k + 2] ^ tempa[2]; + RoundKey[j + 3] = RoundKey[k + 3] ^ tempa[3]; + } +} + +void AES_init_ctx(struct AES_ctx* ctx, const uint8_t* key) +{ + KeyExpansion(ctx->RoundKey, key); +} +#if (defined(CBC) && (CBC == 1)) || (defined(CTR) && (CTR == 1)) +void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, const uint8_t* iv) +{ + KeyExpansion(ctx->RoundKey, key); + memcpy (ctx->Iv, iv, AES_BLOCKLEN); +} +void AES_ctx_set_iv(struct AES_ctx* ctx, const uint8_t* iv) +{ + memcpy (ctx->Iv, iv, AES_BLOCKLEN); +} +#endif + +// This function adds the round key to state. +// The round key is added to the state by an XOR function. +static void AddRoundKey(uint8_t round, state_t* state, const uint8_t* RoundKey) +{ + uint8_t i,j; + for (i = 0; i < 4; ++i) + { + for (j = 0; j < 4; ++j) + { + (*state)[i][j] ^= RoundKey[(round * Nb * 4) + (i * Nb) + j]; + } + } +} + +// The SubBytes Function Substitutes the values in the +// state matrix with values in an S-box. +static void SubBytes(state_t* state) +{ + uint8_t i, j; + for (i = 0; i < 4; ++i) + { + for (j = 0; j < 4; ++j) + { + (*state)[j][i] = getSBoxValue((*state)[j][i]); + } + } +} + +// The ShiftRows() function shifts the rows in the state to the left. +// Each row is shifted with different offset. +// Offset = Row number. So the first row is not shifted. +static void ShiftRows(state_t* state) +{ + uint8_t temp; + + // Rotate first row 1 columns to left + temp = (*state)[0][1]; + (*state)[0][1] = (*state)[1][1]; + (*state)[1][1] = (*state)[2][1]; + (*state)[2][1] = (*state)[3][1]; + (*state)[3][1] = temp; + + // Rotate second row 2 columns to left + temp = (*state)[0][2]; + (*state)[0][2] = (*state)[2][2]; + (*state)[2][2] = temp; + + temp = (*state)[1][2]; + (*state)[1][2] = (*state)[3][2]; + (*state)[3][2] = temp; + + // Rotate third row 3 columns to left + temp = (*state)[0][3]; + (*state)[0][3] = (*state)[3][3]; + (*state)[3][3] = (*state)[2][3]; + (*state)[2][3] = (*state)[1][3]; + (*state)[1][3] = temp; +} + +static uint8_t xtime(uint8_t x) +{ + return ((x<<1) ^ (((x>>7) & 1) * 0x1b)); +} + +// MixColumns function mixes the columns of the state matrix +static void MixColumns(state_t* state) +{ + uint8_t i; + uint8_t Tmp, Tm, t; + for (i = 0; i < 4; ++i) + { + t = (*state)[i][0]; + Tmp = (*state)[i][0] ^ (*state)[i][1] ^ (*state)[i][2] ^ (*state)[i][3] ; + Tm = (*state)[i][0] ^ (*state)[i][1] ; Tm = xtime(Tm); (*state)[i][0] ^= Tm ^ Tmp ; + Tm = (*state)[i][1] ^ (*state)[i][2] ; Tm = xtime(Tm); (*state)[i][1] ^= Tm ^ Tmp ; + Tm = (*state)[i][2] ^ (*state)[i][3] ; Tm = xtime(Tm); (*state)[i][2] ^= Tm ^ Tmp ; + Tm = (*state)[i][3] ^ t ; Tm = xtime(Tm); (*state)[i][3] ^= Tm ^ Tmp ; + } +} + +// Multiply is used to multiply numbers in the field GF(2^8) +// Note: The last call to xtime() is unneeded, but often ends up generating a smaller binary +// The compiler seems to be able to vectorize the operation better this way. +// See https://github.com/kokke/tiny-AES-c/pull/34 +#if MULTIPLY_AS_A_FUNCTION +static uint8_t Multiply(uint8_t x, uint8_t y) +{ + return (((y & 1) * x) ^ + ((y>>1 & 1) * xtime(x)) ^ + ((y>>2 & 1) * xtime(xtime(x))) ^ + ((y>>3 & 1) * xtime(xtime(xtime(x)))) ^ + ((y>>4 & 1) * xtime(xtime(xtime(xtime(x)))))); /* this last call to xtime() can be omitted */ + } +#else +#define Multiply(x, y) \ + ( ((y & 1) * x) ^ \ + ((y>>1 & 1) * xtime(x)) ^ \ + ((y>>2 & 1) * xtime(xtime(x))) ^ \ + ((y>>3 & 1) * xtime(xtime(xtime(x)))) ^ \ + ((y>>4 & 1) * xtime(xtime(xtime(xtime(x)))))) \ + +#endif + +#if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) +/* +static uint8_t getSBoxInvert(uint8_t num) +{ + return rsbox[num]; +} +*/ +#define getSBoxInvert(num) (rsbox[(num)]) + +// MixColumns function mixes the columns of the state matrix. +// The method used to multiply may be difficult to understand for the inexperienced. +// Please use the references to gain more information. +static void InvMixColumns(state_t* state) +{ + int i; + uint8_t a, b, c, d; + for (i = 0; i < 4; ++i) + { + a = (*state)[i][0]; + b = (*state)[i][1]; + c = (*state)[i][2]; + d = (*state)[i][3]; + + (*state)[i][0] = Multiply(a, 0x0e) ^ Multiply(b, 0x0b) ^ Multiply(c, 0x0d) ^ Multiply(d, 0x09); + (*state)[i][1] = Multiply(a, 0x09) ^ Multiply(b, 0x0e) ^ Multiply(c, 0x0b) ^ Multiply(d, 0x0d); + (*state)[i][2] = Multiply(a, 0x0d) ^ Multiply(b, 0x09) ^ Multiply(c, 0x0e) ^ Multiply(d, 0x0b); + (*state)[i][3] = Multiply(a, 0x0b) ^ Multiply(b, 0x0d) ^ Multiply(c, 0x09) ^ Multiply(d, 0x0e); + } +} + + +// The SubBytes Function Substitutes the values in the +// state matrix with values in an S-box. +static void InvSubBytes(state_t* state) +{ + uint8_t i, j; + for (i = 0; i < 4; ++i) + { + for (j = 0; j < 4; ++j) + { + (*state)[j][i] = getSBoxInvert((*state)[j][i]); + } + } +} + +static void InvShiftRows(state_t* state) +{ + uint8_t temp; + + // Rotate first row 1 columns to right + temp = (*state)[3][1]; + (*state)[3][1] = (*state)[2][1]; + (*state)[2][1] = (*state)[1][1]; + (*state)[1][1] = (*state)[0][1]; + (*state)[0][1] = temp; + + // Rotate second row 2 columns to right + temp = (*state)[0][2]; + (*state)[0][2] = (*state)[2][2]; + (*state)[2][2] = temp; + + temp = (*state)[1][2]; + (*state)[1][2] = (*state)[3][2]; + (*state)[3][2] = temp; + + // Rotate third row 3 columns to right + temp = (*state)[0][3]; + (*state)[0][3] = (*state)[1][3]; + (*state)[1][3] = (*state)[2][3]; + (*state)[2][3] = (*state)[3][3]; + (*state)[3][3] = temp; +} +#endif // #if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) + +// Cipher is the main function that encrypts the PlainText. +static void Cipher(state_t* state, const uint8_t* RoundKey) +{ + uint8_t round = 0; + + // Add the First round key to the state before starting the rounds. + AddRoundKey(0, state, RoundKey); + + // There will be Nr rounds. + // The first Nr-1 rounds are identical. + // These Nr rounds are executed in the loop below. + // Last one without MixColumns() + for (round = 1; ; ++round) + { + SubBytes(state); + ShiftRows(state); + if (round == Nr) { + break; + } + MixColumns(state); + AddRoundKey(round, state, RoundKey); + } + // Add round key to last round + AddRoundKey(Nr, state, RoundKey); +} + +#if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) +static void InvCipher(state_t* state, const uint8_t* RoundKey) +{ + uint8_t round = 0; + + // Add the First round key to the state before starting the rounds. + AddRoundKey(Nr, state, RoundKey); + + // There will be Nr rounds. + // The first Nr-1 rounds are identical. + // These Nr rounds are executed in the loop below. + // Last one without InvMixColumn() + for (round = (Nr - 1); ; --round) + { + InvShiftRows(state); + InvSubBytes(state); + AddRoundKey(round, state, RoundKey); + if (round == 0) { + break; + } + InvMixColumns(state); + } + +} +#endif // #if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) + +/*****************************************************************************/ +/* Public functions: */ +/*****************************************************************************/ +#if defined(ECB) && (ECB == 1) + + +void AES_ECB_encrypt(const struct AES_ctx* ctx, uint8_t* buf) +{ + // The next function call encrypts the PlainText with the Key using AES algorithm. + Cipher((state_t*)buf, ctx->RoundKey); +} + +void AES_ECB_decrypt(const struct AES_ctx* ctx, uint8_t* buf) +{ + // The next function call decrypts the PlainText with the Key using AES algorithm. + InvCipher((state_t*)buf, ctx->RoundKey); +} + + +#endif // #if defined(ECB) && (ECB == 1) + + + + + +#if defined(CBC) && (CBC == 1) + + +static void XorWithIv(uint8_t* buf, const uint8_t* Iv) +{ + uint8_t i; + for (i = 0; i < AES_BLOCKLEN; ++i) // The block in AES is always 128bit no matter the key size + { + buf[i] ^= Iv[i]; + } +} + +void AES_CBC_encrypt_buffer(struct AES_ctx *ctx, uint8_t* buf, size_t length) +{ + size_t i; + uint8_t *Iv = ctx->Iv; + for (i = 0; i < length; i += AES_BLOCKLEN) + { + XorWithIv(buf, Iv); + Cipher((state_t*)buf, ctx->RoundKey); + Iv = buf; + buf += AES_BLOCKLEN; + } + /* store Iv in ctx for next call */ + memcpy(ctx->Iv, Iv, AES_BLOCKLEN); +} + +void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) +{ + size_t i; + uint8_t storeNextIv[AES_BLOCKLEN]; + for (i = 0; i < length; i += AES_BLOCKLEN) + { + memcpy(storeNextIv, buf, AES_BLOCKLEN); + InvCipher((state_t*)buf, ctx->RoundKey); + XorWithIv(buf, ctx->Iv); + memcpy(ctx->Iv, storeNextIv, AES_BLOCKLEN); + buf += AES_BLOCKLEN; + } + +} + +#endif // #if defined(CBC) && (CBC == 1) + + + +#if defined(CTR) && (CTR == 1) + +/* Symmetrical operation: same function for encrypting as for decrypting. Note any IV/nonce should never be reused with the same key */ +void AES_CTR_xcrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) +{ + uint8_t buffer[AES_BLOCKLEN]; + + size_t i; + int bi; + for (i = 0, bi = AES_BLOCKLEN; i < length; ++i, ++bi) + { + if (bi == AES_BLOCKLEN) /* we need to regen xor compliment in buffer */ + { + + memcpy(buffer, ctx->Iv, AES_BLOCKLEN); + Cipher((state_t*)buffer,ctx->RoundKey); + + /* Increment Iv and handle overflow */ + for (bi = (AES_BLOCKLEN - 1); bi >= 0; --bi) + { + /* inc will overflow */ + if (ctx->Iv[bi] == 255) + { + ctx->Iv[bi] = 0; + continue; + } + ctx->Iv[bi] += 1; + break; + } + bi = 0; + } + + buf[i] = (buf[i] ^ buffer[bi]); + } +} + +#endif // #if defined(CTR) && (CTR == 1) + diff --git a/app/src/main/cpp/aes.h b/app/src/main/cpp/aes.h new file mode 100644 index 000000000..b29b66835 --- /dev/null +++ b/app/src/main/cpp/aes.h @@ -0,0 +1,91 @@ +#ifndef _AES_H_ +#define _AES_H_ + +#include +#include + +// #define the macros below to 1/0 to enable/disable the mode of operation. +// +// CBC enables AES encryption in CBC-mode of operation. +// CTR enables encryption in counter-mode. +// ECB enables the basic ECB 16-byte block algorithm. All can be enabled simultaneously. + +// The #ifndef-guard allows it to be configured before #include'ing or at compile time. +#ifndef CBC + #define CBC 1 +#endif + +#ifndef ECB + #define ECB 1 +#endif + +#ifndef CTR + #define CTR 1 +#endif + + +#define AES128 1 +//#define AES192 1 +//#define AES256 1 + +#define AES_BLOCKLEN 16 // Block length in bytes - AES is 128b block only + +#if defined(AES256) && (AES256 == 1) + #define AES_KEYLEN 32 + #define AES_keyExpSize 240 +#elif defined(AES192) && (AES192 == 1) + #define AES_KEYLEN 24 + #define AES_keyExpSize 208 +#else + #define AES_KEYLEN 16 // Key length in bytes + #define AES_keyExpSize 176 +#endif + +struct AES_ctx +{ + uint8_t RoundKey[AES_keyExpSize]; +#if (defined(CBC) && (CBC == 1)) || (defined(CTR) && (CTR == 1)) + uint8_t Iv[AES_BLOCKLEN]; +#endif +}; + +void AES_init_ctx(struct AES_ctx* ctx, const uint8_t* key); +#if (defined(CBC) && (CBC == 1)) || (defined(CTR) && (CTR == 1)) +void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, const uint8_t* iv); +void AES_ctx_set_iv(struct AES_ctx* ctx, const uint8_t* iv); +#endif + +#if defined(ECB) && (ECB == 1) +// buffer size is exactly AES_BLOCKLEN bytes; +// you need only AES_init_ctx as IV is not used in ECB +// NB: ECB is considered insecure for most uses +void AES_ECB_encrypt(const struct AES_ctx* ctx, uint8_t* buf); +void AES_ECB_decrypt(const struct AES_ctx* ctx, uint8_t* buf); + +#endif // #if defined(ECB) && (ECB == !) + + +#if defined(CBC) && (CBC == 1) +// buffer size MUST be mutile of AES_BLOCKLEN; +// Suggest https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme +// NOTES: you need to set IV in ctx via AES_init_ctx_iv() or AES_ctx_set_iv() +// no IV should ever be reused with the same key +void AES_CBC_encrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); +void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); + +#endif // #if defined(CBC) && (CBC == 1) + + +#if defined(CTR) && (CTR == 1) + +// Same function for encrypting as for decrypting. +// IV is incremented for every block, and used after encryption as XOR-compliment for output +// Suggesting https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme +// NOTES: you need to set IV in ctx with AES_init_ctx_iv() or AES_ctx_set_iv() +// no IV should ever be reused with the same key +void AES_CTR_xcrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); + +#endif // #if defined(CTR) && (CTR == 1) + + +#endif // _AES_H_ diff --git a/app/src/main/cpp/base64.cpp b/app/src/main/cpp/base64.cpp new file mode 100644 index 000000000..843719ec9 --- /dev/null +++ b/app/src/main/cpp/base64.cpp @@ -0,0 +1,282 @@ +/* + base64.cpp and base64.h + + base64 encoding and decoding with C++. + More information at + https://renenyffenegger.ch/notes/development/Base64/Encoding-and-decoding-base-64-with-cpp + + Version: 2.rc.09 (release candidate) + + Copyright (C) 2004-2017, 2020-2022 René Nyffenegger + + This source code is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this source code must not be misrepresented; you must not + claim that you wrote the original source code. If you use this source code + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original source code. + + 3. This notice may not be removed or altered from any source distribution. + + René Nyffenegger rene.nyffenegger@adp-gmbh.ch + +*/ + +#include "base64.h" + +#include +#include + + // + // Depending on the url parameter in base64_chars, one of + // two sets of base64 characters needs to be chosen. + // They differ in their last two characters. + // +static const char* base64_chars[2] = { + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "+/", + + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "-_"}; + +static unsigned int pos_of_char(const unsigned char chr) { + // + // Return the position of chr within base64_encode() + // + + if (chr >= 'A' && chr <= 'Z') return chr - 'A'; + else if (chr >= 'a' && chr <= 'z') return chr - 'a' + ('Z' - 'A') + 1; + else if (chr >= '0' && chr <= '9') return chr - '0' + ('Z' - 'A') + ('z' - 'a') + 2; + else if (chr == '+' || chr == '-') return 62; // Be liberal with input and accept both url ('-') and non-url ('+') base 64 characters ( + else if (chr == '/' || chr == '_') return 63; // Ditto for '/' and '_' + else + // + // 2020-10-23: Throw std::exception rather than const char* + //(Pablo Martin-Gomez, https://github.com/Bouska) + // + throw std::runtime_error("Input is not valid base64-encoded data."); +} + +static std::string insert_linebreaks(std::string str, size_t distance) { + // + // Provided by https://github.com/JomaCorpFX, adapted by me. + // + if (!str.length()) { + return ""; + } + + size_t pos = distance; + + while (pos < str.size()) { + str.insert(pos, "\n"); + pos += distance + 1; + } + + return str; +} + +template +static std::string encode_with_line_breaks(String s) { + return insert_linebreaks(base64_encode(s, false), line_length); +} + +template +static std::string encode_pem(String s) { + return encode_with_line_breaks(s); +} + +template +static std::string encode_mime(String s) { + return encode_with_line_breaks(s); +} + +template +static std::string encode(String s, bool url) { + return base64_encode(reinterpret_cast(s.data()), s.length(), url); +} + +std::string base64_encode(unsigned char const* bytes_to_encode, size_t in_len, bool url) { + + size_t len_encoded = (in_len +2) / 3 * 4; + + unsigned char trailing_char = url ? '.' : '='; + + // + // Choose set of base64 characters. They differ + // for the last two positions, depending on the url + // parameter. + // A bool (as is the parameter url) is guaranteed + // to evaluate to either 0 or 1 in C++ therefore, + // the correct character set is chosen by subscripting + // base64_chars with url. + // + const char* base64_chars_ = base64_chars[url]; + + std::string ret; + ret.reserve(len_encoded); + + unsigned int pos = 0; + + while (pos < in_len) { + ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0xfc) >> 2]); + + if (pos+1 < in_len) { + ret.push_back(base64_chars_[((bytes_to_encode[pos + 0] & 0x03) << 4) + ((bytes_to_encode[pos + 1] & 0xf0) >> 4)]); + + if (pos+2 < in_len) { + ret.push_back(base64_chars_[((bytes_to_encode[pos + 1] & 0x0f) << 2) + ((bytes_to_encode[pos + 2] & 0xc0) >> 6)]); + ret.push_back(base64_chars_[ bytes_to_encode[pos + 2] & 0x3f]); + } + else { + ret.push_back(base64_chars_[(bytes_to_encode[pos + 1] & 0x0f) << 2]); + ret.push_back(trailing_char); + } + } + else { + + ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0x03) << 4]); + ret.push_back(trailing_char); + ret.push_back(trailing_char); + } + + pos += 3; + } + + + return ret; +} + +template +static std::string decode(String const& encoded_string, bool remove_linebreaks) { + // + // decode(…) is templated so that it can be used with String = const std::string& + // or std::string_view (requires at least C++17) + // + + if (encoded_string.empty()) return std::string(); + + if (remove_linebreaks) { + + std::string copy(encoded_string); + + copy.erase(std::remove(copy.begin(), copy.end(), '\n'), copy.end()); + + return base64_decode(copy, false); + } + + size_t length_of_string = encoded_string.length(); + size_t pos = 0; + + // + // The approximate length (bytes) of the decoded string might be one or + // two bytes smaller, depending on the amount of trailing equal signs + // in the encoded string. This approximation is needed to reserve + // enough space in the string to be returned. + // + size_t approx_length_of_decoded_string = length_of_string / 4 * 3; + std::string ret; + ret.reserve(approx_length_of_decoded_string); + + while (pos < length_of_string) { + // + // Iterate over encoded input string in chunks. The size of all + // chunks except the last one is 4 bytes. + // + // The last chunk might be padded with equal signs or dots + // in order to make it 4 bytes in size as well, but this + // is not required as per RFC 2045. + // + // All chunks except the last one produce three output bytes. + // + // The last chunk produces at least one and up to three bytes. + // + + size_t pos_of_char_1 = pos_of_char(encoded_string.at(pos+1) ); + + // + // Emit the first output byte that is produced in each chunk: + // + ret.push_back(static_cast( ( (pos_of_char(encoded_string.at(pos+0)) ) << 2 ) + ( (pos_of_char_1 & 0x30 ) >> 4))); + + if ( ( pos + 2 < length_of_string ) && // Check for data that is not padded with equal signs (which is allowed by RFC 2045) + encoded_string.at(pos+2) != '=' && + encoded_string.at(pos+2) != '.' // accept URL-safe base 64 strings, too, so check for '.' also. + ) + { + // + // Emit a chunk's second byte (which might not be produced in the last chunk). + // + unsigned int pos_of_char_2 = pos_of_char(encoded_string.at(pos+2) ); + ret.push_back(static_cast( (( pos_of_char_1 & 0x0f) << 4) + (( pos_of_char_2 & 0x3c) >> 2))); + + if ( ( pos + 3 < length_of_string ) && + encoded_string.at(pos+3) != '=' && + encoded_string.at(pos+3) != '.' + ) + { + // + // Emit a chunk's third byte (which might not be produced in the last chunk). + // + ret.push_back(static_cast( ( (pos_of_char_2 & 0x03 ) << 6 ) + pos_of_char(encoded_string.at(pos+3)) )); + } + } + + pos += 4; + } + + return ret; +} + +std::string base64_decode(std::string const& s, bool remove_linebreaks) { + return decode(s, remove_linebreaks); +} + +std::string base64_encode(std::string const& s, bool url) { + return encode(s, url); +} + +std::string base64_encode_pem (std::string const& s) { + return encode_pem(s); +} + +std::string base64_encode_mime(std::string const& s) { + return encode_mime(s); +} + +#if __cplusplus >= 201703L +// +// Interface with std::string_view rather than const std::string& +// Requires C++17 +// Provided by Yannic Bonenberger (https://github.com/Yannic) +// + +std::string base64_encode(std::string_view s, bool url) { + return encode(s, url); +} + +std::string base64_encode_pem(std::string_view s) { + return encode_pem(s); +} + +std::string base64_encode_mime(std::string_view s) { + return encode_mime(s); +} + +std::string base64_decode(std::string_view s, bool remove_linebreaks) { + return decode(s, remove_linebreaks); +} + +#endif // __cplusplus >= 201703L diff --git a/app/src/main/cpp/base64.h b/app/src/main/cpp/base64.h new file mode 100644 index 000000000..4860d63d8 --- /dev/null +++ b/app/src/main/cpp/base64.h @@ -0,0 +1,35 @@ +// +// base64 encoding and decoding with C++. +// Version: 2.rc.09 (release candidate) +// + +#ifndef BASE64_H_C0CE2A47_D10E_42C9_A27C_C883944E704A +#define BASE64_H_C0CE2A47_D10E_42C9_A27C_C883944E704A + +#include + +#if __cplusplus >= 201703L +#include +#endif // __cplusplus >= 201703L + +std::string base64_encode (std::string const& s, bool url = false); +std::string base64_encode_pem (std::string const& s); +std::string base64_encode_mime(std::string const& s); + +std::string base64_decode(std::string const& s, bool remove_linebreaks = false); +std::string base64_encode(unsigned char const*, size_t len, bool url = false); + +#if __cplusplus >= 201703L +// +// Interface with std::string_view rather than const std::string& +// Requires C++17 +// Provided by Yannic Bonenberger (https://github.com/Yannic) +// +std::string base64_encode (std::string_view s, bool url = false); +std::string base64_encode_pem (std::string_view s); +std::string base64_encode_mime(std::string_view s); + +std::string base64_decode(std::string_view s, bool remove_linebreaks = false); +#endif // __cplusplus >= 201703L + +#endif /* BASE64_H_C0CE2A47_D10E_42C9_A27C_C883944E704A */ diff --git a/app/src/main/ic_mitanin_launcher-playstore.png b/app/src/main/ic_mitanin_launcher-playstore.png new file mode 100644 index 000000000..717e93b00 Binary files /dev/null and b/app/src/main/ic_mitanin_launcher-playstore.png differ diff --git a/app/src/main/java/org/piramalswasthya/sakhi/SakhiApplication.kt b/app/src/main/java/org/piramalswasthya/sakhi/SakhiApplication.kt index d18af9264..5175c3864 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/SakhiApplication.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/SakhiApplication.kt @@ -1,11 +1,25 @@ package org.piramalswasthya.sakhi import android.app.Application +import android.app.NotificationChannel +import android.app.NotificationManager +import android.os.Build import android.os.StrictMode import android.os.StrictMode.VmPolicy +import androidx.appcompat.app.AppCompatDelegate import androidx.hilt.work.HiltWorkerFactory import androidx.work.Configuration +import com.google.firebase.FirebaseApp import dagger.hilt.android.HiltAndroidApp +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import org.piramalswasthya.sakhi.database.room.InAppDb +import org.piramalswasthya.sakhi.helpers.CrashHandler +import org.piramalswasthya.sakhi.helpers.SyncLogFileWriter +import org.piramalswasthya.sakhi.helpers.SyncLogManager +import org.piramalswasthya.sakhi.helpers.SyncLogTree +import org.piramalswasthya.sakhi.utils.KeyUtils import timber.log.Timber import javax.inject.Inject @@ -16,16 +30,129 @@ class SakhiApplication : Application(), Configuration.Provider { @Inject lateinit var workerFactory: HiltWorkerFactory - override fun getWorkManagerConfiguration() = - Configuration.Builder() + @Inject + lateinit var database: InAppDb + + @Inject + lateinit var syncLogManager: SyncLogManager + + @Inject + lateinit var syncLogFileWriter: SyncLogFileWriter + + override val workManagerConfiguration: Configuration + get() = Configuration.Builder() .setWorkerFactory(workerFactory) .build() override fun onCreate() { super.onCreate() + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) val builder = VmPolicy.Builder() StrictMode.setVmPolicy(builder.build()) - Timber.plant(Timber.DebugTree()) + if (BuildConfig.DEBUG) { + Timber.plant(Timber.DebugTree()) + } + Timber.plant(SyncLogTree(syncLogManager)) + syncLogFileWriter.runRotation() + KeyUtils.encryptedPassKey() + KeyUtils.baseAbhaUrl() + KeyUtils.baseTMCUrl() + KeyUtils.abhaAuthUrl() + KeyUtils.abhaClientID() + KeyUtils.abhaClientSecret() + KeyUtils.abhaTokenUrl() + FirebaseApp.initializeApp(this) + createNotificationChannels() + + Thread.setDefaultUncaughtExceptionHandler(CrashHandler(applicationContext)) + + // Recover any records orphaned in SYNCING state from a previous crash/kill + CoroutineScope(Dispatchers.IO).launch { + try { + recoverOrphanedSyncStates() + } catch (e: Exception) { + Timber.e(e, "Failed to recover orphaned SYNCING states") + } + } } + /** + * Create notification channels early so that WorkManager workers always have + * a valid channel available — even after a device reboot when workers restart + * before LoginActivity.onCreate() runs. + */ + private fun createNotificationChannels() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val nm = getSystemService(NotificationManager::class.java) + + // Sync channel used by all push/pull/dynamic workers + nm.createNotificationChannel( + NotificationChannel( + getString(R.string.notification_sync_channel_id), + getString(R.string.notification_sync_channel_name), + NotificationManager.IMPORTANCE_DEFAULT + ).apply { + description = getString(R.string.notification_sync_channel_description) + } + ) + + // Download channel used by DownloadCardWorker + nm.createNotificationChannel( + NotificationChannel( + "download abha card", + "Download ABHA Card", + NotificationManager.IMPORTANCE_DEFAULT + ).apply { + description = "Notifications for ABHA card downloads" + } + ) + } + } + + private suspend fun recoverOrphanedSyncStates() { + // Reset all records stuck in SYNCING (ordinal 1) back to UNSYNCED (ordinal 0). + // No sync can be in progress at app startup, so any SYNCING record is orphaned. + database.benDao.resetSyncingToUnsynced() + database.cbacDao.resetSyncingToUnsynced() + database.pmsmaDao.resetSyncingToUnsynced() + database.deliveryOutcomeDao.resetSyncingToUnsynced() + database.pncDao.resetSyncingToUnsynced() + database.infantRegDao.resetSyncingToUnsynced() + database.vaccineDao.resetSyncingToUnsynced() + database.aesDao.resetSyncingToUnsynced() + database.adolescentHealthDao.resetSyncingToUnsynced() + database.filariaDao.resetSyncingToUnsynced() + database.kalaAzarDao.resetSyncingToUnsynced() + database.maaMeetingDao.resetSyncingToUnsynced() + database.saasBahuSammelanDao.resetSyncingToUnsynced() + database.uwinDao.resetSyncingToUnsynced() + + // Multi-table DAOs + database.ecrDao.resetRegSyncingToUnsynced() + database.ecrDao.resetTrackingSyncingToUnsynced() + database.maternalHealthDao.resetPwrSyncingToUnsynced() + database.maternalHealthDao.resetAncSyncingToUnsynced() + database.leprosyDao.resetScreeningSyncingToUnsynced() + database.leprosyDao.resetFollowUpSyncingToUnsynced() + database.malariaDao.resetScreeningSyncingToUnsynced() + database.malariaDao.resetConfirmedSyncingToUnsynced() + database.tbDao.resetScreeningSyncingToUnsynced() + database.tbDao.resetSuspectedSyncingToUnsynced() + database.tbDao.resetConfirmedSyncingToUnsynced() + database.hrpDao.resetPregnantAssessSyncingToUnsynced() + database.hrpDao.resetPregnantTrackSyncingToUnsynced() + database.hrpDao.resetNonPregnantAssessSyncingToUnsynced() + database.hrpDao.resetNonPregnantTrackSyncingToUnsynced() + database.hrpDao.resetMicroBirthPlanSyncingToUnsynced() + database.vlfDao.resetVhndSyncingToUnsynced() + database.vlfDao.resetVhncSyncingToUnsynced() + database.vlfDao.resetPhcSyncingToUnsynced() + database.vlfDao.resetAhdSyncingToUnsynced() + database.vlfDao.resetDewormingSyncingToUnsynced() + database.vlfDao.resetPulsePolioSyncingToUnsynced() + database.vlfDao.resetOrsSyncingToUnsynced() + database.vlfDao.resetFilariaMdaSyncingToUnsynced() + + Timber.d("Recovered orphaned SYNCING states at startup") + } } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/AESMemberListAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/AESMemberListAdapter.kt new file mode 100644 index 000000000..23c63e758 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/AESMemberListAdapter.kt @@ -0,0 +1,106 @@ +package org.piramalswasthya.sakhi.adapters + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.databinding.RvItemAesMembersListBinding +import org.piramalswasthya.sakhi.model.BenWithAESScreeningDomain + +class AESMemberListAdapter( + private val clickListener: ClickListener? = null +) : + ListAdapter + (BenDiffUtilCallBack) { + private object BenDiffUtilCallBack : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: BenWithAESScreeningDomain, + newItem: BenWithAESScreeningDomain + ) = oldItem.ben.benId == newItem.ben.benId + + override fun areContentsTheSame( + oldItem: BenWithAESScreeningDomain, + newItem: BenWithAESScreeningDomain + ) = oldItem == newItem + + } + + class BenViewHolder private constructor(private val binding: RvItemAesMembersListBinding) : + RecyclerView.ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): BenViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = RvItemAesMembersListBinding.inflate(layoutInflater, parent, false) + return BenViewHolder(binding) + } + } + + fun bind( + item: BenWithAESScreeningDomain, + clickListener: ClickListener?, + ) { + binding.benWithAes = item + binding.ivSyncState.visibility = if (item.aes == null) View.GONE else View.VISIBLE + + if (item.ben.spouseName == "Not Available" && item.ben.fatherName == "Not Available") { + binding.father = true + binding.husband = false + binding.spouse = false + } else { + if (item.ben.gender == "MALE") { + binding.father = true + binding.husband = false + binding.spouse = false + } else if (item.ben.gender == "FEMALE") { + if (item.ben.ageInt > 15) { + binding.father = + item.ben.fatherName != "Not Available" && item.ben.spouseName == "Not Available" + binding.husband = item.ben.spouseName != "Not Available" + binding.spouse = false + } else { + binding.father = true + binding.husband = false + binding.spouse = false + } + } else { + binding.father = + item.ben.fatherName != "Not Available" && item.ben.spouseName == "Not Available" + binding.spouse = item.ben.spouseName != "Not Available" + binding.husband = false + } + } + + binding.btnFormTb.visibility = if (item.aes == null && item.ben.isDeath) View.INVISIBLE else View.VISIBLE + binding.btnFormTb.text = if (item.aes == null) binding.root.resources.getString(R.string.register) else binding.root.resources.getString(R.string.view) + binding.btnFormTb.setBackgroundColor(binding.root.resources.getColor(if (item.aes == null) android.R.color.holo_red_dark else android.R.color.holo_green_dark)) + binding.clickListener = clickListener + + binding.executePendingBindings() + + } + + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ) = + BenViewHolder.from(parent) + + override fun onBindViewHolder(holder: BenViewHolder, position: Int) { + holder.bind(getItem(position), clickListener) + } + + + class ClickListener( + private val clickedForm: ((hhId: Long, benId: Long) -> Unit)? = null + + ) { + fun onClickForm(item: BenWithAESScreeningDomain) = + clickedForm?.let { it(item.ben.hhId, item.ben.benId) } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/AHDAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/AHDAdapter.kt new file mode 100644 index 000000000..ac98fd50c --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/AHDAdapter.kt @@ -0,0 +1,55 @@ +package org.piramalswasthya.sakhi.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.databinding.LayoutAhdItemBinding +import org.piramalswasthya.sakhi.model.AHDCache + +class AHDAdapter( + private val clickListener: AHDClickListener? = null, +) : ListAdapter(BenDiffUtilCallBack) { + + private object BenDiffUtilCallBack : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: AHDCache, + newItem: AHDCache + ) = oldItem.id == newItem.id + + override fun areContentsTheSame( + oldItem: AHDCache, + newItem: AHDCache + ) = oldItem == newItem + } + + class AHDViewHolder private constructor(private val binding: LayoutAhdItemBinding) : + RecyclerView.ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): AHDViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = LayoutAhdItemBinding.inflate(layoutInflater, parent, false) + return AHDViewHolder(binding) + } + } + + fun bind(item: AHDCache, clickListener: AHDClickListener?) { + binding.ahd = item + binding.clickListener = clickListener + binding.executePendingBindings() + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AHDViewHolder { + return AHDViewHolder.from(parent) + } + + override fun onBindViewHolder(holder: AHDViewHolder, position: Int) { + holder.bind(getItem(position), clickListener) + } + + class AHDClickListener(val clickListener: (id: Int) -> Unit) { + fun onClick(id: Int) = clickListener(id) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/AadhaarConsentAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/AadhaarConsentAdapter.kt new file mode 100644 index 000000000..8ca1c515b --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/AadhaarConsentAdapter.kt @@ -0,0 +1,67 @@ +package org.piramalswasthya.sakhi.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.databinding.RvCardAbhaConcentBinding +import org.piramalswasthya.sakhi.model.AadhaarConsentModel + +class AadhaarConsentAdapter(private val clickListener: ConsentClickListener) : + ListAdapter( + MyDiffUtilCallBack + ) { + private object MyDiffUtilCallBack : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: AadhaarConsentModel, newItem: AadhaarConsentModel + ) = oldItem.title == newItem.title + + override fun areContentsTheSame( + oldItem: AadhaarConsentModel, newItem: AadhaarConsentModel + ) = oldItem == newItem + + } + + class AadhaarConsentViewHolder private constructor(private val binding: RvCardAbhaConcentBinding) : + RecyclerView.ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): AadhaarConsentViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = RvCardAbhaConcentBinding.inflate(layoutInflater, parent, false) + return AadhaarConsentViewHolder(binding) + } + } + + fun bind( + item: AadhaarConsentModel, clickListener: ConsentClickListener + ) { + binding.item = item + binding.clickListener = clickListener + binding.executePendingBindings() + + binding.checkBox.setOnCheckedChangeListener(null) + binding.checkBox.isChecked=item.checked + binding.checkBox.setOnCheckedChangeListener { _, isChecked -> + item.checked=isChecked + if (isChecked){ + clickListener.onClicked(item.apply { + checked = true },position) + }else{ + clickListener.onClicked(item.apply { checked = false },position) + } + } + + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int)= AadhaarConsentViewHolder.from(parent) + + override fun onBindViewHolder(holder: AadhaarConsentViewHolder, position: Int) { + holder.bind(getItem(position), clickListener) + } + + class ConsentClickListener(val selectedListener: (selectedConsent: AadhaarConsentModel,position:Int) -> Unit) { + fun onClicked(selectedConsent: AadhaarConsentModel,position:Int) = selectedListener(selectedConsent,position) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/AdolescentHealthListAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/AdolescentHealthListAdapter.kt new file mode 100644 index 000000000..a7dd21d51 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/AdolescentHealthListAdapter.kt @@ -0,0 +1,142 @@ +package org.piramalswasthya.sakhi.adapters + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.databinding.RvItemAdolscentHealthListBinding +import org.piramalswasthya.sakhi.model.BenWithAdolescentDomain + + +class AdolescentHealthListAdapter( + private val clickListener: ClickListener? = null, + private val showBeneficiaries: Boolean = false, + private val showRegistrationDate: Boolean = false, + private val showSyncIcon: Boolean = false, + private val showAbha: Boolean = false, + private val role: Int? = 0 +) : + ListAdapter(BenDiffUtilCallBack) { + private object BenDiffUtilCallBack : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: BenWithAdolescentDomain, newItem: BenWithAdolescentDomain + ) = oldItem.ben.benId == newItem.ben.benId + + override fun areContentsTheSame( + oldItem: BenWithAdolescentDomain, newItem: BenWithAdolescentDomain + ) = oldItem == newItem + + } + + class BenViewHolder private constructor(private val binding: RvItemAdolscentHealthListBinding) : + RecyclerView.ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): BenViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = RvItemAdolscentHealthListBinding.inflate(layoutInflater, parent, false) + return BenViewHolder(binding) + } + } + + fun bind( + item: BenWithAdolescentDomain, + clickListener: ClickListener?, + showAbha: Boolean, + showSyncIcon: Boolean, + showRegistrationDate: Boolean, + showBeneficiaries: Boolean, role: Int? + ) { + binding.ivSyncState.visibility = if (item.adolescent == null) View.INVISIBLE else View.VISIBLE + + binding.ben = item + binding.clickListener = clickListener + binding.showAbha = showAbha + binding.showRegistrationDate = showRegistrationDate + binding.registrationDate.visibility = + if (showRegistrationDate) View.VISIBLE else View.INVISIBLE + binding.blankSpace.visibility = + if (showRegistrationDate) View.VISIBLE else View.INVISIBLE + binding.hasAbha = !item.ben.abhaId.isNullOrEmpty() + binding.role = role + + if (item.ben.gender == "MALE") { + binding.btnFormTb.visibility = View.GONE + } else { + binding.btnFormTb.visibility = View.VISIBLE + } + + if (showBeneficiaries) { + if (item.ben.spouseName == "Not Available" && item.ben.fatherName == "Not Available") { + binding.father = true + binding.husband = false + binding.spouse = false + + } else { + if (item.ben.gender == "MALE") { + binding.father = true + binding.husband = false + binding.spouse = false + } else if (item.ben.gender == "FEMALE") { + if (item.ben.ageInt > 15) { + binding.father = + item.ben.fatherName != "Not Available" && item.ben.spouseName == "Not Available" + binding.husband = item.ben.spouseName != "Not Available" + binding.spouse = false + } else { + binding.father = true + binding.husband = false + binding.spouse = false + } + + } else { + binding.father = + item.ben.fatherName != "Not Available" && item.ben.spouseName == "Not Available" + binding.spouse = item.ben.spouseName != "Not Available" + binding.husband = false + } + } + } else { + binding.father = false + binding.husband = false + binding.spouse = false + } + binding.btnFormTb.text = if (item.adolescent == null) binding.root.context.getString(R.string.register) else binding.root.context.getString(R.string.view) + binding.btnFormTb.setBackgroundColor(binding.root.resources.getColor(if (item.adolescent == null) android.R.color.holo_red_dark else android.R.color.holo_green_dark)) + binding.clickListener = clickListener + + binding.executePendingBindings() + + } + } + + + + override fun onCreateViewHolder( + parent: ViewGroup, viewType: Int + ) = BenViewHolder.from(parent) + + override fun onBindViewHolder(holder: BenViewHolder, position: Int) { + holder.bind( + getItem(position), + clickListener, + showAbha, + showSyncIcon, + showRegistrationDate, + showBeneficiaries, + role + ) + } + + + class ClickListener( + private val clickedForm: ((hhId: Long, benId: Long) -> Unit)? = null + + ) { + fun onClickForm(item: BenWithAdolescentDomain) = + clickedForm?.let { it(item.ben.hhId, item.ben.benId) } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/AncAbortionListAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/AncAbortionListAdapter.kt new file mode 100644 index 000000000..bf752014e --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/AncAbortionListAdapter.kt @@ -0,0 +1,72 @@ +package org.piramalswasthya.sakhi.adapters + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.databinding.RvItemAbortionBinding +import org.piramalswasthya.sakhi.model.BenWithAncListDomain + +class AncAbortionListAdapter( + private val clickListener: AbortionListClickListener? = null +) : ListAdapter(DiffCallback) { + + private object DiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: BenWithAncListDomain, newItem: BenWithAncListDomain + ) = oldItem.ben.benId == newItem.ben.benId + + override fun areContentsTheSame( + oldItem: BenWithAncListDomain, newItem: BenWithAncListDomain + ) = oldItem == newItem + } + + class AbortionViewHolder private constructor( + private val binding: RvItemAbortionBinding + ) : RecyclerView.ViewHolder(binding.root) { + + fun bind(item: BenWithAncListDomain, clickListener: AbortionListClickListener?) { + binding.visit = item + binding.clickListener = clickListener + + binding.btnPmsma.text = if (item.isAbortionFormFilled) binding.root.resources.getString( + R.string.view) else binding.root.resources.getString( + R.string.add) + binding.btnPmsma.setBackgroundColor( + binding.root.resources.getColor( + if (item.isAbortionFormFilled) android.R.color.holo_green_dark + else android.R.color.holo_red_dark + ) + ) + + binding.btnViewVisits.visibility =View.INVISIBLE + binding.executePendingBindings() + } + + companion object { + fun from(parent: ViewGroup): AbortionViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = RvItemAbortionBinding.inflate(layoutInflater, parent, false) + return AbortionViewHolder(binding) + } + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = + AbortionViewHolder.from(parent) + + override fun onBindViewHolder(holder: AbortionViewHolder, position: Int) { + holder.bind(getItem(position), clickListener) + } + + class AbortionListClickListener( + private val showVisits: (benId: Long) -> Unit, + private val addVisit: (benId: Long) -> Unit, + ) { + fun showVisits(item: BenWithAncListDomain) = showVisits(item.ben.benId) + fun addVisit(item: BenWithAncListDomain) = addVisit(item.ben.benId) + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/AncHomeVisitAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/AncHomeVisitAdapter.kt new file mode 100644 index 000000000..2bd696284 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/AncHomeVisitAdapter.kt @@ -0,0 +1,62 @@ +package org.piramalswasthya.sakhi.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.databinding.ItemAncHomeVisitBinding +import org.piramalswasthya.sakhi.model.HomeVisitDomain + +class AncHomeVisitAdapter( + private val clickListener: HomeVisitClickListener? = null +) : ListAdapter(MyDiffUtilCallBack) { + + private object MyDiffUtilCallBack : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: HomeVisitDomain, newItem: HomeVisitDomain) = + oldItem.id == newItem.id + + override fun areContentsTheSame(oldItem: HomeVisitDomain, newItem: HomeVisitDomain) = + oldItem == newItem + } + + class HomeVisitViewHolder private constructor(private val binding: ItemAncHomeVisitBinding) : + RecyclerView.ViewHolder(binding.root) { + + companion object { + fun from(parent: ViewGroup): HomeVisitViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = ItemAncHomeVisitBinding.inflate(layoutInflater, parent, false) + return HomeVisitViewHolder(binding) + } + } + + fun bind(item: HomeVisitDomain, clickListener: HomeVisitClickListener?) { + binding.visit = item + binding.clickListener = clickListener + binding.executePendingBindings() + + + + + binding.btnView.setOnClickListener { + clickListener?.onViewClick(item) + } + + binding.executePendingBindings() + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = + HomeVisitViewHolder.from(parent) + + override fun onBindViewHolder(holder: HomeVisitViewHolder, position: Int) { + holder.bind(getItem(position), clickListener) + } + + class HomeVisitClickListener( + private val onViewClick: (HomeVisitDomain) -> Unit + ) { + fun onViewClick(item: HomeVisitDomain) = onViewClick.invoke(item) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/AncVisitAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/AncVisitAdapter.kt index 97669204b..47fd52ac3 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/adapters/AncVisitAdapter.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/AncVisitAdapter.kt @@ -1,21 +1,24 @@ package org.piramalswasthya.sakhi.adapters import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao import org.piramalswasthya.sakhi.databinding.RvItemPregnancyAncBinding import org.piramalswasthya.sakhi.model.AncStatus +import org.piramalswasthya.sakhi.utils.RoleConstants -class AncVisitAdapter(private val clickListener: AncVisitClickListener) : +class AncVisitAdapter(private val clickListener: AncVisitClickListener, private val pref: PreferenceDao? = null) : ListAdapter( MyDiffUtilCallBack ) { private object MyDiffUtilCallBack : DiffUtil.ItemCallback() { override fun areItemsTheSame( oldItem: AncStatus, newItem: AncStatus - ) = oldItem.benId == newItem.benId + ) = oldItem.benId == newItem.benId && oldItem.visitNumber == newItem.visitNumber override fun areContentsTheSame( oldItem: AncStatus, newItem: AncStatus @@ -34,12 +37,23 @@ class AncVisitAdapter(private val clickListener: AncVisitClickListener) : } fun bind( - item: AncStatus, clickListener: AncVisitClickListener + item: AncStatus, clickListener: AncVisitClickListener, pref: PreferenceDao?, isLastItem: Boolean ) { + + if (pref?.getLoggedInUser()?.role.equals(RoleConstants.ROLE_ASHA_SUPERVISOR, true)) { + binding.btnView.visibility = View.GONE + } else { + binding.btnView.visibility = View.VISIBLE + } + binding.visit = item binding.clickListener = clickListener binding.executePendingBindings() + binding.btnView.setOnClickListener { + clickListener.onClickedVisit(item, isLastItem) + } + } } @@ -48,16 +62,16 @@ class AncVisitAdapter(private val clickListener: AncVisitClickListener) : ) = AncViewHolder.from(parent) override fun onBindViewHolder(holder: AncViewHolder, position: Int) { - holder.bind(getItem(position), clickListener) + val isLastItem = position == itemCount - 1 + holder.bind(getItem(position), clickListener, pref, isLastItem) } class AncVisitClickListener( - private val clickedForm: (benId: Long, visitNumber: Int) -> Unit, - + private val clickedForm: (benId: Long, visitNumber: Int, isLast: Boolean) -> Unit ) { - fun onClickedVisit(item: AncStatus) = clickedForm( - item.benId, item.visitNumber + fun onClickedVisit(item: AncStatus, isLast: Boolean) = clickedForm( + item.benId, item.visitNumber, isLast ) } diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/AncVisitListAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/AncVisitListAdapter.kt index 561b55d30..6301e340c 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/adapters/AncVisitListAdapter.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/AncVisitListAdapter.kt @@ -6,26 +6,47 @@ import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.BuildConfig +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao import org.piramalswasthya.sakhi.databinding.RvItemPregnancyVisitBinding +import org.piramalswasthya.sakhi.model.BenBasicDomain import org.piramalswasthya.sakhi.model.BenWithAncListDomain +import org.piramalswasthya.sakhi.utils.HelperUtil +import org.piramalswasthya.sakhi.utils.RoleConstants +import java.util.concurrent.TimeUnit -class AncVisitListAdapter(private val clickListener: PregnancyVisitClickListener? = null) : - ListAdapter( - MyDiffUtilCallBack - ) { +private fun View.setVisibleIf(condition: Boolean) { + this.isEnabled = condition + this.alpha = if (condition) 1.0f else 0.5f +} +private fun View.setVisibleIfViewVisit(condition: Boolean) { + this.visibility = if (condition) View.VISIBLE else View.INVISIBLE +} + +class AncVisitListAdapter( + private val clickListener: PregnancyVisitClickListener? = null, + private val showCall: Boolean = false, + private val pref: PreferenceDao? = null, + private val isHighRiskMode: Boolean = false, + private val hidePmsma: Boolean +) : ListAdapter( + MyDiffUtilCallBack +) { private object MyDiffUtilCallBack : DiffUtil.ItemCallback() { - override fun areItemsTheSame( - oldItem: BenWithAncListDomain, newItem: BenWithAncListDomain - ) = oldItem.ben.benId == newItem.ben.benId + override fun areItemsTheSame(oldItem: BenWithAncListDomain, newItem: BenWithAncListDomain) = + oldItem.ben.benId == newItem.ben.benId override fun areContentsTheSame( - oldItem: BenWithAncListDomain, newItem: BenWithAncListDomain - ) = oldItem == newItem - + oldItem: BenWithAncListDomain, + newItem: BenWithAncListDomain + ) = + oldItem == newItem } class PregnancyVisitViewHolder private constructor(private val binding: RvItemPregnancyVisitBinding) : RecyclerView.ViewHolder(binding.root) { + companion object { fun from(parent: ViewGroup): PregnancyVisitViewHolder { val layoutInflater = LayoutInflater.from(parent.context) @@ -35,46 +56,114 @@ class AncVisitListAdapter(private val clickListener: PregnancyVisitClickListener } fun bind( - item: BenWithAncListDomain, clickListener: PregnancyVisitClickListener? + item: BenWithAncListDomain, + clickListener: PregnancyVisitClickListener?, + showCall: Boolean, + pref: PreferenceDao?, + isHighRiskMode: Boolean, + hidePmsma: Boolean ) { + + if (pref?.getLoggedInUser()?.role.equals(RoleConstants.ROLE_ASHA_SUPERVISOR, true)) { + binding.btnPmsma.visibility = View.INVISIBLE + binding.btnAddAnc.visibility = View.INVISIBLE + binding.btnAddHomeVisit.visibility = View.INVISIBLE + } else { + binding.btnPmsma.visibility = View.VISIBLE + binding.btnAddAnc.visibility = View.VISIBLE + binding.btnAddHomeVisit.visibility = View.VISIBLE + } + + if (item.ancDate == 0L) { + binding.ivFollowState.visibility = View.GONE + binding.llBenPwrTrackingDetails4.visibility = View.GONE + } else if (item.ancDate < System.currentTimeMillis() - TimeUnit.DAYS.toMillis(90) && + item.ancDate > System.currentTimeMillis() - TimeUnit.DAYS.toMillis(365) + ) { + binding.ivFollowState.visibility = View.VISIBLE + binding.llBenPwrTrackingDetails4.visibility = View.VISIBLE + binding.benVisitDate.text = HelperUtil.getDateStringFromLongStraight(item.ancDate) + } else { + binding.ivFollowState.visibility = View.GONE + binding.llBenPwrTrackingDetails4.visibility = View.VISIBLE + binding.benVisitDate.text = HelperUtil.getDateStringFromLongStraight(item.ancDate) + } + + binding.btnAddHomeVisit.visibility = + if (item.showAddHomeVisit) View.VISIBLE else View.GONE + + binding.btnViewHomeVisit.visibility = + if (item.showViewHomeVisit) View.VISIBLE else View.GONE + + if (!BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true)) + { + binding.btnAddHomeVisit.visibility = View.GONE + binding.btnViewHomeVisit.visibility = View.GONE + + + } + + if (showCall) { + binding.ivCall.visibility = View.VISIBLE + } else { + binding.ivCall.visibility = View.GONE + } + binding.visit = item - binding.btnAddAnc.visibility = if (item.showAddAnc) View.VISIBLE else View.INVISIBLE - binding.btnPmsma.visibility = if (item.pmsmaFillable) View.VISIBLE else View.INVISIBLE - binding.btnPmsma.text = if (item.hasPmsma) "View PMSMA" else "Add PMSMA" - binding.btnPmsma.setBackgroundColor(binding.root.resources.getColor(if (item.hasPmsma) android.R.color.holo_green_dark else android.R.color.holo_red_dark)) - binding.btnViewVisits.visibility = - if (item.anc.isEmpty()) View.INVISIBLE else View.VISIBLE + binding.llAnc.visibility = if (!isHighRiskMode) View.VISIBLE else View.GONE + binding.btnAddAnc.setVisibleIf(item.showAddAnc) + binding.btnPmsma.setVisibleIf(item.showAddAnc) + binding.btnViewVisits.setVisibleIfViewVisit(item.anc.isNotEmpty()) + binding.btnViewVisitsPmsma.setVisibleIfViewVisit(item.anc.isNotEmpty()) + if (hidePmsma) { + binding.btnPmsma.visibility = View.GONE + binding.btnViewVisitsPmsma.visibility = View.GONE + } binding.clickListener = clickListener binding.executePendingBindings() - } } - override fun onCreateViewHolder( - parent: ViewGroup, viewType: Int - ) = PregnancyVisitViewHolder.from(parent) + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = + PregnancyVisitViewHolder.from(parent) override fun onBindViewHolder(holder: PregnancyVisitViewHolder, position: Int) { - holder.bind(getItem(position), clickListener) + holder.bind(getItem(position), clickListener, showCall, pref, isHighRiskMode, hidePmsma) } - class PregnancyVisitClickListener( private val showVisits: (benId: Long) -> Unit, - private val addVisit: (benId: Long, visitNumber: Int) -> Unit, - private val pmsma: (benId: Long, hhId: Long) -> Unit, + private val showPmsmaVisits: (benId: Long, hhId: Long) -> Unit, + private val addVisit: (benId: Long, hhId: Long, visitNumber: Int) -> Unit, + private val pmsma: (benId: Long, hhId: Long, visitNumber: Int) -> Unit, + private val callBen: (ben: BenWithAncListDomain) -> Unit, + private val addHomeVisit: ((benId: Long) -> Unit)? = null, + private val showHomeVisit : ((benId: Long) -> Unit)? = null + ) { + fun showVisits(item: BenWithAncListDomain) = showVisits(item.ben.benId) + fun showPmsmaVisits(item: BenWithAncListDomain) = + showPmsmaVisits(item.ben.benId, item.ben.hhId) - ) { - fun showVisits(item: BenWithAncListDomain) = showVisits( + fun addVisit(item: BenWithAncListDomain) = addVisit( item.ben.benId, + item.ben.hhId, + if (item.anc.isEmpty()) 1 else item.anc.maxOf { it.visitNumber } + 1 ) - fun addVisit(item: BenWithAncListDomain) = addVisit(item.ben.benId, - if (item.anc.isEmpty()) 1 else item.anc.maxOf { it.visitNumber } + 1) - fun pmsma(item: BenWithAncListDomain) = pmsma( - item.ben.benId, item.ben.hhId + item.ben.benId, + item.ben.hhId, + if (item.pmsma.isEmpty()) 1 else item.pmsma.maxOf { it.visitNumber } + 1 ) - } -} \ No newline at end of file + fun onClickedForCall(item: BenWithAncListDomain) = callBen(item) + fun addHomeVisit(item: BenWithAncListDomain) { + addHomeVisit?.invoke( + item.ben.benId, + + ) + } + fun showHomeVisit(item: BenWithAncListDomain) = showHomeVisit?.invoke(item.ben.benId) + + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/BenChildImmunizationListAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/BenChildImmunizationListAdapter.kt new file mode 100644 index 000000000..7488467bb --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/BenChildImmunizationListAdapter.kt @@ -0,0 +1,86 @@ +package org.piramalswasthya.sakhi.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.databinding.RvItemBenChildImmunizationBinding +import org.piramalswasthya.sakhi.model.BenBasicDomain +import org.piramalswasthya.sakhi.model.ImmunizationDetailsDomain +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Locale + +class BenChildImmunizationListAdapter( + private val clickListener: VaccinesClickListener? = null +) : ListAdapter( + BenDiffUtilCallBack +) { + private object BenDiffUtilCallBack : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: ImmunizationDetailsDomain, newItem: ImmunizationDetailsDomain + ) = oldItem.ben.benId == newItem.ben.benId + + override fun areContentsTheSame( + oldItem: ImmunizationDetailsDomain, newItem: ImmunizationDetailsDomain + ) = oldItem == newItem + + } + + class BenVaccineViewHolder private constructor(private val binding: RvItemBenChildImmunizationBinding) : + RecyclerView.ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): BenVaccineViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = RvItemBenChildImmunizationBinding.inflate(layoutInflater, parent, false) + return BenVaccineViewHolder(binding) + } + } + + fun bind( + item: ImmunizationDetailsDomain, clickListener: VaccinesClickListener? + ) { + + binding.temp = item.ben + binding.clickListener = clickListener + + var dob = getDateFromLong(item.ben.dob) + binding.tvDob.text = dob + + binding.executePendingBindings() + + } + + fun getDateFromLong(dateLong: Long): String? { + if (dateLong == 0L) return null + val cal = Calendar.getInstance() + cal.timeInMillis = dateLong + val f = SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH) + return f.format(cal.time) + } + } + + + + override fun onCreateViewHolder( + parent: ViewGroup, viewType: Int + ): BenVaccineViewHolder = BenVaccineViewHolder.from(parent) + + override fun onBindViewHolder(holder: BenVaccineViewHolder, position: Int) { + holder.bind(getItem(position), clickListener) + + } + + + class VaccinesClickListener( + private val clickedVaccine: (benId: Long) -> Unit, + + ) { + fun onClickedBen(item: BenBasicDomain) = clickedVaccine( + item.benId + ) + + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/BenListAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/BenListAdapter.kt index 9be771ac3..e034d597e 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/adapters/BenListAdapter.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/BenListAdapter.kt @@ -3,11 +3,19 @@ package org.piramalswasthya.sakhi.adapters import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.content.ContextCompat +import androidx.fragment.app.FragmentActivity import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao import org.piramalswasthya.sakhi.databinding.RvItemBenBinding +import org.piramalswasthya.sakhi.helpers.getDateFromLong +import org.piramalswasthya.sakhi.helpers.getPatientTypeByAge import org.piramalswasthya.sakhi.model.BenBasicDomain +import org.piramalswasthya.sakhi.model.Gender +import org.piramalswasthya.sakhi.utils.RoleConstants class BenListAdapter( @@ -16,10 +24,16 @@ class BenListAdapter( private val showRegistrationDate: Boolean = false, private val showSyncIcon: Boolean = false, private val showAbha: Boolean = false, - private val role: Int? = 0 + private val showCall: Boolean = false, + private val role: Int? = 0, + private val pref: PreferenceDao? = null, + var context: FragmentActivity, + private val isSoftDeleteEnabled: Boolean = false, + private val showActionButtons: Boolean = true, ) : ListAdapter(BenDiffUtilCallBack) { - private object BenDiffUtilCallBack : DiffUtil.ItemCallback() { + + object BenDiffUtilCallBack : DiffUtil.ItemCallback() { override fun areItemsTheSame( oldItem: BenBasicDomain, newItem: BenBasicDomain ) = oldItem.benId == newItem.benId @@ -46,8 +60,21 @@ class BenListAdapter( showAbha: Boolean, showSyncIcon: Boolean, showRegistrationDate: Boolean, - showBeneficiaries: Boolean, role: Int? + showBeneficiaries: Boolean, role: Int?, + showCall: Boolean, + isSoftDeleteEnabled: Boolean, + pref: PreferenceDao?, + context: FragmentActivity, + benIdList: List, + childCountMap: Map = emptyMap(), + showActionButtons: Boolean = true ) { + + if (pref?.getLoggedInUser()?.role.equals(RoleConstants.ROLE_ASHA_SUPERVISOR, true)) { + binding.btnAbha.visibility = View.GONE + } else { + binding.btnAbha.visibility = View.VISIBLE + } if (!showSyncIcon) item.syncState = null binding.ben = item binding.clickListener = clickListener @@ -60,6 +87,147 @@ class BenListAdapter( binding.hasAbha = !item.abhaId.isNullOrEmpty() binding.role = role + if (showCall) { + binding.ivCall.visibility = View.VISIBLE + } else { + binding.ivCall.visibility = View.GONE + } + + val isMatched = benIdList.contains(item.benId) + binding.isMatched = isMatched + + binding.btnAbove30.text = if (isMatched) { + binding.root.context.getString(R.string.view_edit_eye_surgery) + } else { + binding.root.context.getString(R.string.add_eye_surgery) + } + + binding.executePendingBindings() + + var gender = item.gender.toString() + + if (item.relToHeadId == 19) { + binding.HOF.visibility = View.VISIBLE + } else { + binding.HOF.visibility = View.GONE + } + + if (item.dob != null) { + val type = getPatientTypeByAge(getDateFromLong(item.dob)) + when (type) { + + "new_born_baby" -> { + binding.ivHhLogo.setImageResource(R.drawable.ic_icon_baby) + } + + "infant" -> { + binding.ivHhLogo.setImageResource(R.drawable.ic_infant) + } + + "child", "adolescence" -> { + when (gender) { + Gender.MALE.name -> { + binding.ivHhLogo.setImageResource(R.drawable.ic_icon_boy_ben) + } + + Gender.FEMALE.name -> { + binding.ivHhLogo.setImageResource(R.drawable.ic_girl) + } + + else -> { + // Intentionally left blank (no icon change) + } + } + } + + "adult" -> { + when (gender) { + Gender.MALE.name -> { + binding.ivHhLogo.setImageResource(R.drawable.ic_males) + } + + Gender.FEMALE.name -> { + binding.ivHhLogo.setImageResource(R.drawable.ic_icon_female_2) + } + + else -> { + binding.ivHhLogo.setImageResource(R.drawable.ic_unisex) + } + } + } + } + + } + + val effectiveChildCount = childCountMap[item.benId] ?: item.noOfChildren + + when { + item.gender == "MALE" && !item.isSpouseAdded && item.isMarried -> { + binding.btnAddSpouse.visibility = View.VISIBLE + binding.btnAddChildren.visibility = View.INVISIBLE + binding.llAddSpouseBtn.visibility = View.VISIBLE + binding.btnAddSpouse.text = context.getString(R.string.add_wife) + binding.btnAddSpouse.setOnClickListener { + clickListener?.onClickedWifeBen(item) + } + } + + // FEMALE + married + no spouse: show Add Husband only + item.gender == "FEMALE" && !item.isSpouseAdded && item.isMarried -> { + binding.btnAddSpouse.visibility = View.VISIBLE + binding.btnAddChildren.visibility = View.INVISIBLE + binding.llAddSpouseBtn.visibility = View.VISIBLE + binding.btnAddSpouse.text = context.getString(R.string.add_husband) + binding.btnAddSpouse.setOnClickListener { + clickListener?.onClickedHusbandBen(item) + } + } + + // FEMALE + married + spouse added + 0 children + doYouHavechildren: Register Children + item.gender == "FEMALE" && + item.isMarried && + item.isSpouseAdded && + item.doYouHavechildren && + effectiveChildCount == 0 -> { + + binding.btnAddChildren.visibility = View.VISIBLE + binding.btnAddChildren.text = context.getString(R.string.add_children) + binding.btnAddSpouse.visibility = View.GONE + binding.llAddSpouseBtn.visibility = View.VISIBLE + binding.btnAddChildren.setOnClickListener { + clickListener?.onClickChildBen(item) + } + } + + // FEMALE + married + spouse added + >0 children: View Children + item.gender == "FEMALE" && + item.isMarried && + item.isSpouseAdded && + effectiveChildCount > 0 -> { + + binding.btnAddChildren.visibility = View.VISIBLE + binding.btnAddChildren.text = context.getString(R.string.view_children) + binding.btnAddSpouse.visibility = View.GONE + binding.llAddSpouseBtn.visibility = View.VISIBLE + binding.btnAddChildren.setOnClickListener { + clickListener?.onClickChildBen(item) + } + } + + else -> { + binding.btnAddSpouse.visibility = View.GONE + binding.btnAddChildren.visibility = View.INVISIBLE + binding.llAddSpouseBtn.visibility = View.GONE + } + } + + if (!showActionButtons) { + binding.btnAbove30.visibility = View.GONE + binding.btnAddSpouse.visibility = View.GONE + binding.btnAddChildren.visibility = View.GONE + binding.llAddSpouseBtn.visibility = View.GONE + } + if (showBeneficiaries) { if (item.spouseName == "Not Available" && item.fatherName == "Not Available") { binding.father = true @@ -93,8 +261,76 @@ class BenListAdapter( binding.husband = false binding.spouse = false } - binding.executePendingBindings() + if (item.isDeath) { + binding.contentLayout.setBackgroundColor( + ContextCompat.getColor( + binding.contentLayout.context, + R.color.md_theme_dark_outline + ) + ) + binding.ivCall.visibility = View.GONE + binding.ivSyncState.visibility = View.GONE + binding.llAddSpouseBtn.visibility = View.GONE + binding.btnAbha.visibility = View.GONE + binding.ivSoftDelete.visibility = View.GONE + } else { + binding.contentLayout.setBackgroundColor( + ContextCompat.getColor( + binding.contentLayout.context, + R.color.md_theme_light_primary + ) + ) + } + + if (isSoftDeleteEnabled) { + binding.ivSoftDelete.visibility = View.VISIBLE + + if (item.isDeactivate) { + binding.contentLayout.setBackgroundColor( + ContextCompat.getColor( + binding.contentLayout.context, + R.color.Quartenary + ) + ) + binding.ivSoftDelete.visibility = View.GONE + + binding.btnAbha.visibility = View.INVISIBLE + binding.tvTitleDuplicaterecord.visibility = View.VISIBLE + binding.ivCall.visibility = View.INVISIBLE + binding.ivSyncState.visibility = View.INVISIBLE + binding.llBenDetails4.visibility = View.GONE + binding.llAddSpouseBtn.visibility = View.GONE + + + } else { + + if (!item.isDeath) { + binding.contentLayout.setBackgroundColor( + ContextCompat.getColor( + binding.contentLayout.context, + R.color.md_theme_light_primary + ) + ) + + binding.btnAbha.visibility = View.VISIBLE + binding.tvTitleDuplicaterecord.visibility = View.GONE + binding.llBenDetails4.visibility = View.VISIBLE + binding.ivCall.visibility = View.VISIBLE + binding.ivSyncState.visibility = View.VISIBLE + binding.llAddSpouseBtn.visibility = View.VISIBLE + + } else { + binding.ivSoftDelete.visibility = View.GONE + + } + } + + } else { + binding.ivSoftDelete.visibility = View.GONE + } + + binding.executePendingBindings() } } @@ -102,6 +338,8 @@ class BenListAdapter( parent: ViewGroup, viewType: Int ) = BenViewHolder.from(parent) + private val benIds = mutableListOf() + override fun onBindViewHolder(holder: BenViewHolder, position: Int) { holder.bind( getItem(position), @@ -110,25 +348,79 @@ class BenListAdapter( showSyncIcon, showRegistrationDate, showBeneficiaries, - role + role, + showCall, + isSoftDeleteEnabled, + pref, + context, + benIds, + showActionButtons = showActionButtons ) } + fun submitBenIds(list: List) { + val oldIds = benIds.toSet() + benIds.clear() + benIds.addAll(list) + val newIds = benIds.toSet() + val changed = (oldIds - newIds) + (newIds - oldIds) + if (changed.isNotEmpty()) { + currentList.forEachIndexed { index, item -> + if (item.benId in changed) { + notifyItemChanged(index) + } + } + } + } + class BenClickListener( - private val clickedBen: (hhId: Long, benId: Long, relToHeadId: Int) -> Unit, - private val clickedHousehold: (hhId: Long) -> Unit, - private val clickedABHA: (benId: Long, hhId: Long) -> Unit, + private val clickedBen: (item: BenBasicDomain, hhId: Long, benId: Long, relToHeadId: Int) -> Unit, + private val clickedWifeBen: (item: BenBasicDomain, hhId: Long, benId: Long, relToHeadId: Int) -> Unit, + private val clickedHusbandBen: (item: BenBasicDomain, hhId: Long, benId: Long, relToHeadId: Int) -> Unit, + private val clickedChildben: (item: BenBasicDomain, hhId: Long, benId: Long, relToHeadId: Int) -> Unit, + private val clickedHousehold: (item: BenBasicDomain, hhId: Long) -> Unit, + private val clickedABHA: (item: BenBasicDomain, benId: Long, hhId: Long) -> Unit, + private val clickedAddAllBenBtn: (item: BenBasicDomain, benId: Long, hhId: Long, isViewMode: Boolean, isIFA: Boolean) -> Unit, + private val callBen: (ben: BenBasicDomain) -> Unit, + private val softDeleteBen: (ben: BenBasicDomain) -> Unit ) { fun onClickedBen(item: BenBasicDomain) = clickedBen( + item, item.hhId, item.benId, item.relToHeadId - 1 ) - fun onClickedHouseHold(item: BenBasicDomain) = clickedHousehold(item.hhId) - fun onClickABHA(item: BenBasicDomain) = clickedABHA(item.benId, item.hhId) - } + fun onClickedWifeBen(item: BenBasicDomain) = clickedWifeBen( + item, + item.hhId, + item.benId, + item.relToHeadId + ) + + + fun onClickedHusbandBen(item: BenBasicDomain) = clickedHusbandBen( + item, + item.hhId, + item.benId, + item.relToHeadId + ) -} \ No newline at end of file + fun onClickChildBen(item: BenBasicDomain) = clickedChildben( + item, + item.hhId, + item.benId, + item.relToHeadId + ) + + fun onClickedHouseHold(item: BenBasicDomain) = clickedHousehold(item, item.hhId) + fun onClickABHA(item: BenBasicDomain) = clickedABHA(item, item.benId, item.hhId) + fun clickedAddAllBenBtn(item: BenBasicDomain, isMatched: Boolean, isIFA: Boolean) = + clickedAddAllBenBtn(item, item.benId, item.hhId, isMatched, isIFA) + + fun onClickedForCall(item: BenBasicDomain) = callBen(item) + fun onClickSoftDeleteBen(item: BenBasicDomain) = softDeleteBen(item) + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/BenListAdapterForForm.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/BenListAdapterForForm.kt index a80bcec7c..963feda14 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/adapters/BenListAdapterForForm.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/BenListAdapterForForm.kt @@ -7,13 +7,17 @@ import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao import org.piramalswasthya.sakhi.databinding.RvItemBenWithFormBinding import org.piramalswasthya.sakhi.model.BenBasicDomainForForm +import org.piramalswasthya.sakhi.utils.RoleConstants class BenListAdapterForForm( private val clickListener: ClickListener? = null, private vararg val formButtonText: String, - private val role: Int? = 0 + private val role: Int? = 0, + private val pref: PreferenceDao? = null, + private val isGeneralForm: Boolean? = null ) : ListAdapter (BenDiffUtilCallBack) { @@ -44,8 +48,27 @@ class BenListAdapterForForm( item: BenBasicDomainForForm, clickListener: ClickListener?, vararg btnText: String, - role: Int? + role: Int?, + pref: PreferenceDao?, + isGeneralForm: Boolean? = null ) { + + binding.isGeneralForm = isGeneralForm == true + + if (pref?.getLoggedInUser()?.role.equals(RoleConstants.ROLE_ASHA_SUPERVISOR, true)) { + binding.btnForm1.visibility = View.INVISIBLE + binding.btnForm2.visibility = View.INVISIBLE + binding.btnForm3.visibility = View.INVISIBLE + } else { + binding.btnForm1.visibility = View.VISIBLE + binding.btnForm2.visibility = View.VISIBLE + binding.btnForm3.visibility = View.VISIBLE + } + + + + + binding.ben = item binding.clickListener = clickListener if (btnText.isNotEmpty()) { @@ -78,6 +101,13 @@ class BenListAdapterForForm( } binding.role = role binding.hasLmp = !item.lastMenstrualPeriod.isNullOrEmpty() + + if (isGeneralForm == true) { + binding.ivSyncState.visibility = View.GONE + binding.btnForm1.visibility = View.GONE + binding.btnForm2.visibility = View.GONE + binding.btnForm3.visibility = View.GONE + } binding.executePendingBindings() } @@ -153,7 +183,14 @@ class BenListAdapterForForm( BenViewHolder.from(parent) override fun onBindViewHolder(holder: BenViewHolder, position: Int) { - holder.bind(getItem(position), clickListener, btnText = formButtonText, role = role) + holder.bind( + getItem(position), + clickListener, + btnText = formButtonText, + role = role, + pref = pref, + isGeneralForm = isGeneralForm ?: false + ) } diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/BenPagingAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/BenPagingAdapter.kt new file mode 100644 index 000000000..f4c43011c --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/BenPagingAdapter.kt @@ -0,0 +1,82 @@ +package org.piramalswasthya.sakhi.adapters + +import android.view.ViewGroup +import androidx.fragment.app.FragmentActivity +import androidx.paging.PagingDataAdapter +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao +import org.piramalswasthya.sakhi.model.BenBasicDomain + +class BenPagingAdapter( + private val clickListener: BenListAdapter.BenClickListener? = null, + private val showBeneficiaries: Boolean = false, + private val showRegistrationDate: Boolean = false, + private val showSyncIcon: Boolean = false, + private val showAbha: Boolean = false, + private val showCall: Boolean = false, + private val role: Int? = 0, + private val pref: PreferenceDao? = null, + var context: FragmentActivity, + private val isSoftDeleteEnabled: Boolean = false, + private val showActionButtons: Boolean = true, +) : + PagingDataAdapter(BenListAdapter.BenDiffUtilCallBack) { + + private val benIds = mutableListOf() + private val childCountMap = mutableMapOf() + + override fun onCreateViewHolder( + parent: ViewGroup, viewType: Int + ) = BenListAdapter.BenViewHolder.from(parent) + + override fun onBindViewHolder(holder: BenListAdapter.BenViewHolder, position: Int) { + val item = getItem(position) ?: return + holder.bind( + item, + clickListener, + showAbha, + showSyncIcon, + showRegistrationDate, + showBeneficiaries, + role, + showCall, + isSoftDeleteEnabled, + pref, + context, + benIds, + childCountMap, + showActionButtons = showActionButtons + ) + } + + fun submitBenIds(list: List) { + val oldIds = benIds.toSet() + benIds.clear() + benIds.addAll(list) + val newIds = benIds.toSet() + val changed = (oldIds - newIds) + (newIds - oldIds) + if (changed.isNotEmpty()) { + val items = snapshot() + items.forEachIndexed { index, item -> + if (item != null && item.benId in changed) { + notifyItemChanged(index) + } + } + } + } + + fun submitChildCounts(map: Map) { + val old = childCountMap.toMap() + childCountMap.clear() + childCountMap.putAll(map) + val changed = map.entries.filter { (k, v) -> old[k] != v }.map { it.key }.toSet() + + old.entries.filter { (k, v) -> map[k] != v }.map { it.key }.toSet() + if (changed.isNotEmpty()) { + val items = snapshot() + items.forEachIndexed { index, item -> + if (item != null && item.benId in changed) { + notifyItemChanged(index) + } + } + } + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/CUFYAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/CUFYAdapter.kt new file mode 100644 index 000000000..c24a186a8 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/CUFYAdapter.kt @@ -0,0 +1,86 @@ +package org.piramalswasthya.sakhi.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.databinding.ChildrenUnderFiveYearsItemBinding +import org.piramalswasthya.sakhi.model.BenBasicDomain +import org.piramalswasthya.sakhi.ui.home_activity.child_care.children_under_five_years.CUFYListViewModel +import org.piramalswasthya.sakhi.utils.dynamicFormConstants.FormConstants + +class CUFYAdapter( + private val clickListener: ChildListClickListener, + +) : + ListAdapter(BenDiffUtilCallBack) { + private object BenDiffUtilCallBack : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: CUFYListViewModel.BenWithSamStatus, newItem: CUFYListViewModel.BenWithSamStatus + ) = oldItem.ben.benId == newItem.ben.benId + + override fun areContentsTheSame( + oldItem: CUFYListViewModel.BenWithSamStatus, newItem: CUFYListViewModel.BenWithSamStatus + ) = oldItem == newItem + + } + + class BenViewHolder private constructor(private val binding: ChildrenUnderFiveYearsItemBinding) : + RecyclerView.ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): BenViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = ChildrenUnderFiveYearsItemBinding.inflate(layoutInflater, parent, false) + return BenViewHolder(binding) + } + } + + fun bind( + item: CUFYListViewModel.BenWithSamStatus, + clickListener: ChildListClickListener + ) { + binding.ben = item.ben + binding.clickListener = clickListener + binding.samStatus = item.samStatus + binding.executePendingBindings() + + } + } + + override fun onCreateViewHolder( + parent: ViewGroup, viewType: Int + ) = BenViewHolder.from(parent) + + override fun onBindViewHolder(holder: BenViewHolder, position: Int) { + holder.bind(getItem(position), clickListener) + + } + + suspend fun updateSamStatus(benId: Long, status: String) { + val currentList = currentList.toMutableList() + val index = currentList.indexOfFirst { it.ben.benId == benId } + if (index != -1) { + val updatedItem = currentList[index].copy(samStatus = status) + currentList[index] = updatedItem + submitList(currentList.toList()) + } + } + + + class ChildListClickListener( + val goToForm: (benId: Long, hhId: Long, dob: Long, type: String) -> Unit + + ) { + fun onClickedSAM(item: BenBasicDomain) = goToForm( + item.benId, item.hhId, item.dob, FormConstants.SAM_FORM_NAME + ) + fun onClickedORS(item: BenBasicDomain) = goToForm( + item.benId, item.hhId, item.dob, FormConstants.ORS_FORM_NAME + ) + fun onClickedIFA(item: BenBasicDomain) = goToForm( + item.benId, item.hhId, item.dob, FormConstants.IFA_FORM_NAME + ) + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/ChildCareVisitListAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/ChildCareVisitListAdapter.kt new file mode 100644 index 000000000..186df071a --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/ChildCareVisitListAdapter.kt @@ -0,0 +1,58 @@ +package org.piramalswasthya.sakhi.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.databinding.RvItemChildrenCareBottomSheetBinding +import org.piramalswasthya.sakhi.model.ChildOption + +class ChildCareVisitListAdapter( + private val clickListener: ChildOptionsClickListener +) : ListAdapter( + MyDiffUtilCallBack +) { + + + private object MyDiffUtilCallBack : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: ChildOption, newItem: ChildOption): Boolean = + oldItem.formType == newItem.formType + + override fun areContentsTheSame(oldItem: ChildOption, newItem: ChildOption): Boolean = + oldItem == newItem + } + + class ChildOptionViewHolder private constructor( + private val binding: RvItemChildrenCareBottomSheetBinding + ) : RecyclerView.ViewHolder(binding.root) { + + companion object { + fun from(parent: ViewGroup): ChildOptionViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = RvItemChildrenCareBottomSheetBinding.inflate(layoutInflater, parent, false) + return ChildOptionViewHolder(binding) + } + } + + fun bind(item: ChildOption, clickListener: ChildOptionsClickListener) { + binding.option = item + binding.clickListener = clickListener + binding.executePendingBindings() + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChildOptionViewHolder = + ChildOptionViewHolder.from(parent) + + override fun onBindViewHolder(holder: ChildOptionViewHolder, position: Int) { + holder.bind(getItem(position), clickListener) + } + + class ChildOptionsClickListener( + private val clickedOption: (formType: String, visitDay: String?, isViewMode: Boolean,formDataJson: String?, recordId: Int?) -> Unit + ) { + fun onAddClicked(item: ChildOption) = clickedOption(item.formType, null, false, null,null) + fun onViewClicked(item: ChildOption) = clickedOption(item.formType, item.visitDay, true,item.formDataJson,item.recordId) + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/ChildImmunizationVaccineAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/ChildImmunizationVaccineAdapter.kt new file mode 100644 index 000000000..cc458ff17 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/ChildImmunizationVaccineAdapter.kt @@ -0,0 +1,133 @@ +package org.piramalswasthya.sakhi.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.Toast +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.databinding.ItemChildImmunizationDoseBinding +import org.piramalswasthya.sakhi.model.VaccineDomain + + + +class ChildImmunizationVaccineAdapter (private val clickListener: ImmunizationClickListener? = null) : + ListAdapter( + ImmunizationIconDiffCallback + ) { + object ImmunizationIconDiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: VaccineDomain, newItem: VaccineDomain) = + oldItem.vaccineId == newItem.vaccineId + + override fun areContentsTheSame(oldItem: VaccineDomain, newItem: VaccineDomain) = + (oldItem == newItem) + + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = + IconViewHolder.from(parent) + + override fun onBindViewHolder(holder: IconViewHolder, position: Int) { + holder.bind(getItem(position), clickListener) + } + + class IconViewHolder private constructor(private val binding: ItemChildImmunizationDoseBinding) : + RecyclerView.ViewHolder(binding.root) { + + companion object { + fun from(parent: ViewGroup): IconViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = ItemChildImmunizationDoseBinding.inflate(layoutInflater, parent, false) + return IconViewHolder(binding) + } + } + + /*fun bind( + item: VaccineDomain, + clickListener: ImmunizationClickListener? + ) { + binding.vaccine = item + binding.clickListener = clickListener + + + binding.idSwitch.setOnCheckedChangeListener(null) + binding.idSwitch.isChecked = item.isSwitchChecked + if (item.state.name =="PENDING"||item.state.name =="OVERDUE"){ + binding.idSwitch.isEnabled = true + + }else if(item.state.name =="MISSED" ||item.state.name =="UNAVAILABLE"){ + binding.idSwitch.isEnabled = false + + }else{ + //If state is DONE + binding.idSwitch.isEnabled = false + binding.idSwitch.isChecked = true + } + + + binding.idSwitch.setOnCheckedChangeListener { compoundButton, b -> + if (b){ + clickListener?.onClicked(position,item.apply { + isSwitchChecked = true }) + }else{ + clickListener?.onClicked(position,item.apply { isSwitchChecked = false }) + } + + } + binding.executePendingBindings() + + + }*/ + + fun bind( + item: VaccineDomain, + clickListener: ImmunizationClickListener? + ) { + binding.vaccine = item + binding.clickListener = clickListener + + // Reset listeners before re-binding + binding.idSwitch.setOnCheckedChangeListener(null) + binding.idSwitch.setOnClickListener(null) + + // Set switch state + binding.idSwitch.isChecked = item.isSwitchChecked + + when (item.state.name) { + "PENDING", "OVERDUE" -> { + binding.idSwitch.isEnabled = true + binding.idSwitch.setOnCheckedChangeListener { _, isChecked -> + clickListener?.onClicked(adapterPosition, item.apply { isSwitchChecked = isChecked }) + } + } + + "MISSED", "UNAVAILABLE" -> { + binding.idSwitch.isEnabled = true // keep it tappable + binding.idSwitch.isChecked = false + binding.idSwitch.setOnClickListener { + Toast.makeText( + binding.root.context, + "Immunization cannot be done as it is missed", + Toast.LENGTH_SHORT + ).show() + binding.idSwitch.isChecked = false // always force back to off + } + } + + else -> { // DONE + binding.idSwitch.isEnabled = false + binding.idSwitch.isChecked = true + } + } + + binding.executePendingBindings() + } + + } + + + class ImmunizationClickListener(val selectedListener: (position:Int, vaccine: VaccineDomain) -> Unit) { + fun onClicked(position:Int,vaccine: VaccineDomain) = selectedListener(position,vaccine) + + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/ChildListAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/ChildListAdapter.kt index 439d11da6..179c5d9cb 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/adapters/ChildListAdapter.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/ChildListAdapter.kt @@ -58,11 +58,11 @@ class ChildListAdapter( class ChildListClickListener( - val goToHbnc: (benId: Long, hhId: Long) -> Unit + val goToHbyc: (benId: Long, hhId: Long,dob:Long) -> Unit ) { - fun onClickedHbnc(item: BenBasicDomain) = goToHbnc( - item.benId, item.hhId + fun onClickedHbyc(item: BenBasicDomain) = goToHbyc( + item.benId, item.hhId,item.dob ) } diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/ChildRegistrationAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/ChildRegistrationAdapter.kt index 4219ac94d..f75f477d5 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/adapters/ChildRegistrationAdapter.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/ChildRegistrationAdapter.kt @@ -5,6 +5,7 @@ import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.R import org.piramalswasthya.sakhi.databinding.RvItemBenChildRegBinding import org.piramalswasthya.sakhi.model.ChildRegDomain @@ -42,7 +43,7 @@ class ChildRegistrationAdapter( clickListener: ClickListener?, ) { binding.item = item - binding.btnForm.text = if (item.childBen == null) "Register" else "View" + binding.btnForm.text = if (item.childBen == null) binding.root.resources.getString(R.string.register) else binding.root.resources.getString(R.string.view) binding.btnForm.setBackgroundColor(binding.root.resources.getColor(if (item.childBen == null) android.R.color.holo_red_dark else android.R.color.holo_green_dark)) binding.clickListener = clickListener diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/DewormingAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/DewormingAdapter.kt new file mode 100644 index 000000000..cb1b192d2 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/DewormingAdapter.kt @@ -0,0 +1,55 @@ +package org.piramalswasthya.sakhi.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.model.DewormingCache +import org.piramalswasthya.sakhi.databinding.LayoutDewormingItemBinding + +class DewormingAdapter( + private val clickListener: DewormingClickListener? = null, +) : ListAdapter(DewormingDiffUtilCallBack) { + + private object DewormingDiffUtilCallBack : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: DewormingCache, + newItem: DewormingCache + ) = oldItem.id == newItem.id + + override fun areContentsTheSame( + oldItem: DewormingCache, + newItem: DewormingCache + ) = oldItem == newItem + } + + class DewormingViewHolder private constructor(private val binding: LayoutDewormingItemBinding) : + RecyclerView.ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): DewormingViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = LayoutDewormingItemBinding.inflate(layoutInflater, parent, false) + return DewormingViewHolder(binding) + } + } + + fun bind(item: DewormingCache, clickListener: DewormingClickListener?) { + binding.deworming = item + binding.clickListener = clickListener + binding.executePendingBindings() + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DewormingViewHolder { + return DewormingViewHolder.from(parent) + } + + override fun onBindViewHolder(holder: DewormingViewHolder, position: Int) { + holder.bind(getItem(position), clickListener) + } + + class DewormingClickListener(val clickListener: (id: Int) -> Unit) { + fun onClick(id: Int) = clickListener(id) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/ECRegistrationAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/ECRegistrationAdapter.kt index 35e6337a4..6e8f410d7 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/adapters/ECRegistrationAdapter.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/ECRegistrationAdapter.kt @@ -6,8 +6,11 @@ import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.R import org.piramalswasthya.sakhi.databinding.RvItemBenEcWithFormBinding import org.piramalswasthya.sakhi.model.BenWithEcrDomain +import org.piramalswasthya.sakhi.utils.HelperUtil +import java.util.concurrent.TimeUnit class ECRegistrationAdapter( private val clickListener: ClickListener? = null @@ -43,10 +46,39 @@ class ECRegistrationAdapter( ) { binding.benWithEcr = item + if (item.ecr == null) { + binding.llLmpDate.visibility = View.INVISIBLE + binding.llBenStatus.visibility = View.INVISIBLE + } else { + binding.llLmpDate.visibility = View.VISIBLE + binding.llBenStatus.visibility = View.VISIBLE + } + + if (item.ecr?.lmpDate != null && item.ecr.lmpDate != 0L) { + binding.benLmpDate.text = HelperUtil.getDateStringFromLongStraight(item.ecr.lmpDate) + if (System.currentTimeMillis() - item.ecr.lmpDate > TimeUnit.DAYS.toMillis(35)) { + binding.ivMissState.visibility = View.VISIBLE + binding.benStatus.text = "Missed Period" + } else { + binding.ivMissState.visibility = View.GONE + binding.benStatus.text = "Under Review" + } + } else { + binding.ivMissState.visibility = View.GONE + binding.llLmpDate.visibility = View.INVISIBLE + binding.llBenStatus.visibility = View.INVISIBLE + } + binding.ivSyncState.visibility = if (item.ecr == null) View.INVISIBLE else View.VISIBLE - binding.btnFormEc1.text = if (item.ecr == null) "Register" else "View" + binding.btnFormEc1.text = + if (item.ecr != null && item.ecr.lmpDate != 0L) { + binding.root.resources.getString(R.string.view) + } else { + binding.root.resources.getString(R.string.register) + } - binding.btnFormEc1.setBackgroundColor(binding.root.resources.getColor(if (item.ecr == null) android.R.color.holo_red_dark else android.R.color.holo_green_dark)) + + binding.btnFormEc1.setBackgroundColor(binding.root.resources.getColor(if (item.ecr != null && item.ecr.lmpDate != 0L) android.R.color.holo_green_dark else android.R.color.holo_red_dark)) binding.clickListener = clickListener binding.executePendingBindings() @@ -67,9 +99,13 @@ class ECRegistrationAdapter( class ClickListener( - private val clickedForm: ((hhId: Long, benId: Long) -> Unit)? = null + private val clickedForm: ((hhId: Long, benId: Long) -> Unit)? = null, + private val clickedAddAllBenBtn: (item:BenWithEcrDomain,benId: Long, hhId: Long, isViewMode: Boolean, isIFA: Boolean) -> Unit, + - ) { + + ) { + fun clickedAddAllBenBtn(item: BenWithEcrDomain, isMatched: Boolean, isIFA: Boolean) = clickedAddAllBenBtn(item,item.ben.benId, item.ben.hhId, isMatched,isIFA) fun onClickForm(item: BenWithEcrDomain) = clickedForm?.let { it(item.ben.hhId, item.ben.benId) } } diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/ECTrackingListAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/ECTrackingListAdapter.kt index abe5a50c7..a476ccca9 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/adapters/ECTrackingListAdapter.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/ECTrackingListAdapter.kt @@ -1,12 +1,15 @@ package org.piramalswasthya.sakhi.adapters import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import org.piramalswasthya.sakhi.databinding.RvItemEcTrackingListBinding import org.piramalswasthya.sakhi.model.BenWithEctListDomain +import org.piramalswasthya.sakhi.utils.HelperUtil +import java.util.concurrent.TimeUnit class ECTrackingListAdapter(private val clickListener: ECTrackListClickListener) : ListAdapter( @@ -36,6 +39,41 @@ class ECTrackingListAdapter(private val clickListener: ECTrackListClickListener) fun bind( item: BenWithEctListDomain, clickListener: ECTrackListClickListener ) { + + if (item.savedECTRecords.isEmpty()) { + binding.llEcTrackingDetails3.visibility = View.GONE + } else { + binding.llEcTrackingDetails3.visibility = View.VISIBLE + } + + if (item.ectDate == 0L) { + binding.ivFollowState.visibility = View.GONE + binding.llVisitDate.visibility = View.INVISIBLE + } else if (item.ectDate < System.currentTimeMillis() - TimeUnit.DAYS.toMillis(90) && + item.ectDate > System.currentTimeMillis() - TimeUnit.DAYS.toMillis(365)) { + binding.ivFollowState.visibility = View.VISIBLE + binding.llVisitDate.visibility = View.VISIBLE + binding.benVisitDate.text = HelperUtil.getDateStringFromLongStraight(item.ectDate) + } else { + binding.ivFollowState.visibility = View.GONE + binding.llVisitDate.visibility = View.VISIBLE + binding.benVisitDate.text = HelperUtil.getDateStringFromLongStraight(item.ectDate) + } + + if (item.lmpDate != 0L) { + binding.benLmpDate.text = HelperUtil.getDateStringFromLongStraight(item.lmpDate) + if (System.currentTimeMillis() - item.lmpDate > TimeUnit.DAYS.toMillis(35)) { + binding.ivMissState.visibility = View.VISIBLE + binding.benStatus.text = "Missed Period" + } else { + binding.ivMissState.visibility = View.GONE + binding.benStatus.text = "Under Review" + } + } else { + binding.ivMissState.visibility = View.GONE + binding.llEcTrackingDetails3.visibility = View.GONE + } + binding.item = item binding.clickListener = clickListener binding.executePendingBindings() diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/FailedWorkerAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/FailedWorkerAdapter.kt new file mode 100644 index 000000000..5a79f9470 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/FailedWorkerAdapter.kt @@ -0,0 +1,42 @@ +package org.piramalswasthya.sakhi.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.databinding.RvItemFailedWorkerBinding +import org.piramalswasthya.sakhi.model.FailedWorkerInfo + +class FailedWorkerAdapter : + ListAdapter(DiffCallback) { + + private object DiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: FailedWorkerInfo, newItem: FailedWorkerInfo) = + oldItem.workerName == newItem.workerName + + override fun areContentsTheSame(oldItem: FailedWorkerInfo, newItem: FailedWorkerInfo) = + oldItem == newItem + } + + class ViewHolder( + private val binding: RvItemFailedWorkerBinding + ) : RecyclerView.ViewHolder(binding.root) { + + fun bind(item: FailedWorkerInfo) { + binding.tvWorkerName.text = item.workerName + binding.tvErrorReason.text = item.error + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val binding = RvItemFailedWorkerBinding.inflate( + LayoutInflater.from(parent.context), parent, false + ) + return ViewHolder(binding) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.bind(getItem(position)) + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/FilariaMemberListAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/FilariaMemberListAdapter.kt new file mode 100644 index 000000000..ef33d9ad7 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/FilariaMemberListAdapter.kt @@ -0,0 +1,121 @@ +package org.piramalswasthya.sakhi.adapters + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.databinding.RvItemFilariaMemberListBinding +import org.piramalswasthya.sakhi.databinding.RvItemKalaAzarMemberListBinding +import org.piramalswasthya.sakhi.model.BenWithFilariaScreeningDomain +import org.piramalswasthya.sakhi.model.BenWithKALAZARScreeningDomain + +class FilariaMemberListAdapter( + private val clickListener: ClickListener? = null +) : + ListAdapter + (BenDiffUtilCallBack) { + private object BenDiffUtilCallBack : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: BenWithFilariaScreeningDomain, + newItem: BenWithFilariaScreeningDomain + ) = oldItem.ben.benId == newItem.ben.benId + + override fun areContentsTheSame( + oldItem: BenWithFilariaScreeningDomain, + newItem: BenWithFilariaScreeningDomain + ) = oldItem == newItem + + } + + class BenViewHolder private constructor(private val binding: RvItemFilariaMemberListBinding) : + RecyclerView.ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): BenViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = RvItemFilariaMemberListBinding.inflate(layoutInflater, parent, false) + return BenViewHolder(binding) + } + } + + fun bind( + item: BenWithFilariaScreeningDomain, + clickListener: ClickListener?, + ) { + binding.benWithFilaria = item + + /*if(item.tb?.historyOfTb == true){ + binding.cvContent.visibility = View.GONE + }*/ + + binding.ivSyncState.visibility = if (item.filaria == null) View.GONE else View.VISIBLE + + if (item.ben.spouseName == "Not Available" && item.ben.fatherName == "Not Available") { + binding.father = true + binding.husband = false + binding.spouse = false + } else { + if (item.ben.gender == "MALE") { + binding.father = true + binding.husband = false + binding.spouse = false + } else if (item.ben.gender == "FEMALE") { + if (item.ben.ageInt > 15) { + binding.father = + item.ben.fatherName != "Not Available" && item.ben.spouseName == "Not Available" + binding.husband = item.ben.spouseName != "Not Available" + binding.spouse = false + } else { + binding.father = true + binding.husband = false + binding.spouse = false + } + } else { + binding.father = + item.ben.fatherName != "Not Available" && item.ben.spouseName == "Not Available" + binding.spouse = item.ben.spouseName != "Not Available" + binding.husband = false + } + } + + if ( item.filaria != null && item.ben.isDeath) { + binding.btnFormTb.visibility = View.VISIBLE + } else if (item.filaria == null && !item.ben.isDeath){ + binding.btnFormTb.visibility = View.VISIBLE + } else if (item.filaria != null && !item.ben.isDeath){ + binding.btnFormTb.visibility = View.VISIBLE + } else { + binding.btnFormTb.visibility = View.INVISIBLE + } + binding.btnFormTb.text = if (item.filaria == null) binding.root.resources.getString(R.string.register) else binding.root.resources.getString(R.string.view) + binding.btnFormTb.setBackgroundColor(binding.root.resources.getColor(if (item.filaria == null) android.R.color.holo_red_dark else android.R.color.holo_green_dark)) + binding.clickListener = clickListener + + binding.executePendingBindings() + + } + + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ) = + BenViewHolder.from(parent) + + override fun onBindViewHolder(holder: BenViewHolder, position: Int) { + holder.bind(getItem(position), clickListener) + } + + + class ClickListener( + private val clickedForm: ((hhId: Long, benId: Long) -> Unit)? = null + + ) { + fun onClickForm(item: BenWithFilariaScreeningDomain) = + clickedForm?.let { it(item.ben.hhId, item.ben.benId) } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/FileListAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/FileListAdapter.kt new file mode 100644 index 000000000..0a62ae3b6 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/FileListAdapter.kt @@ -0,0 +1,38 @@ +package org.piramalswasthya.sakhi.adapters + +import android.net.Uri +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.databinding.LayoutFileViewBinding + +class FileListAdapter(private var images: MutableList) : RecyclerView.Adapter() { + + + fun updateFileList(newList: MutableList) { + this.images.clear() + this.images.addAll(newList) + notifyDataSetChanged() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FileVH { + val v = LayoutInflater.from(parent.context) + .inflate(R.layout.layout_file_view, parent, false) + + val layoutInflater = LayoutInflater.from(parent.context) + val binding = LayoutFileViewBinding.inflate(layoutInflater, parent, false) + return FileVH(binding) + } + + override fun getItemCount() = images.size + + override fun onBindViewHolder(holder: FileVH, position: Int) { + val file = images.get(position) + holder.binding.ivPreview.setImageURI(file) + } + + class FileVH(var binding: LayoutFileViewBinding) : RecyclerView.ViewHolder(binding.root) { + + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/FollowUpDatesAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/FollowUpDatesAdapter.kt new file mode 100644 index 000000000..724c92d0f --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/FollowUpDatesAdapter.kt @@ -0,0 +1,59 @@ +package org.piramalswasthya.sakhi.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.databinding.ItemFollowUpDateBinding +import org.piramalswasthya.sakhi.helpers.Languages +import org.piramalswasthya.sakhi.model.LeprosyFollowUpCache +import org.piramalswasthya.sakhi.utils.HelperUtil +import java.text.SimpleDateFormat +import java.util.* + +class FollowUpDatesAdapter : + ListAdapter(DiffCallback) { + + private val dateFormat = SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FollowUpViewHolder { + val binding = ItemFollowUpDateBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + return FollowUpViewHolder(binding) + } + + override fun onBindViewHolder(holder: FollowUpViewHolder, position: Int) { + val followUp = getItem(position) + holder.bind(followUp) + } + + inner class FollowUpViewHolder(private val binding: ItemFollowUpDateBinding) : + RecyclerView.ViewHolder(binding.root) { + + fun bind(followUp: LeprosyFollowUpCache) { + binding.tvFollowUpDate.text = dateFormat.format(Date(followUp.followUpDate)) + val ctx = binding.root.context + val englishArray = HelperUtil.getLocalizedResources(ctx, Languages.ENGLISH) + .getStringArray(R.array.leprosy_treatment_status_before_time) + val localizedArray = ctx.resources.getStringArray(R.array.leprosy_treatment_status_before_time) + val idx = englishArray.indexOf(followUp.treatmentStatus) + binding.tvTreatmentStatus.text = if (idx >= 0) localizedArray[idx] + else ctx.getString(R.string.pending) + } + } + + companion object DiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: LeprosyFollowUpCache, newItem: LeprosyFollowUpCache): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame(oldItem: LeprosyFollowUpCache, newItem: LeprosyFollowUpCache): Boolean { + return oldItem == newItem + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/FormInputAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/FormInputAdapter.kt index 949fd361b..ecaba4258 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/adapters/FormInputAdapter.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/FormInputAdapter.kt @@ -2,12 +2,16 @@ package org.piramalswasthya.sakhi.adapters import android.app.DatePickerDialog import android.app.TimePickerDialog +import android.content.Context import android.content.Context.INPUT_METHOD_SERVICE import android.content.res.ColorStateList import android.content.res.Resources import android.graphics.Color +import android.net.Uri import android.os.Build import android.text.Editable +import android.text.InputFilter +import android.text.InputFilter.AllCaps import android.text.Spannable import android.text.SpannableString import android.text.TextWatcher @@ -23,26 +27,36 @@ import android.widget.CheckBox import android.widget.LinearLayout import android.widget.RadioButton import android.widget.RadioGroup +import android.widget.TextView import androidx.appcompat.content.res.AppCompatResources import androidx.core.view.children import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView.ViewHolder +import com.google.android.material.button.MaterialButton +import com.google.android.material.textfield.TextInputEditText +import com.google.android.material.textfield.TextInputLayout import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.databinding.LayoutMultiFileUploadBinding +import org.piramalswasthya.sakhi.databinding.LayoutUploafFormBinding import org.piramalswasthya.sakhi.databinding.RvItemFormAgePickerViewV2Binding +import org.piramalswasthya.sakhi.databinding.RvItemFormBtnBinding import org.piramalswasthya.sakhi.databinding.RvItemFormCheckV2Binding import org.piramalswasthya.sakhi.databinding.RvItemFormDatepickerV2Binding import org.piramalswasthya.sakhi.databinding.RvItemFormDropdownV2Binding import org.piramalswasthya.sakhi.databinding.RvItemFormEditTextV2Binding import org.piramalswasthya.sakhi.databinding.RvItemFormHeadlineV2Binding import org.piramalswasthya.sakhi.databinding.RvItemFormImageViewV2Binding +import org.piramalswasthya.sakhi.databinding.RvItemFormNumberPickerBinding import org.piramalswasthya.sakhi.databinding.RvItemFormRadioV2Binding import org.piramalswasthya.sakhi.databinding.RvItemFormTextViewV2Binding import org.piramalswasthya.sakhi.databinding.RvItemFormTimepickerV2Binding import org.piramalswasthya.sakhi.helpers.Konstants import org.piramalswasthya.sakhi.helpers.getDateString +import org.piramalswasthya.sakhi.helpers.isInternetAvailable import org.piramalswasthya.sakhi.model.AgeUnitDTO import org.piramalswasthya.sakhi.model.FormElement +import org.piramalswasthya.sakhi.model.InputType import org.piramalswasthya.sakhi.model.InputType.AGE_PICKER import org.piramalswasthya.sakhi.model.InputType.CHECKBOXES import org.piramalswasthya.sakhi.model.InputType.DATE_PICKER @@ -55,25 +69,30 @@ import org.piramalswasthya.sakhi.model.InputType.TEXT_VIEW import org.piramalswasthya.sakhi.model.InputType.TIME_PICKER import org.piramalswasthya.sakhi.model.InputType.values import org.piramalswasthya.sakhi.ui.home_activity.all_ben.new_ben_registration.AgePickerDialog +import org.piramalswasthya.sakhi.ui.home_activity.all_ben.new_ben_registration.ben_form.NewBenRegViewModel.Companion.isOtpVerified +import org.piramalswasthya.sakhi.utils.HelperUtil +import org.piramalswasthya.sakhi.utils.HelperUtil.findFragmentActivity import org.piramalswasthya.sakhi.utils.HelperUtil.getAgeStrFromAgeUnit import org.piramalswasthya.sakhi.utils.HelperUtil.getDobFromAge import org.piramalswasthya.sakhi.utils.HelperUtil.getLongFromDate import org.piramalswasthya.sakhi.utils.HelperUtil.updateAgeDTO import timber.log.Timber import java.util.Calendar +import java.util.Locale class FormInputAdapter( private val imageClickListener: ImageClickListener? = null, private val ageClickListener: AgeClickListener? = null, + private val sendOtpClickListener: SendOtpClickListener? = null, private val formValueListener: FormValueListener? = null, - private val isEnabled: Boolean = true + private val isEnabled: Boolean = true, + private val selectImageClickListener: SelectUploadImageClickListener? = null, + private val viewDocumentListner: ViewDocumentOnClick? = null, + var fileList: MutableList? = null, ) : ListAdapter(FormInputDiffCallBack) { + var disableUpload = false - // @Inject -// lateinit var preferenceDao: PreferenceDao -// @Inject -// lateinit var context: Context object FormInputDiffCallBack : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: FormElement, newItem: FormElement) = oldItem.id == newItem.id @@ -108,6 +127,28 @@ class FormInputAdapter( } else { binding.et.isClickable = true binding.et.isFocusable = true + binding.et.isFocusableInTouchMode = true + } + if (isOtpVerified && item.id == 44 && item.title.equals("Contact Number")) { + binding.et.isClickable = false + binding.et.isFocusable = false + } + + if (item.title.contains("first name", true) || + item.title.contains("last name", true) || + item.title.contains("father's name", true) || + item.title.contains("mother's name", true) + ) { +// edittext.setFilters(arrayOf(AllCaps())) + val editFilters = binding.et.filters + var newFilters = arrayOfNulls(editFilters.size + 1) + editFilters.forEachIndexed { index, inputFilter -> + newFilters[index] = editFilters[index] + } + newFilters[editFilters.size] = AllCaps() +// newFilters.set(editFilters.size, AllCaps()) +// binding.et.filters = arrayOf(AllCaps()) + binding.et.filters = newFilters } binding.form = item if (item.errorText == null) binding.tilEditText.isErrorEnabled = false @@ -155,7 +196,7 @@ class FormInputAdapter( binding.tilEditText.isErrorEnabled = item.errorText != null binding.tilEditText.error = item.errorText } -// binding.tilEditText.error = null +// binding.tilEditText.error = null // else if(item.errorText!= null && binding.tilEditText.error==null) // binding.tilEditText.error = item.errorText @@ -240,16 +281,32 @@ class FormInputAdapter( } } binding.et.setOnFocusChangeListener { _, hasFocus -> - if (hasFocus) binding.et.addTextChangedListener(textWatcher) - else { + if (hasFocus){ + binding.et.requestFocus() + binding.et.addTextChangedListener(textWatcher) + val imm = + binding.root.context.getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager? + imm!!.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0) + } else { binding.et.removeTextChangedListener(textWatcher) + binding.et.clearFocus() val imm = binding.root.context.getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager? imm!!.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0) } } + binding.et.setOnEditorActionListener { v, actionId, _ -> + if (actionId == android.view.inputmethod.EditorInfo.IME_ACTION_DONE || + actionId == android.view.inputmethod.EditorInfo.IME_ACTION_NEXT) { + v.clearFocus() + val imm = v.context.getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager? + imm?.hideSoftInputFromWindow(v.windowToken, 0) + true + } else false + } binding.et.setOnKeyListener(View.OnKeyListener { v, keyCode, event -> if (keyCode == KeyEvent.KEYCODE_ENTER && (event.action == KeyEvent.ACTION_UP || event.action == KeyEvent.ACTION_DOWN)) { + v.clearFocus() return@OnKeyListener true } false @@ -293,6 +350,11 @@ class FormInputAdapter( } fun bind(item: FormElement, isEnabled: Boolean, formValueListener: FormValueListener?) { + binding.tilRvDropdown.clearFocus() + binding.tilEditText.clearFocus() + binding.et.clearFocus() + binding.actvRvDropdown.clearFocus() + binding.actvRvDropdown.showSoftInputOnFocus = false binding.form = item if (item.errorText == null) { binding.tilRvDropdown.error = null @@ -307,7 +369,11 @@ class FormInputAdapter( return } + hideKeyboardImmediately() + + binding.actvRvDropdown.setOnItemClickListener { _, _, index, _ -> + hideKeyboardWithRetry() item.value = item.entries?.get(index) Timber.d("Item DD : $item") // if (item.hasDependants || item.hasAlertError) { @@ -317,10 +383,34 @@ class FormInputAdapter( binding.tilRvDropdown.error = item.errorText } + binding.actvRvDropdown.setOnClickListener { + hideKeyboardWithRetry() + } + item.errorText?.let { binding.tilRvDropdown.error = it } binding.executePendingBindings() } + + private fun hideKeyboardImmediately() { + val imm = binding.root.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(binding.root.windowToken, 0) + } + + private fun hideKeyboardWithRetry() { + val imm = binding.root.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + + imm.hideSoftInputFromWindow(binding.root.windowToken, 0) + + binding.root.postDelayed({ + imm.hideSoftInputFromWindow(binding.root.windowToken, 0) + }, 50) + + binding.root.postDelayed({ + imm.hideSoftInputFromWindow(binding.root.windowToken, 0) + }, 200) + } + } class RadioInputViewHolder private constructor(private val binding: RvItemFormRadioV2Binding) : @@ -398,6 +488,10 @@ class FormInputAdapter( if (item.value == it) rdBtn.isChecked = true rdBtn.setOnCheckedChangeListener { _, b -> if (b) { + // Clear focus from any previously focused EditText to prevent auto-scroll + binding.root.rootView.findFocus()?.clearFocus() + val imm = binding.root.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(binding.root.windowToken, 0) item.value = it if (item.hasDependants || item.hasAlertError) { Timber.d( @@ -480,76 +574,106 @@ class FormInputAdapter( } fun bind( - item: FormElement, isEnabled: Boolean, formValueListener: FormValueListener? + item: FormElement, + isEnabled: Boolean, + formValueListener: FormValueListener? ) { binding.form = item - - if (item.errorText != null) binding.clRi.setBackgroundResource(R.drawable.state_errored) - else binding.clRi.setBackgroundResource(0) binding.llChecks.removeAllViews() - binding.llChecks.apply { - item.entries?.let { items -> - orientation = item.orientation ?: LinearLayout.VERTICAL - weightSum = items.size.toFloat() - items.forEachIndexed { index, it -> - val cbx = CheckBox(this.context) - cbx.layoutParams = RadioGroup.LayoutParams( - RadioGroup.LayoutParams.MATCH_PARENT, - RadioGroup.LayoutParams.WRAP_CONTENT, - 1.0F - ) - if (!isEnabled) { - cbx.isClickable = false - cbx.isFocusable = false - } - cbx.id = View.generateViewId() - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) cbx.setTextAppearance( - context, android.R.style.TextAppearance_Material_Medium - ) - else cbx.setTextAppearance(android.R.style.TextAppearance_Material_Subhead) - cbx.text = it - addView(cbx) - if (item.value?.contains(it) == true) cbx.isChecked = true - cbx.setOnCheckedChangeListener { _, b -> - if (b) { - if (item.value != null) item.value = item.value + it - else item.value = it - if (item.hasDependants || item.hasAlertError) { - Timber.d( - "listener trigger : ${item.id} ${ - item.entries!!.indexOf( - it - ) - } $it" - ) -// formValueListener?.onValueChanged( -// item, item.entries!!.indexOf(it) -// ) - } - } else { - if (item.value?.contains(it) == true) { - item.value = item.value?.replace(it, "") - } - } - formValueListener?.onValueChanged( - item, index * (if (b) 1 else -1) - ) - if (item.value.isNullOrBlank()) { - item.value = null - } else { - Timber.d("Called here!") - item.errorText = null - binding.clRi.setBackgroundResource(0) - } - Timber.d("Checkbox value : ${item.value}") - } + val selectedIndexes = item.value + ?.split("|") + ?.mapNotNull { it.toIntOrNull() } + ?.toMutableSet() + ?: mutableSetOf() + + item.entries?.forEachIndexed { index, text -> + + val cbx = CheckBox(binding.root.context) + cbx.text = text + cbx.isEnabled = isEnabled + cbx.isChecked = selectedIndexes.contains(index) + cbx.setOnCheckedChangeListener { _, isChecked -> + + if (isChecked) { + selectedIndexes.add(index) + } else { + selectedIndexes.remove(index) } + + item.value = + if (selectedIndexes.isEmpty()) null + else selectedIndexes.sorted().joinToString("|") + + item.errorText = null + binding.clRi.setBackgroundResource(0) + + formValueListener?.onValueChanged(item, index) } + + binding.llChecks.addView(cbx) } + binding.executePendingBindings() + } + + } + + class ButtonInputViewHolder private constructor(private val binding: RvItemFormBtnBinding) : + ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): ViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = RvItemFormBtnBinding.inflate(layoutInflater, parent, false) + return ButtonInputViewHolder(binding) + } + } + + fun bind(item: FormElement, isEnabled: Boolean, formValueListener: SendOtpClickListener?) { + binding.form = item + isOtpVerified(isEnabled, isInternetAvailable(binding.root.context)) + + binding.generateOtp.setOnClickListener { + formValueListener!!.onButtonClick(item,binding.generateOtp,binding.timerInSec,binding.tilEditText,isEnabled,adapterPosition,binding.et) + } + + + + } + + private fun isOtpVerified(isEnabled: Boolean, internetAvailable: Boolean) { + if(isOtpVerified) { + binding.generateOtp.text = binding.generateOtp.resources.getString(R.string.verified) + binding.generateOtp.isEnabled = isEnabled + + } else { + binding.generateOtp.text = binding.generateOtp.resources.getString(R.string.send_otp) + if (internetAvailable){ + binding.generateOtp.isEnabled = isEnabled + } else { + binding.generateOtp.isEnabled = !isEnabled + + } + + } } + + + } + + + + class SelectUploadImageClickListener(private val selectImageClick: (formId: Int) -> Unit) { + + fun onSelectImageClick(form: FormElement) = selectImageClick(form.id) + + } + + class ViewDocumentOnClick(private val viewDocument: (formId: Int) -> Unit) { + + fun onViewDocumentClick(form: FormElement) = viewDocument(form.id) + } class DatePickerInputViewHolder private constructor(private val binding: RvItemFormDatepickerV2Binding) : @@ -581,6 +705,11 @@ class FormInputAdapter( item.errorText?.also { binding.tilEditText.error = it } ?: run { binding.tilEditText.error = null } binding.et.setOnClickListener { + val activity = binding.et.context.findFragmentActivity() + ?: return@setOnClickListener + val originalLocale = Locale.getDefault() + HelperUtil.setEnLocaleForDatePicker(activity) + item.value?.let { value -> thisYear = value.substring(6).toInt() thisMonth = value.substring(3, 5).trim().toInt() - 1 @@ -599,18 +728,20 @@ class FormInputAdapter( item.value = getDateString(item.max) else item.value = getDateString(millis) -// "${if (day > 9) day else "0$day"}-${if (month > 8) month + 1 else "0${month + 1}"}-$year" binding.invalidateAll() if (item.hasDependants) formValueListener?.onValueChanged(item, -1) }, thisYear, thisMonth, thisDay ) item.errorText = null binding.tilEditText.error = null - datePickerDialog.datePicker.maxDate = item.max ?: 0 - datePickerDialog.datePicker.minDate = item.min ?: 0 + item.min?.let { datePickerDialog.datePicker.minDate = it } + item.max?.let { datePickerDialog.datePicker.maxDate = it } if (item.showYearFirstInDatePicker) datePickerDialog.datePicker.touchables[0].performClick() datePickerDialog.show() + datePickerDialog.setOnDismissListener { + HelperUtil.setOriginalLocaleForDatePicker(activity,originalLocale) + } } binding.executePendingBindings() @@ -754,6 +885,8 @@ class FormInputAdapter( getDobFromAge(ageUnitDTO) binding.etDate.setText(getDateString(calDob.timeInMillis)) item.value = getDateString(calDob.timeInMillis) + item.errorText = null + binding.tilEditTextDate.error = null if (item.hasDependants) formValueListener?.onValueChanged(item, -1) } } @@ -775,6 +908,11 @@ class FormInputAdapter( item.errorText?.also { binding.tilEditTextDate.error = it } ?: run { binding.tilEditTextDate.error = null } binding.etDate.setOnClickListener { + val activity = binding.etDate.context.findFragmentActivity() + ?: return@setOnClickListener + val originalLocale = Locale.getDefault() + HelperUtil.setEnLocaleForDatePicker(activity) + item.value?.let { value -> thisYear = value.substring(6).toInt() thisMonth = value.substring(3, 5).trim().toInt() - 1 @@ -803,11 +941,14 @@ class FormInputAdapter( ) item.errorText = null binding.tilEditTextDate.error = null - datePickerDialog.datePicker.maxDate = item.max ?: 0 - datePickerDialog.datePicker.minDate = item.min ?: 0 + item.min?.let { datePickerDialog.datePicker.minDate = it } + item.max?.let { datePickerDialog.datePicker.maxDate = it } if (item.showYearFirstInDatePicker) datePickerDialog.datePicker.touchables[0].performClick() datePickerDialog.show() + datePickerDialog.setOnDismissListener { + HelperUtil.setOriginalLocaleForDatePicker(activity,originalLocale) + } } binding.executePendingBindings() @@ -839,8 +980,20 @@ class FormInputAdapter( } class ImageClickListener(private val imageClick: (formId: Int) -> Unit) { - fun onImageClick(form: FormElement) = imageClick(form.id) + } + + class SendOtpClickListener(private val btnClick: (formId: Int,generateOtp:MaterialButton,timerInsec: TextView,tilEditText:TextInputLayout, isEnabled: Boolean,adapterPosition:Int,otpField: TextInputEditText) -> Unit) { + + fun onButtonClick( + form: FormElement, + generateOtp: MaterialButton, + timerInSec: TextView, + tilEditText: TextInputLayout, + isEnabled: Boolean, + adapterPosition: Int, + otpField: TextInputEditText + ) = btnClick(form.id,generateOtp,timerInSec,tilEditText,isEnabled,adapterPosition,otpField) } @@ -858,11 +1011,171 @@ class FormInputAdapter( } } + class NumberPickerInputViewHolder private constructor( + private val binding: RvItemFormNumberPickerBinding + ) : ViewHolder(binding.root) { + + private var textWatcher: TextWatcher? = null + private var internalUpdate = false + + companion object { + fun from(parent: ViewGroup): ViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = RvItemFormNumberPickerBinding.inflate(layoutInflater, parent, false) + return NumberPickerInputViewHolder(binding) + } + } + + fun bind( + item: FormElement, + isEnabled: Boolean, + formValueListener: FormValueListener? + ) { + binding.form = item + + binding.etNumberInput.isEnabled = isEnabled + binding.btnDecrement.isEnabled = isEnabled + binding.btnIncrement.isEnabled = isEnabled + if (!isEnabled) { + hideError() + return + } + + val minValue = item.min?.toInt() ?: 0 + val maxValue = item.max?.toInt() + val allowNegative = item.minDecimal != null && item.minDecimal!! < 0 + + binding.etNumberInput.setText(minValue.toString()) + binding.etNumberInput.setSelection(binding.etNumberInput.text!!.length) + var currentValue = item.value?.toIntOrNull() ?: minValue + + textWatcher?.let { binding.etNumberInput.removeTextChangedListener(it) } + + textWatcher = object : TextWatcher { + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { + // No action required for this implementation. + // This method is implemented to satisfy the interface contract. + + + } + + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { + // No action required for this implementation. + // This method is implemented to satisfy the interface contract. + + } + + + + + override fun afterTextChanged(editable: Editable?) { + + if (internalUpdate) return + if (editable.isNullOrBlank()) { + item.errorText = binding.root.resources.getString(R.string.value_cannot_be_empty) + showError(item.errorText!!) + return + } + + val inputValue = editable.toString().toIntOrNull() + if (inputValue == null) { + item.errorText = binding.root.resources.getString(R.string.enter_a_valid_number) + showError(item.errorText!!) + return + } + + val validated = validateValue(inputValue, minValue, maxValue, allowNegative) + + if (validated != inputValue) { + val msg = if (maxValue != null) + binding.root.resources.getString(R.string.allowed_range, minValue, maxValue) + else + binding.root.resources.getString(R.string.minimum_allowed_value, minValue) + item.errorText = msg + showError(msg) + + updateDisplay(validated) + updateValue(validated, item, formValueListener) + return + } + + item.errorText = null + hideError() + + currentValue = validated + updateValue(currentValue, item, formValueListener) + } + } + + updateDisplay(currentValue) + + binding.etNumberInput.addTextChangedListener(textWatcher) + + + + binding.btnDecrement.setOnClickListener { + currentValue = (item.value?.toIntOrNull() ?: minValue) - 1 + currentValue = validateValue(currentValue, minValue, maxValue, allowNegative) + hideError() + updateValue(currentValue, item, formValueListener) + updateDisplay(currentValue) + } + + binding.btnIncrement.setOnClickListener { + currentValue = (item.value?.toIntOrNull() ?: minValue) + 1 + currentValue = validateValue(currentValue, minValue, maxValue, allowNegative) + hideError() + updateValue(currentValue, item, formValueListener) + updateDisplay(currentValue) + } + } + + + private fun validateValue(value: Int, min: Int, max: Int?, allowNegative: Boolean): Int { + var newValue = value + + if (!allowNegative && newValue < min) newValue = min + if (max != null && newValue > max) newValue = max + + return newValue + } + + private fun updateDisplay(value: Int) { + internalUpdate = true + binding.etNumberInput.setText(value.toString()) + binding.etNumberInput.setSelection(binding.etNumberInput.text!!.length) + internalUpdate = false + + } + + private fun updateValue( + newValue: Int, + item: FormElement, + formValueListener: FormValueListener? + ) { + item.value = newValue.toString() + formValueListener?.onValueChanged(item,newValue) + } + + private fun showError(message: String) { + binding.tvError.apply { + text = message + visibility = View.VISIBLE + } + } + + private fun hideError() { + binding.tvError.visibility = View.GONE + } + } + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val inputTypes = values() - return when (inputTypes[viewType]) { + val safeType = inputTypes.getOrNull(viewType) ?: TEXT_VIEW + + return when (safeType) { EDIT_TEXT -> EditTextInputViewHolder.from(parent) DROPDOWN -> DropDownInputViewHolder.from(parent) RADIO -> RadioInputViewHolder.from(parent) @@ -873,46 +1186,158 @@ class FormInputAdapter( TIME_PICKER -> TimePickerInputViewHolder.from(parent) HEADLINE -> HeadlineViewHolder.from(parent) AGE_PICKER -> AgePickerViewInputViewHolder.from(parent) + InputType.BUTTON -> ButtonInputViewHolder.from(parent) + InputType.FILE_UPLOAD -> FileUploadInputViewHolder.from(parent) + InputType.NUMBER_PICKER -> NumberPickerInputViewHolder.from(parent) + InputType.MULTIFILE_UPLOAD -> MultiFileUploadInputViewHolder.from(parent) } } + fun updateFileList(newList: List) { + this.fileList?.addAll(newList) + notifyDataSetChanged() + } + class MultiFileUploadInputViewHolder private constructor(private val binding: LayoutMultiFileUploadBinding) : + ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): ViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = LayoutMultiFileUploadBinding.inflate(layoutInflater, parent, false) + return MultiFileUploadInputViewHolder(binding) + } + } + + private lateinit var fileAdapter: FileListAdapter + + fun bind( + item: FormElement, + clickListener: SelectUploadImageClickListener?, + documentOnClick: ViewDocumentOnClick?, + isEnabled: Boolean, + fileList : MutableList? + ) { + val items = fileList ?: mutableListOf() + fileAdapter = FileListAdapter(items) + binding.rvFiles.adapter = fileAdapter + fileAdapter.updateFileList(items) + fileAdapter.notifyDataSetChanged() + + binding.btnSelectFiles.isEnabled = isEnabled + binding.btnSelectFiles.alpha = if (isEnabled) 1f else 0.5f + + binding.btnSelectFiles.setOnClickListener { + clickListener?.onSelectImageClick(item) + } + } + + } + + + + class FileUploadInputViewHolder private constructor(private val binding: LayoutUploafFormBinding) : + ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): ViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = LayoutUploafFormBinding.inflate(layoutInflater, parent, false) + return FileUploadInputViewHolder(binding) + } + } + + + fun bind( + item: FormElement, + clickListener: SelectUploadImageClickListener?, + documentOnClick: ViewDocumentOnClick?, + isEnabled: Boolean + ) { + binding.form = item + binding.tvTitle.text = item.title + binding.clickListener = clickListener + binding.documentclickListener = documentOnClick + binding.btnView.visibility = if (!item.value.isNullOrEmpty()) View.VISIBLE else View.GONE + + if (isEnabled) { + binding.addFile.visibility = View.VISIBLE +// binding.addFile.isEnabled = true +// binding.addFile.alpha = 1f + } else { + binding.addFile.visibility = View.GONE +// binding.addFile.isEnabled = false +// binding.addFile.alpha = 0.5f + } + } + + } override fun onBindViewHolder(holder: ViewHolder, position: Int) { val item = getItem(position) - val isEnabled = if (isEnabled) item.isEnabled else false - when (item.inputType) { - EDIT_TEXT -> (holder as EditTextInputViewHolder).bind( - item, isEnabled, formValueListener - ) - - DROPDOWN -> (holder as DropDownInputViewHolder).bind(item, isEnabled, formValueListener) - RADIO -> (holder as RadioInputViewHolder).bind(item, isEnabled, formValueListener) - DATE_PICKER -> (holder as DatePickerInputViewHolder).bind( - item, isEnabled, formValueListener - ) - - TEXT_VIEW -> (holder as TextViewInputViewHolder).bind(item) - IMAGE_VIEW -> (holder as ImageViewInputViewHolder).bind( - item, imageClickListener, isEnabled - ) - - CHECKBOXES -> (holder as CheckBoxesInputViewHolder).bind( - item, - isEnabled, - formValueListener - ) - - TIME_PICKER -> (holder as TimePickerInputViewHolder).bind(item, isEnabled) - HEADLINE -> (holder as HeadlineViewHolder).bind(item, formValueListener) - AGE_PICKER -> (holder as AgePickerViewInputViewHolder).bind( - item, - isEnabled, - formValueListener - ) +// I was getting crash that's why i have moved this line inside try block +// val isEnabled = if (isEnabled) item.isEnabled else false + try { + val isEnabled = if (isEnabled) item.isEnabled else false + when (item.inputType) { + EDIT_TEXT -> (holder as EditTextInputViewHolder).bind( + item, isEnabled, formValueListener + ) + + DROPDOWN -> (holder as DropDownInputViewHolder).bind( + item, + isEnabled, + formValueListener + ) + + RADIO -> (holder as RadioInputViewHolder).bind(item, isEnabled, formValueListener) + DATE_PICKER -> (holder as DatePickerInputViewHolder).bind( + item, isEnabled, formValueListener + ) + + TEXT_VIEW -> (holder as TextViewInputViewHolder).bind(item) + IMAGE_VIEW -> (holder as ImageViewInputViewHolder).bind( + item, imageClickListener, isEnabled + ) + + CHECKBOXES -> (holder as CheckBoxesInputViewHolder).bind( + item, + isEnabled, + formValueListener + ) + + TIME_PICKER -> (holder as TimePickerInputViewHolder).bind(item, isEnabled) + HEADLINE -> (holder as HeadlineViewHolder).bind(item, formValueListener) + AGE_PICKER -> (holder as AgePickerViewInputViewHolder).bind( + item, + isEnabled, + formValueListener + ) + InputType.BUTTON -> (holder as ButtonInputViewHolder).bind( + item, + isEnabled, + sendOtpClickListener + ) + + InputType.FILE_UPLOAD -> (holder as FileUploadInputViewHolder).bind(item,selectImageClickListener,viewDocumentListner,isEnabled = !disableUpload) + InputType.MULTIFILE_UPLOAD -> (holder as MultiFileUploadInputViewHolder).bind(item,selectImageClickListener,viewDocumentListner,isEnabled = !disableUpload,fileList) + + InputType.NUMBER_PICKER -> (holder as NumberPickerInputViewHolder).bind( + item, isEnabled, formValueListener + ) + } + } catch (e: Exception) { + e.printStackTrace() + } } - override fun getItemViewType(position: Int) = getItem(position).inputType.ordinal + //override fun getItemViewType(position: Int) = getItem(position).inputType.ordinal + //THis changes done to solve crashalytics issue + override fun getItemViewType(position: Int): Int { + if (position < 0 || position >= itemCount) { + return TEXT_VIEW.ordinal // fallback view type + } + val item = getItem(position) ?: return TEXT_VIEW.ordinal + return item.inputType.ordinal + } /** * Validation Result : -1 -> all good * else index of element creating trouble @@ -945,4 +1370,7 @@ class FormInputAdapter( } return retVal } + + + } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/FormInputAdapterOld.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/FormInputAdapterOld.kt index 0490528fb..f26ea3b02 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/adapters/FormInputAdapterOld.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/FormInputAdapterOld.kt @@ -2,6 +2,9 @@ package org.piramalswasthya.sakhi.adapters import android.app.DatePickerDialog import android.app.TimePickerDialog +import android.content.Context +import android.net.Uri +import android.os.CountDownTimer import android.text.Editable import android.text.InputFilter import android.text.InputFilter.AllCaps @@ -11,39 +14,55 @@ import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.view.inputmethod.InputMethodManager import android.widget.LinearLayout import android.widget.RadioButton import android.widget.RadioGroup +import android.widget.TextView import androidx.core.view.children import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView.ViewHolder +import com.google.android.material.button.MaterialButton import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.adapters.FormInputAdapter.SelectUploadImageClickListener +import org.piramalswasthya.sakhi.adapters.FormInputAdapter.ViewDocumentOnClick import org.piramalswasthya.sakhi.configuration.FormEditTextDefaultInputFilter +import org.piramalswasthya.sakhi.databinding.RvItemFormBtnBinding +import org.piramalswasthya.sakhi.databinding.LayoutMultiFileUploadBinding import org.piramalswasthya.sakhi.databinding.RvItemFormCheckBinding import org.piramalswasthya.sakhi.databinding.RvItemFormDatepickerBinding import org.piramalswasthya.sakhi.databinding.RvItemFormDropdownBinding import org.piramalswasthya.sakhi.databinding.RvItemFormEditTextBinding import org.piramalswasthya.sakhi.databinding.RvItemFormHeadlineBinding import org.piramalswasthya.sakhi.databinding.RvItemFormImageViewBinding +import org.piramalswasthya.sakhi.databinding.RvItemFormNumberPickerBinding import org.piramalswasthya.sakhi.databinding.RvItemFormRadioBinding import org.piramalswasthya.sakhi.databinding.RvItemFormTextViewBinding import org.piramalswasthya.sakhi.databinding.RvItemFormTimepickerBinding +import org.piramalswasthya.sakhi.databinding.RvItemFormUploadImageBinding import org.piramalswasthya.sakhi.helpers.Konstants +import org.piramalswasthya.sakhi.model.FormElement import org.piramalswasthya.sakhi.model.FormInputOld import org.piramalswasthya.sakhi.model.InputType.AGE_PICKER +import org.piramalswasthya.sakhi.model.InputType.BUTTON import org.piramalswasthya.sakhi.model.InputType.CHECKBOXES import org.piramalswasthya.sakhi.model.InputType.DATE_PICKER import org.piramalswasthya.sakhi.model.InputType.DROPDOWN import org.piramalswasthya.sakhi.model.InputType.EDIT_TEXT +import org.piramalswasthya.sakhi.model.InputType.FILE_UPLOAD import org.piramalswasthya.sakhi.model.InputType.HEADLINE import org.piramalswasthya.sakhi.model.InputType.IMAGE_VIEW import org.piramalswasthya.sakhi.model.InputType.RADIO import org.piramalswasthya.sakhi.model.InputType.TEXT_VIEW import org.piramalswasthya.sakhi.model.InputType.TIME_PICKER +import org.piramalswasthya.sakhi.model.InputType.NUMBER_PICKER import org.piramalswasthya.sakhi.model.InputType.values +import org.piramalswasthya.sakhi.utils.HelperUtil +import org.piramalswasthya.sakhi.utils.HelperUtil.findFragmentActivity import timber.log.Timber import java.util.Calendar +import java.util.Locale class FormInputAdapterOld( private val imageClickListener: ImageClickListener? = null, @@ -245,6 +264,124 @@ class FormInputAdapterOld( } } + class ButtonInputViewHolder private constructor(private val binding: RvItemFormBtnBinding) : + ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): ViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = RvItemFormBtnBinding.inflate(layoutInflater, parent, false) + return ButtonInputViewHolder(binding) + } + } + + fun bind(item: FormInputOld, isEnabled: Boolean) { + + + } + } + + + class MultiFileUploadInputViewHolder private constructor(private val binding: LayoutMultiFileUploadBinding) : + ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): ViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = LayoutMultiFileUploadBinding.inflate(layoutInflater, parent, false) + return MultiFileUploadInputViewHolder(binding) + } + } + var selectedFiles = mutableListOf() + + private lateinit var fileAdapter: FileListAdapter + + fun bind( + item: FormElement, + clickListener: SelectUploadImageClickListener?, + documentOnClick: ViewDocumentOnClick?, + isEnabled: Boolean + ) { + /* binding.form = item + binding.tvTitle.text = item.title + binding.clickListener = clickListener + binding.documentclickListener = documentOnClick + binding.btnView.visibility = if (item.value != null) View.VISIBLE else View.GONE + + if (isEnabled) { + binding.addFile.isEnabled = true + binding.addFile.alpha = 1f + } else { + binding.addFile.isEnabled = false + binding.addFile.alpha = 0.5f + }*/ + + fileAdapter = FileListAdapter(selectedFiles) + binding.rvFiles.adapter = fileAdapter + + binding.btnSelectFiles.isEnabled = isEnabled + binding.btnSelectFiles.alpha = if (isEnabled) 1f else 0.5f + + binding.btnSelectFiles.setOnClickListener { + clickListener?.onSelectImageClick(item) + } + + // Show view button only if images exist +// binding.btnView.visibility = if (selectedFiles.isNotEmpty()) View.VISIBLE else View.GONE + + /*binding.btnView.setOnClickListener { + documentOnClick?.onViewDocumentClicked(selectedFiles) + }*/ + } + + fun updateSelectedFiles(files: List) { + selectedFiles.clear() + selectedFiles.addAll(files) + fileAdapter.notifyDataSetChanged() +// binding.btnView.visibility = if (files.isNotEmpty()) View.VISIBLE else View.GONE + + } + + } + + + class FileUploadInputViewHolder private constructor(private val binding: RvItemFormUploadImageBinding) : + ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): ViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = RvItemFormUploadImageBinding.inflate(layoutInflater, parent, false) + return FileUploadInputViewHolder(binding) + } + } + + fun bind(item: FormInputOld, isEnabled: Boolean) { + + } + + private lateinit var countDownTimer : CountDownTimer + private var countdownTimers : HashMap = HashMap() + + private fun formatTimeInSeconds(millis: Long) : String { + val seconds = millis / 1000 + return "${seconds} sec" + } + private fun startTimer(timerInSec: TextView, generateOtp: MaterialButton) { + countDownTimer = object : CountDownTimer(60000, 1000) { + override fun onTick(millisUntilFinished: Long) { + timerInSec.visibility = View.VISIBLE + timerInSec.text = formatTimeInSeconds(millisUntilFinished) + } + override fun onFinish() { + timerInSec.visibility = View.INVISIBLE + timerInSec.text = "" + generateOtp.isEnabled = true + generateOtp.text = timerInSec.resources.getString(R.string.resend_otp) + } + }.start() + + countdownTimers[adapterPosition] = countDownTimer + + } + } class RadioInputViewHolder private constructor(private val binding: RvItemFormRadioBinding) : ViewHolder(binding.root) { companion object { @@ -289,6 +426,10 @@ class FormInputAdapterOld( rdBtn.isChecked = true rdBtn.setOnCheckedChangeListener { _, b -> if (b) { + // Clear focus from any previously focused EditText to prevent auto-scroll + binding.root.rootView.findFocus()?.clearFocus() + val imm = binding.root.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(binding.root.windowToken, 0) item.value.value = it } item.errorText = null @@ -375,6 +516,11 @@ class FormInputAdapterOld( item.errorText?.also { binding.tilEditText.error = it } ?: run { binding.tilEditText.error = null } binding.et.setOnClickListener { + val activity = binding.et.context.findFragmentActivity() + ?: return@setOnClickListener + val originalLocale = Locale.getDefault() + HelperUtil.setEnLocaleForDatePicker(activity) + item.value.value?.let { value -> thisYear = value.substring(6).toInt() thisMonth = value.substring(3, 5).trim().toInt() - 1 @@ -390,10 +536,13 @@ class FormInputAdapterOld( ) item.errorText = null binding.tilEditText.error = null - datePickerDialog.datePicker.maxDate = item.max ?: 0 - datePickerDialog.datePicker.minDate = item.min ?: 0 + item.min?.let { datePickerDialog.datePicker.minDate = it } + item.max?.let { datePickerDialog.datePicker.maxDate = it } datePickerDialog.datePicker.touchables[0].performClick() datePickerDialog.show() + datePickerDialog.setOnDismissListener { + HelperUtil.setOriginalLocaleForDatePicker(activity,originalLocale) + } } binding.executePendingBindings() @@ -511,6 +660,151 @@ class FormInputAdapterOld( } + class NumberPickerInputViewHolder private constructor( + private val binding: RvItemFormNumberPickerBinding + ) : ViewHolder(binding.root) { + + private var textWatcher: TextWatcher? = null + private var internalUpdate = false + + companion object { + fun from(parent: ViewGroup): ViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = RvItemFormNumberPickerBinding.inflate(layoutInflater, parent, false) + return NumberPickerInputViewHolder(binding) + } + } + + fun bind( + item: FormElement, + isEnabled: Boolean, + formValueListener: FormInputAdapterOld.FormValueListener? + ) { + binding.form = item + + val minValue = item.min?.toInt() ?: 0 + val maxValue = item.max?.toInt() + val allowNegative = item.minDecimal != null && item.minDecimal!! < 0 + + binding.etNumberInput.setText(minValue.toString()) + binding.etNumberInput.setSelection(binding.etNumberInput.text!!.length) + var currentValue = item.value?.toIntOrNull() ?: minValue + + textWatcher?.let { binding.etNumberInput.removeTextChangedListener(it) } + + textWatcher = object : TextWatcher { + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} + + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { + + } + + + + + override fun afterTextChanged(editable: Editable?) { + + if (internalUpdate) return + if (editable.isNullOrBlank()) { + showError(binding.root.context.getString(R.string.value_cannot_be_empty)) + return + } + + val inputValue = editable.toString().toIntOrNull() + if (inputValue == null) { + showError(binding.root.context.getString(R.string.enter_a_valid_number)) + return + } + + val validated = validateValue(inputValue, minValue, maxValue, allowNegative) + + if (validated != inputValue) { + showError(binding.root.context.getString(R.string.allowed_range, minValue, maxValue)) + + updateDisplay(validated) + updateValue(validated, item, formValueListener) + return + } + + hideError() + + currentValue = validated + updateValue(currentValue, item, formValueListener) + } + } + + updateDisplay(currentValue) + + binding.etNumberInput.addTextChangedListener(textWatcher) + + + + binding.btnDecrement.setOnClickListener { + currentValue = (item.value?.toIntOrNull() ?: minValue) - 1 + currentValue = validateValue(currentValue, minValue, maxValue, allowNegative) + hideError() + updateValue(currentValue, item, formValueListener) + updateDisplay(currentValue) + } + + binding.btnIncrement.setOnClickListener { + currentValue = (item.value?.toIntOrNull() ?: minValue) + 1 + currentValue = validateValue(currentValue, minValue, maxValue, allowNegative) + hideError() + updateValue(currentValue, item, formValueListener) + updateDisplay(currentValue) + } + } + + + private fun validateValue(value: Int, min: Int, max: Int?, allowNegative: Boolean): Int { + var newValue = value + + if (!allowNegative && newValue < min) newValue = min + if (max != null && newValue > max) newValue = max + + return newValue + } + + private fun updateDisplay(value: Int) { + internalUpdate = true + binding.etNumberInput.setText(value.toString()) + binding.etNumberInput.setSelection(binding.etNumberInput.text!!.length) + internalUpdate = false + } + + private fun updateValue( + newValue: Int, + item: FormElement, + formValueListener: FormInputAdapterOld.FormValueListener? + ) { + item.value = newValue.toString() + formValueListener?.onValueChanged(item,newValue) + } + + private fun showError(message: String) { + binding.tvError.apply { + text = message + visibility = View.VISIBLE + } + } + + private fun hideError() { + binding.tvError.visibility = View.GONE + } + } + + + + class FormValueListener(private val valueChanged: (id: Int, value: Int) -> Unit) { + + fun onValueChanged(form: FormElement, index: Int) { + valueChanged(form.id, index) + + } + + } + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val inputTypes = values() @@ -525,6 +819,10 @@ class FormInputAdapterOld( TIME_PICKER -> TimePickerInputViewHolder.from(parent) HEADLINE -> HeadlineViewHolder.from(parent) AGE_PICKER -> FormInputAdapter.AgePickerViewInputViewHolder.from(parent) + BUTTON -> FormInputAdapter.ButtonInputViewHolder.from(parent) + FILE_UPLOAD -> FileUploadInputViewHolder.from(parent) + org.piramalswasthya.sakhi.model.InputType.MULTIFILE_UPLOAD -> MultiFileUploadInputViewHolder.from(parent) + NUMBER_PICKER ->NumberPickerInputViewHolder.from(parent) } } @@ -546,6 +844,11 @@ class FormInputAdapterOld( TIME_PICKER -> (holder as TimePickerInputViewHolder).bind(item, isEnabled) HEADLINE -> (holder as HeadlineViewHolder).bind(item) AGE_PICKER -> null + BUTTON -> (holder as ButtonInputViewHolder).bind(item, isEnabled) + FILE_UPLOAD -> (holder as FileUploadInputViewHolder).bind(item, isEnabled) + org.piramalswasthya.sakhi.model.InputType.MULTIFILE_UPLOAD -> (holder as FileUploadInputViewHolder).bind(item, isEnabled) + NUMBER_PICKER -> null + } } diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/FormInputAdapterWithBgIcon.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/FormInputAdapterWithBgIcon.kt new file mode 100644 index 000000000..38fc92e80 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/FormInputAdapterWithBgIcon.kt @@ -0,0 +1,1238 @@ +package org.piramalswasthya.sakhi.adapters + +import android.app.DatePickerDialog +import android.app.TimePickerDialog +import android.content.Context +import android.content.Context.INPUT_METHOD_SERVICE +import android.content.res.ColorStateList +import android.content.res.Resources +import android.graphics.Color +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import android.text.Editable +import android.text.Spannable +import android.text.SpannableString +import android.text.TextWatcher +import android.text.style.ForegroundColorSpan +import android.view.Gravity +import android.view.KeyEvent +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.inputmethod.InputMethodManager +import android.widget.CheckBox +import android.widget.LinearLayout +import android.widget.RadioButton +import android.widget.RadioGroup +import android.widget.TextView +import android.widget.Toast +import androidx.appcompat.content.res.AppCompatResources +import androidx.core.view.children +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import com.google.android.material.button.MaterialButton +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.databinding.LayoutMultiFileUploadBinding +import org.piramalswasthya.sakhi.databinding.RvItemFormAgePickerViewV2Binding +import org.piramalswasthya.sakhi.databinding.RvItemFormBtnBinding +import org.piramalswasthya.sakhi.databinding.RvItemFormCheckV2Binding +import org.piramalswasthya.sakhi.databinding.RvItemFormDatepickerWithBgIconBinding +import org.piramalswasthya.sakhi.databinding.RvItemFormDropdownWithBgIconBinding +import org.piramalswasthya.sakhi.databinding.RvItemFormEditTextWithBgIconBinding +import org.piramalswasthya.sakhi.databinding.RvItemFormHeadlineV2Binding +import org.piramalswasthya.sakhi.databinding.RvItemFormImageViewWithBgIconBinding +import org.piramalswasthya.sakhi.databinding.RvItemFormNumberPickerBinding +import org.piramalswasthya.sakhi.databinding.RvItemFormRadioWithBgIconBinding +import org.piramalswasthya.sakhi.databinding.RvItemFormTextViewWithBgIconBinding +import org.piramalswasthya.sakhi.databinding.RvItemFormTimepickerWithBgIconBinding +import org.piramalswasthya.sakhi.databinding.RvItemFormUploadImageBinding +import org.piramalswasthya.sakhi.helpers.Konstants +import org.piramalswasthya.sakhi.helpers.getDateString +import org.piramalswasthya.sakhi.model.AgeUnitDTO +import org.piramalswasthya.sakhi.model.FormElement +import org.piramalswasthya.sakhi.model.InputType +import org.piramalswasthya.sakhi.model.InputType.AGE_PICKER +import org.piramalswasthya.sakhi.model.InputType.BUTTON +import org.piramalswasthya.sakhi.model.InputType.CHECKBOXES +import org.piramalswasthya.sakhi.model.InputType.DATE_PICKER +import org.piramalswasthya.sakhi.model.InputType.DROPDOWN +import org.piramalswasthya.sakhi.model.InputType.EDIT_TEXT +import org.piramalswasthya.sakhi.model.InputType.FILE_UPLOAD +import org.piramalswasthya.sakhi.model.InputType.HEADLINE +import org.piramalswasthya.sakhi.model.InputType.IMAGE_VIEW +import org.piramalswasthya.sakhi.model.InputType.RADIO +import org.piramalswasthya.sakhi.model.InputType.TEXT_VIEW +import org.piramalswasthya.sakhi.model.InputType.TIME_PICKER +import org.piramalswasthya.sakhi.model.InputType.values +import org.piramalswasthya.sakhi.ui.home_activity.all_ben.new_ben_registration.AgePickerDialog +import org.piramalswasthya.sakhi.utils.HelperUtil +import org.piramalswasthya.sakhi.utils.HelperUtil.findFragmentActivity +import org.piramalswasthya.sakhi.utils.HelperUtil.getAgeStrFromAgeUnit +import org.piramalswasthya.sakhi.utils.HelperUtil.getDobFromAge +import org.piramalswasthya.sakhi.utils.HelperUtil.getLongFromDate +import org.piramalswasthya.sakhi.utils.HelperUtil.updateAgeDTO +import timber.log.Timber +import java.util.Calendar +import java.util.Locale + +class FormInputAdapterWithBgIcon ( + private val imageClickListener: ImageClickListener? = null, + private val ageClickListener: AgeClickListener? = null, + private val formValueListener: FormValueListener? = null, + private val selectImageClickListener: SelectUploadImageClickListener? = null, + private val viewDocumentListner: ViewDocumentOnClick? = null, + private val isEnabled: Boolean = true +) : ListAdapter(FormInputDiffCallBack) { + + object FormInputDiffCallBack : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: FormElement, newItem: FormElement) = + oldItem.id == newItem.id + + override fun areContentsTheSame(oldItem: FormElement, newItem: FormElement): Boolean { + Timber.d("${oldItem.id} ${oldItem.errorText} ${newItem.errorText}") + return oldItem.errorText == newItem.errorText + } + } + + class EditTextInputViewHolder private constructor(private val binding: RvItemFormEditTextWithBgIconBinding) : + ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): ViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = RvItemFormEditTextWithBgIconBinding.inflate(layoutInflater, parent, false) + return EditTextInputViewHolder(binding) + } + } + + fun bind(item: FormElement, isEnabled: Boolean, formValueListener: FormValueListener?) { + Timber.d("binding triggered!!! $isEnabled ${item.id}") + if (!isEnabled) { + binding.et.isClickable = false + binding.et.isFocusable = false + handleHintLength(item) + binding.form = item + binding.et.setText(item.value) + binding.executePendingBindings() + return + } else { + binding.et.isClickable = true + binding.et.isFocusable = true + } + val param = binding.tilEditText.layoutParams as ViewGroup.MarginLayoutParams +// param.setMargins(60,0,0,0) + binding.tilEditText.layoutParams = param + binding.form = item + if (item.errorText == null) binding.tilEditText.isErrorEnabled = false + Timber.d("Bound EditText item ${item.title} with ${item.required}") + binding.tilEditText.error = item.errorText + handleHintLength(item) + if (item.hasSpeechToText) { + binding.tilEditText.endIconDrawable = + AppCompatResources.getDrawable(binding.root.context, R.drawable.ic_mic) + binding.tilEditText.setEndIconOnClickListener { + formValueListener?.onValueChanged(item, Konstants.micClickIndex) + } + } else { + binding.tilEditText.endIconDrawable = null + binding.tilEditText.setEndIconOnClickListener(null) + } + + + val textWatcher = object : TextWatcher { + override fun beforeTextChanged( + s: CharSequence?, start: Int, count: Int, after: Int + ) { + } + + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { + } + + override fun afterTextChanged(editable: Editable?) { + + item.value = editable?.toString() + Timber.d("editable : $editable Current value : ${item.value} isNull: ${item.value == null} isEmpty: ${item.value == ""}") + formValueListener?.onValueChanged(item, -1) + if (item.errorText != binding.tilEditText.error) { + binding.tilEditText.isErrorEnabled = item.errorText != null + binding.tilEditText.error = item.errorText + } + + } + } + binding.et.setOnFocusChangeListener { _, hasFocus -> + if (hasFocus) binding.et.addTextChangedListener(textWatcher) + else { + binding.et.removeTextChangedListener(textWatcher) + val imm = + binding.root.context.getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager? + imm!!.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0) + } + } + binding.et.setOnKeyListener(View.OnKeyListener { v, keyCode, event -> + if (keyCode == KeyEvent.KEYCODE_ENTER && (event.action == KeyEvent.ACTION_UP || event.action == KeyEvent.ACTION_DOWN)) { + return@OnKeyListener true + } + false + }) + + binding.executePendingBindings() + } + + private fun handleHintLength(item: FormElement) { + if (item.title.length > Konstants.editTextHintLimit) { + binding.tvHint.visibility = View.VISIBLE + binding.et.hint = null + binding.tilEditText.hint = null + binding.tilEditText.isHintEnabled = false + } else { + binding.tvHint.visibility = View.GONE + binding.tilEditText.isHintEnabled = true + } + } + } + + class SelectUploadImageClickListener(private val selectImageClick: (formId: Int) -> Unit) { + + fun onSelectImageClick(form: FormElement) = selectImageClick(form.id) + + } + + class ViewDocumentOnClick(private val viewDocument: (formId: Int) -> Unit) { + + fun onViewDocumentClick(form: FormElement) = viewDocument(form.id) + + } + + + class DropDownInputViewHolder private constructor(private val binding: RvItemFormDropdownWithBgIconBinding) : + ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): ViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = RvItemFormDropdownWithBgIconBinding.inflate(layoutInflater, parent, false) + return DropDownInputViewHolder(binding) + } + } + + fun bind(item: FormElement, isEnabled: Boolean, formValueListener: FormValueListener?) { + binding.form = item + if (item.errorText == null) { + binding.tilRvDropdown.error = null + binding.tilRvDropdown.isErrorEnabled = false + } + if (!isEnabled) { + binding.tilRvDropdown.visibility = View.GONE +// binding.ivIconBg.visibility = View.GONE +// binding.ivIconTemperature.visibility = View.GONE + + binding.tilEditText.visibility = View.VISIBLE + binding.ivIconBg1.visibility = View.VISIBLE + binding.ivIconTemperature1.visibility = View.VISIBLE + + binding.et.isFocusable = false + binding.et.isClickable = false + binding.executePendingBindings() + return + } + + binding.actvRvDropdown.setOnItemClickListener { _, _, index, _ -> + item.value = item.entries?.get(index) + Timber.d("Item DD : $item") + formValueListener?.onValueChanged(item, index) + binding.tilRvDropdown.isErrorEnabled = item.errorText != null + binding.tilRvDropdown.error = item.errorText + } + + item.errorText?.let { binding.tilRvDropdown.error = it } + binding.executePendingBindings() + + } + } + + class RadioInputViewHolder private constructor(private val binding: RvItemFormRadioWithBgIconBinding) : + ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): ViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = RvItemFormRadioWithBgIconBinding.inflate(layoutInflater, parent, false) + return RadioInputViewHolder(binding) + } + } + + fun bind( + item: FormElement, isEnabled: Boolean, formValueListener: FormValueListener? + ) { + if (!isEnabled) { + binding.rg.isClickable = false + binding.rg.isFocusable = false + } + binding.invalidateAll() + binding.form = item + + binding.rg.removeAllViews() + + binding.rg.apply { + item.entries?.let { items -> + orientation = item.orientation ?: LinearLayout.HORIZONTAL + weightSum = items.size.toFloat() + val layoutParamsRg = binding.rg.layoutParams as ViewGroup.MarginLayoutParams + + items.forEach { + val rdBtn = RadioButton(this.context) + rdBtn.layoutParams = RadioGroup.LayoutParams( + RadioGroup.LayoutParams.WRAP_CONTENT, + RadioGroup.LayoutParams.WRAP_CONTENT, + 1.0F + + ).apply { + + if (item.id == 23 && item.title == " " || item.id == 9 && item.title == " ") { + val density = binding.root.resources.displayMetrics.density + layoutParamsRg.topMargin = (-25 * density).toInt() + gravity = Gravity.START + rdBtn.textSize = 16f + } else { + layoutParamsRg.topMargin = (0 * binding.root.resources.displayMetrics.density).toInt() + gravity = Gravity.CENTER_HORIZONTAL + rdBtn.textSize = 14f + } + + } + rdBtn.id = View.generateViewId() + val colorStateList = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + ColorStateList( + arrayOf( + intArrayOf(-android.R.attr.state_checked), + intArrayOf(android.R.attr.state_checked) + ), intArrayOf( + binding.root.resources.getColor( + android.R.color.darker_gray, + binding.root.context.theme + ), // disabled + binding.root.resources.getColor( + android.R.color.darker_gray, + binding.root.context.theme + ) // enabled + ) + ) + } else { + ColorStateList( + arrayOf( + intArrayOf(-android.R.attr.state_checked), + intArrayOf(android.R.attr.state_checked) + ), intArrayOf( + binding.root.resources.getColor( + android.R.color.darker_gray, + ), // disabled + binding.root.resources.getColor( + android.R.color.darker_gray, + ) // enabled + ) + ) + } + + if (!isEnabled) rdBtn.buttonTintList = colorStateList + rdBtn.text = it + addView(rdBtn) + if (item.value == it) rdBtn.isChecked = true + rdBtn.setOnCheckedChangeListener { _, b -> + if (b) { + // Clear focus from any previously focused EditText to prevent auto-scroll + binding.root.rootView.findFocus()?.clearFocus() + val imm = binding.root.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(binding.root.windowToken, 0) + item.value = it + if (item.hasDependants || item.hasAlertError) { + Timber.d( + "listener trigger : ${item.id} ${ + item.entries!!.indexOf( + it + ) + } $it" + ) + formValueListener?.onValueChanged( + item, item.entries!!.indexOf(it) + ) + } + } + item.errorText = null + binding.llContent.setBackgroundResource(0) + } + } + } + } + + if (!isEnabled) { + binding.rg.children.forEach { + it.isClickable = false + } + } + if (item.errorText != null) binding.llContent.setBackgroundResource(R.drawable.state_errored) + else binding.llContent.setBackgroundResource(0) + + binding.executePendingBindings() + val str = binding.tvNullable.text + val spannableString = SpannableString(str) + + val colorSpan = ForegroundColorSpan(Color.parseColor("#B00020")) + + if (item.required && item.doubleStar) { + spannableString.setSpan( + colorSpan, + str.length - 2, + str.length, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + binding.tvNullable.text = spannableString + binding.tvNullableHr.text = spannableString + } else if (item.required) { + spannableString.setSpan( + colorSpan, + str.length - 1, + str.length, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + binding.tvNullableHr.text = spannableString + binding.tvNullable.text = spannableString + } + + } + } + + class CheckBoxesInputViewHolder private constructor(private val binding: RvItemFormCheckV2Binding) : + ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): ViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = RvItemFormCheckV2Binding.inflate(layoutInflater, parent, false) + return CheckBoxesInputViewHolder(binding) + } + } + + fun bind( + item: FormElement, isEnabled: Boolean, formValueListener: FormValueListener? + ) { + binding.form = item + + if (item.errorText != null) binding.clRi.setBackgroundResource(R.drawable.state_errored) + else binding.clRi.setBackgroundResource(0) + binding.llChecks.removeAllViews() + binding.llChecks.apply { + item.entries?.let { items -> + orientation = item.orientation ?: LinearLayout.VERTICAL + weightSum = items.size.toFloat() + items.forEachIndexed { index, it -> + val cbx = CheckBox(this.context) + cbx.layoutParams = RadioGroup.LayoutParams( + RadioGroup.LayoutParams.MATCH_PARENT, + RadioGroup.LayoutParams.WRAP_CONTENT, + 1.0F + ) + if (!isEnabled) { + cbx.isClickable = false + cbx.isFocusable = false + } + cbx.id = View.generateViewId() + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) cbx.setTextAppearance( + context, android.R.style.TextAppearance_Material_Medium + ) + else cbx.setTextAppearance(android.R.style.TextAppearance_Material_Subhead) + cbx.text = it + addView(cbx) + if (item.value?.contains(it) == true) cbx.isChecked = true + cbx.setOnCheckedChangeListener { _, b -> + if (b) { + if (item.value != null) item.value = item.value + it + else item.value = it + if (item.hasDependants || item.hasAlertError) { + Timber.d( + "listener trigger : ${item.id} ${ + item.entries!!.indexOf( + it + ) + } $it" + ) + } + } else { + if (item.value?.contains(it) == true) { + item.value = item.value?.replace(it, "") + } + } + formValueListener?.onValueChanged( + item, index * (if (b) 1 else -1) + ) + if (item.value.isNullOrBlank()) { + item.value = null + } else { + Timber.d("Called here!") + item.errorText = null + binding.clRi.setBackgroundResource(0) + } + Timber.d("Checkbox value : ${item.value}") + + } + } + } + } + binding.executePendingBindings() + + } + } + + class DatePickerInputViewHolder private constructor(private val binding: RvItemFormDatepickerWithBgIconBinding) : + ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): ViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = RvItemFormDatepickerWithBgIconBinding.inflate(layoutInflater, parent, false) + return DatePickerInputViewHolder(binding) + } + } + + fun bind( + item: FormElement, isEnabled: Boolean, formValueListener: FormValueListener? + ) { + binding.form = item + binding.invalidateAll() + if (!isEnabled) { + binding.et.isFocusable = false + binding.et.isClickable = false + binding.executePendingBindings() + return + } + val today = Calendar.getInstance() + var thisYear = today.get(Calendar.YEAR) + var thisMonth = today.get(Calendar.MONTH) + var thisDay = today.get(Calendar.DAY_OF_MONTH) + + item.errorText?.also { binding.tilEditText.error = it } + ?: run { binding.tilEditText.error = null } + binding.et.setOnClickListener { + val activity = binding.et.context.findFragmentActivity() + ?: return@setOnClickListener + val originalLocale = Locale.getDefault() + HelperUtil.setEnLocaleForDatePicker(activity) + + item.value?.let { value -> + thisYear = value.substring(6).toInt() + thisMonth = value.substring(3, 5).trim().toInt() - 1 + thisDay = value.substring(0, 2).trim().toInt() + } + val datePickerDialog = DatePickerDialog( + it.context, { _, year, month, day -> + val millis = Calendar.getInstance().apply { + set(Calendar.YEAR, year) + set(Calendar.MONTH, month) + set(Calendar.DAY_OF_MONTH, day) + }.timeInMillis + if (item.min != null && millis < item.min!!) { + item.value = getDateString(item.min) + } else if (item.max != null && millis > item.max!!) + item.value = getDateString(item.max) + else + item.value = getDateString(millis) +// "${if (day > 9) day else "0$day"}-${if (month > 8) month + 1 else "0${month + 1}"}-$year" + binding.invalidateAll() + if (item.hasDependants) formValueListener?.onValueChanged(item, -1) + }, thisYear, thisMonth, thisDay + ) + item.errorText = null + binding.tilEditText.error = null + item.min?.let { datePickerDialog.datePicker.minDate = it } + item.max?.let { datePickerDialog.datePicker.maxDate = it } + if (item.showYearFirstInDatePicker) + datePickerDialog.datePicker.touchables[0].performClick() + val canShow = when { + item.max == null || item.min == null -> true + else -> item.max!! > item.min!! + } + if (canShow){ + datePickerDialog.show() + }else{ + Toast.makeText(binding.root.context,"Something went wrong",Toast.LENGTH_SHORT).show() + HelperUtil.setOriginalLocaleForDatePicker(activity,originalLocale) + } + datePickerDialog.setOnDismissListener { + HelperUtil.setOriginalLocaleForDatePicker(activity,originalLocale) + } + } + binding.executePendingBindings() + + } + } + + class TimePickerInputViewHolder private constructor(private val binding: RvItemFormTimepickerWithBgIconBinding) : + ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): ViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = RvItemFormTimepickerWithBgIconBinding.inflate(layoutInflater, parent, false) + return TimePickerInputViewHolder(binding) + } + } + + fun bind( + item: FormElement, isEnabled: Boolean + ) { + binding.form = item + binding.et.isEnabled = isEnabled + binding.et.setOnClickListener { + val hour: Int + val minute: Int + if (item.value == null) { + val currentTime = Calendar.getInstance() + hour = currentTime.get(Calendar.HOUR_OF_DAY) + minute = currentTime.get(Calendar.MINUTE) + } else { + hour = item.value!!.substringBefore(":").toInt() + minute = item.value!!.substringAfter(":").toInt() + Timber.d("Time picker hour min : $hour $minute") + } + val mTimePicker = TimePickerDialog(it.context, { _, hourOfDay, minuteOfHour -> + item.value = "$hourOfDay:$minuteOfHour" + binding.invalidateAll() + + }, hour, minute, false) + mTimePicker.setTitle("Select Time") + mTimePicker.show() + } + binding.executePendingBindings() + + } + } + + class TextViewInputViewHolder private constructor(private val binding: RvItemFormTextViewWithBgIconBinding) : + ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): ViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = RvItemFormTextViewWithBgIconBinding.inflate(layoutInflater, parent, false) + return TextViewInputViewHolder(binding) + } + } + + fun bind(item: FormElement) { + binding.form = item + binding.executePendingBindings() + } + } + + class ImageViewInputViewHolder private constructor(private val binding: RvItemFormImageViewWithBgIconBinding) : + ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): ViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = RvItemFormImageViewWithBgIconBinding.inflate(layoutInflater, parent, false) + return ImageViewInputViewHolder(binding) + } + } + + fun bind( + item: FormElement, clickListener: ImageClickListener?, isEnabled: Boolean + ) { + binding.form = item + if (isEnabled) { + binding.clickListener = clickListener + if (item.errorText != null) binding.clRi.setBackgroundResource(R.drawable.state_errored) + else binding.clRi.setBackgroundResource(0) + } + binding.executePendingBindings() + + } + } + + class ButtonInputViewHolder private constructor(private val binding: RvItemFormBtnBinding) : + ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): ViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = RvItemFormBtnBinding.inflate(layoutInflater, parent, false) + return ButtonInputViewHolder(binding) + } + } + + fun bind(item: FormElement, isEnabled: Boolean, formValueListener: FormValueListener?) { + binding.form = item + binding.generateOtp.isEnabled = isEnabled + binding.generateOtp.text = binding.generateOtp.resources.getString(R.string.generate_otp) + binding.generateOtp.setOnClickListener { + binding.generateOtp.isEnabled = !isEnabled + startTimer(binding.timerInSec,binding.generateOtp) + binding.tilEditText.visibility = View.VISIBLE + } + + + } + + private lateinit var countDownTimer : CountDownTimer + private var countdownTimers : HashMap = HashMap() + + class MultiFileUploadInputViewHolder private constructor(private val binding: LayoutMultiFileUploadBinding) : + ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): ViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = LayoutMultiFileUploadBinding.inflate(layoutInflater, parent, false) + return MultiFileUploadInputViewHolder(binding) + } + } + var selectedFiles = mutableListOf() + + private lateinit var fileAdapter: FileListAdapter + + fun bind( + item: FormElement, + clickListener: SelectUploadImageClickListener?, + documentOnClick: ViewDocumentOnClick?, + isEnabled: Boolean + ) { + /* binding.form = item + binding.tvTitle.text = item.title + binding.clickListener = clickListener + binding.documentclickListener = documentOnClick + binding.btnView.visibility = if (item.value != null) View.VISIBLE else View.GONE + + if (isEnabled) { + binding.addFile.isEnabled = true + binding.addFile.alpha = 1f + } else { + binding.addFile.isEnabled = false + binding.addFile.alpha = 0.5f + }*/ + + fileAdapter = FileListAdapter(selectedFiles) + binding.rvFiles.adapter = fileAdapter + + binding.btnSelectFiles.isEnabled = isEnabled + binding.btnSelectFiles.alpha = if (isEnabled) 1f else 0.5f + + binding.btnSelectFiles.setOnClickListener { + clickListener?.onSelectImageClick(item) + } + } + + } + private fun formatTimeInSeconds(millis: Long) : String { + val seconds = millis / 1000 + return "${seconds} sec" + } + private fun startTimer(timerInSec: TextView, generateOtp: MaterialButton) { + countDownTimer = object : CountDownTimer(60000, 1000) { + override fun onTick(millisUntilFinished: Long) { + timerInSec.visibility = View.VISIBLE + timerInSec.text = formatTimeInSeconds(millisUntilFinished) + } + override fun onFinish() { + timerInSec.visibility = View.INVISIBLE + timerInSec.text = "" + generateOtp.isEnabled = true + generateOtp.text = timerInSec.resources.getString(R.string.resend_otp) + } + }.start() + + countdownTimers[adapterPosition] = countDownTimer + + } + } + + class MultiFileUploadInputViewHolder private constructor(private val binding: LayoutMultiFileUploadBinding) : + ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): ViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = LayoutMultiFileUploadBinding.inflate(layoutInflater, parent, false) + return MultiFileUploadInputViewHolder(binding) + } + } + var selectedFiles = mutableListOf() + + private lateinit var fileAdapter: FileListAdapter + + fun bind( + item: FormElement, + clickListener: SelectUploadImageClickListener?, + documentOnClick: ViewDocumentOnClick?, + isEnabled: Boolean, + ) { + /* binding.form = item + binding.tvTitle.text = item.title + binding.clickListener = clickListener + binding.documentclickListener = documentOnClick + binding.btnView.visibility = if (item.value != null) View.VISIBLE else View.GONE + + if (isEnabled) { + binding.addFile.isEnabled = true + binding.addFile.alpha = 1f + } else { + binding.addFile.isEnabled = false + binding.addFile.alpha = 0.5f + }*/ + + fileAdapter = FileListAdapter(selectedFiles) + binding.rvFiles.adapter = fileAdapter + + binding.btnSelectFiles.isEnabled = isEnabled + binding.btnSelectFiles.alpha = if (isEnabled) 1f else 0.5f + + binding.btnSelectFiles.setOnClickListener { + clickListener?.onSelectImageClick(item) + } + } + + } + + class FileUploadInputViewHolder private constructor(private val binding: RvItemFormUploadImageBinding) : + ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): ViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = RvItemFormUploadImageBinding.inflate(layoutInflater, parent, false) + return FileUploadInputViewHolder(binding) + } + } + + fun bind(item: FormElement, clickListener: SelectUploadImageClickListener?, documentOnClick: ViewDocumentOnClick?, isEnabled: Boolean) { + + binding.form = item + binding.tvTitle.text = item.title + binding.clickListener = clickListener + binding.documentclickListener = documentOnClick + if (item.value != null) binding.btnView.visibility = View.VISIBLE + else binding.btnView.visibility = View.GONE + + } + + private lateinit var countDownTimer : CountDownTimer + private var countdownTimers : HashMap = HashMap() + + private fun formatTimeInSeconds(millis: Long) : String { + val seconds = millis / 1000 + return "${seconds} sec" + } + private fun startTimer(timerInSec: TextView, generateOtp: MaterialButton) { + countDownTimer = object : CountDownTimer(60000, 1000) { + override fun onTick(millisUntilFinished: Long) { + timerInSec.visibility = View.VISIBLE + timerInSec.text = formatTimeInSeconds(millisUntilFinished) + } + override fun onFinish() { + timerInSec.visibility = View.INVISIBLE + timerInSec.text = "" + generateOtp.isEnabled = true + generateOtp.text = timerInSec.resources.getString(R.string.resend_otp) + } + }.start() + + countdownTimers[adapterPosition] = countDownTimer + + } + } + + class NumberPickerInputViewHolder private constructor( + private val binding: RvItemFormNumberPickerBinding + ) : ViewHolder(binding.root) { + + private var textWatcher: TextWatcher? = null + private var internalUpdate = false + + companion object { + fun from(parent: ViewGroup): ViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = RvItemFormNumberPickerBinding.inflate(layoutInflater, parent, false) + return NumberPickerInputViewHolder(binding) + } + } + + fun bind( + item: FormElement, + isEnabled: Boolean, + formValueListener: FormInputAdapterWithBgIcon.FormValueListener? + ) { + binding.form = item + + val minValue = item.min?.toInt() ?: 0 + val maxValue = item.max?.toInt() + val allowNegative = item.minDecimal != null && item.minDecimal!! < 0 + + binding.etNumberInput.setText(minValue.toString()) + binding.etNumberInput.setSelection(binding.etNumberInput.text!!.length) + var currentValue = item.value?.toIntOrNull() ?: minValue + + textWatcher?.let { binding.etNumberInput.removeTextChangedListener(it) } + + textWatcher = object : TextWatcher { + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} + + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { + + } + + + + + override fun afterTextChanged(editable: Editable?) { + + if (internalUpdate) return + if (editable.isNullOrBlank()) { + showError(binding.root.context.getString(R.string.value_cannot_be_empty)) + return + } + + val inputValue = editable.toString().toIntOrNull() + if (inputValue == null) { + showError(binding.root.context.getString(R.string.enter_a_valid_number)) + return + } + + val validated = validateValue(inputValue, minValue, maxValue, allowNegative) + + if (validated != inputValue) { + showError(binding.root.context.getString(R.string.allowed_range, minValue, maxValue)) + + updateDisplay(validated) + updateValue(validated, item, formValueListener) + return + } + + hideError() + + currentValue = validated + updateValue(currentValue, item, formValueListener) + } + } + + updateDisplay(currentValue) + + binding.etNumberInput.addTextChangedListener(textWatcher) + + + + binding.btnDecrement.setOnClickListener { + currentValue = (item.value?.toIntOrNull() ?: minValue) - 1 + currentValue = validateValue(currentValue, minValue, maxValue, allowNegative) + hideError() + updateValue(currentValue, item, formValueListener) + updateDisplay(currentValue) + } + + binding.btnIncrement.setOnClickListener { + currentValue = (item.value?.toIntOrNull() ?: minValue) + 1 + currentValue = validateValue(currentValue, minValue, maxValue, allowNegative) + hideError() + updateValue(currentValue, item, formValueListener) + updateDisplay(currentValue) + } + } + + + private fun validateValue(value: Int, min: Int, max: Int?, allowNegative: Boolean): Int { + var newValue = value + + if (!allowNegative && newValue < min) newValue = min + if (max != null && newValue > max) newValue = max + + return newValue + } + + private fun updateDisplay(value: Int) { + internalUpdate = true + binding.etNumberInput.setText(value.toString()) + binding.etNumberInput.setSelection(binding.etNumberInput.text!!.length) + internalUpdate = false + } + + private fun updateValue( + newValue: Int, + item: FormElement, + formValueListener: FormInputAdapterWithBgIcon.FormValueListener? + ) { + item.value = newValue.toString() + formValueListener?.onValueChanged(item,newValue) + } + + private fun showError(message: String) { + binding.tvError.apply { + text = message + visibility = View.VISIBLE + } + } + + private fun hideError() { + binding.tvError.visibility = View.GONE + } + } + + + + class AgePickerViewInputViewHolder private constructor(private val binding: RvItemFormAgePickerViewV2Binding) : + ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): ViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = + RvItemFormAgePickerViewV2Binding.inflate(layoutInflater, parent, false) + return AgePickerViewInputViewHolder(binding) + } + } + + fun bind( + item: FormElement, isEnabled: Boolean, formValueListener: FormValueListener? + ) { + binding.form = item + binding.invalidateAll() + val agePicker = AgePickerDialog(binding.root.context) + + val calDob = Calendar.getInstance() + val ageUnitDTO = AgeUnitDTO(0, 0, 0) + val isOk = true + item.value?.let { + calDob.timeInMillis = getLongFromDate(it) + updateAgeDTO(ageUnitDTO, calDob) + binding.etNum.setText(getAgeStrFromAgeUnit(ageUnitDTO)) + + } + + if (isEnabled) { + binding.etNum.setOnClickListener { + val calNow = Calendar.getInstance() + val calMin = Calendar.getInstance() + val calMax = Calendar.getInstance() + item.min?.let { + calMin.timeInMillis = it + } + item.max?.let { + calMax.timeInMillis = it + } + + agePicker.setLimitsAndShow( + calNow.get(Calendar.YEAR) - calMax.get(Calendar.YEAR), + calNow.get(Calendar.YEAR) - calMin.get(Calendar.YEAR), + 0, + 11, + 0, + 30, + ageUnitDTO, + isOk + ) + } + agePicker.setOnDismissListener { + binding.etNum.setText(getAgeStrFromAgeUnit(ageUnitDTO)) + calDob.timeInMillis = + getDobFromAge(ageUnitDTO) + binding.etDate.setText(getDateString(calDob.timeInMillis)) + item.value = getDateString(calDob.timeInMillis) + if (item.hasDependants) formValueListener?.onValueChanged(item, -1) + } + } + + + if (!isEnabled) { + binding.etDate.isFocusable = false + binding.etNum.isFocusable = false + binding.etDate.isClickable = false + binding.etNum.isClickable = false + binding.executePendingBindings() + return + } + val today = Calendar.getInstance() + var thisYear = today.get(Calendar.YEAR) + var thisMonth = today.get(Calendar.MONTH) + var thisDay = today.get(Calendar.DAY_OF_MONTH) + + item.errorText?.also { binding.tilEditTextDate.error = it } + ?: run { binding.tilEditTextDate.error = null } + binding.etDate.setOnClickListener { + val activity = binding.etDate.context.findFragmentActivity() + ?: return@setOnClickListener + val originalLocale = Locale.getDefault() + HelperUtil.setEnLocaleForDatePicker(activity) + + item.value?.let { value -> + thisYear = value.substring(6).toInt() + thisMonth = value.substring(3, 5).trim().toInt() - 1 + thisDay = value.substring(0, 2).trim().toInt() + } + val datePickerDialog = DatePickerDialog( + it.context, { _, year, month, day -> + val millisCal = Calendar.getInstance().apply { + set(Calendar.YEAR, year) + set(Calendar.MONTH, month) + set(Calendar.DAY_OF_MONTH, day) + } + val millis = millisCal.timeInMillis + if (item.min != null && millis < item.min!!) { + item.value = getDateString(item.min) + } else if (item.max != null && millis > item.max!!) + item.value = getDateString(item.max) + else + item.value = getDateString(millis) + + updateAgeDTO(ageUnitDTO, millisCal) + binding.etNum.setText(getAgeStrFromAgeUnit(ageUnitDTO)) + binding.invalidateAll() + if (item.hasDependants) formValueListener?.onValueChanged(item, -1) + }, thisYear, thisMonth, thisDay + ) + item.errorText = null + binding.tilEditTextDate.error = null + item.min?.let { datePickerDialog.datePicker.minDate = it } + item.max?.let { datePickerDialog.datePicker.maxDate = it } + if (item.showYearFirstInDatePicker) + datePickerDialog.datePicker.touchables[0].performClick() + datePickerDialog.show() + datePickerDialog.setOnDismissListener { + HelperUtil.setOriginalLocaleForDatePicker(activity,originalLocale) + } + } + binding.executePendingBindings() + + } + + } + + class HeadlineViewHolder private constructor(private val binding: RvItemFormHeadlineV2Binding) : + ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): ViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = RvItemFormHeadlineV2Binding.inflate(layoutInflater, parent, false) + return HeadlineViewHolder(binding) + } + } + + fun bind( + item: FormElement, + formValueListener: FormValueListener?, + ) { + binding.form = item + if (item.subtitle == null) + binding.textView8.visibility = View.GONE + formValueListener?.onValueChanged(item, -1) + binding.executePendingBindings() + + } + } + + class ImageClickListener(private val imageClick: (formId: Int) -> Unit) { + + fun onImageClick(form: FormElement) = imageClick(form.id) + + } + + class AgeClickListener(private val ageClick: (formId: Int) -> Unit) { + + fun onAgeClick(form: FormElement) = ageClick(form.id) + + } + + class FormValueListener(private val valueChanged: (id: Int, value: Int) -> Unit) { + + fun onValueChanged(form: FormElement, index: Int) { + valueChanged(form.id, index) + + } + + } + + + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val inputTypes = values() + return when (inputTypes[viewType]) { + EDIT_TEXT -> EditTextInputViewHolder.from(parent) + DROPDOWN -> DropDownInputViewHolder.from(parent) + RADIO -> RadioInputViewHolder.from(parent) + DATE_PICKER -> DatePickerInputViewHolder.from(parent) + TEXT_VIEW -> TextViewInputViewHolder.from(parent) + IMAGE_VIEW -> ImageViewInputViewHolder.from(parent) + CHECKBOXES -> CheckBoxesInputViewHolder.from(parent) + TIME_PICKER -> TimePickerInputViewHolder.from(parent) + HEADLINE -> HeadlineViewHolder.from(parent) + AGE_PICKER -> AgePickerViewInputViewHolder.from(parent) + BUTTON -> ButtonInputViewHolder.from(parent) + FILE_UPLOAD -> FileUploadInputViewHolder.from(parent) + InputType.NUMBER_PICKER -> NumberPickerInputViewHolder.from(parent) + InputType.MULTIFILE_UPLOAD -> MultiFileUploadInputViewHolder.from(parent) + + + } + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val item = getItem(position) + val isEnabled = if (isEnabled) item.isEnabled else false + when (item.inputType) { + EDIT_TEXT -> (holder as EditTextInputViewHolder).bind( + item, isEnabled, formValueListener + ) + + DROPDOWN -> (holder as DropDownInputViewHolder).bind(item, isEnabled, formValueListener) + RADIO -> (holder as RadioInputViewHolder).bind(item, isEnabled, formValueListener) + DATE_PICKER -> (holder as DatePickerInputViewHolder).bind( + item, isEnabled, formValueListener + ) + + TEXT_VIEW -> (holder as TextViewInputViewHolder).bind(item) + IMAGE_VIEW -> (holder as ImageViewInputViewHolder).bind( + item, imageClickListener, isEnabled + ) + + CHECKBOXES -> (holder as CheckBoxesInputViewHolder).bind( + item, + isEnabled, + formValueListener + ) + + TIME_PICKER -> (holder as TimePickerInputViewHolder).bind(item, isEnabled) + HEADLINE -> (holder as HeadlineViewHolder).bind(item, formValueListener) + AGE_PICKER -> (holder as AgePickerViewInputViewHolder).bind( + item, + isEnabled, + formValueListener + ) + BUTTON -> (holder as ButtonInputViewHolder).bind(item, isEnabled,formValueListener) + FILE_UPLOAD -> (holder as FileUploadInputViewHolder).bind(item,selectImageClickListener,viewDocumentListner, isEnabled) + InputType.MULTIFILE_UPLOAD -> (holder as MultiFileUploadInputViewHolder).bind(item,selectImageClickListener,viewDocumentListner, isEnabled ) + + InputType.NUMBER_PICKER -> (holder as NumberPickerInputViewHolder).bind( + item, isEnabled, formValueListener + ) + } + } + + override fun getItemViewType(position: Int) = getItem(position).inputType.ordinal + + /** + * Validation Result : -1 -> all good + * else index of element creating trouble + */ + fun validateInput(resources: Resources): Int { + var retVal = -1 + if (!isEnabled) return retVal + currentList.forEachIndexed { index, it -> + Timber.d("Error text for ${it.title} ${it.errorText}") + if (it.inputType != TEXT_VIEW && it.errorText != null) { + retVal = index + return@forEachIndexed + } + } + Timber.d("Validation : $retVal") + if (retVal != -1) return retVal + currentList.forEachIndexed { index, it -> + if (it.inputType != TEXT_VIEW && it.required) { + if (it.value.isNullOrBlank()) { + Timber.d("validateInput called for item $it, with index ${index}") + it.errorText = resources.getString(R.string.form_input_empty_error) + notifyItemChanged(index) + if (retVal == -1) retVal = index + } + } + } + return retVal + } + + + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/GeneralOPDAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/GeneralOPDAdapter.kt new file mode 100644 index 000000000..3abb97bb1 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/GeneralOPDAdapter.kt @@ -0,0 +1,124 @@ +package org.piramalswasthya.sakhi.adapters +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.databinding.RvGeneralOpdBenBinding +import org.piramalswasthya.sakhi.model.BenBasicDomain +import org.piramalswasthya.sakhi.model.GeneralOPEDBeneficiary + +class GeneralOPDAdapter( + private val clickListener: CallClickListener? = null, + private val showBeneficiaries: Boolean = false, + private val showRegistrationDate: Boolean = false, + private val showSyncIcon: Boolean = false, + private val showAbha: Boolean = false, + private val role: Int? = 0 +) : + ListAdapter(BenDiffUtilCallBack) { + private object BenDiffUtilCallBack : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: GeneralOPEDBeneficiary, newItem: GeneralOPEDBeneficiary + ) = oldItem.beneficiaryId == newItem.beneficiaryId + + override fun areContentsTheSame( + oldItem: GeneralOPEDBeneficiary, newItem: GeneralOPEDBeneficiary + ) = oldItem == newItem + + } + + class BenViewHolder private constructor(private val binding: RvGeneralOpdBenBinding) : + RecyclerView.ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): BenViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = RvGeneralOpdBenBinding.inflate(layoutInflater, parent, false) + return BenViewHolder(binding) + } + } + + fun bind( + item: GeneralOPEDBeneficiary, + clickListener: CallClickListener?, + showAbha: Boolean, + showSyncIcon: Boolean, + showRegistrationDate: Boolean, + showBeneficiaries: Boolean, role: Int? + ) { +// if (!showSyncIcon) item.syncState = null + binding.ben = item + binding.callclickListener = clickListener + binding.showAbha = showAbha + binding.showRegistrationDate = showRegistrationDate + binding.registrationDate.visibility = + if (showRegistrationDate) View.VISIBLE else View.INVISIBLE +// binding.hasAbha = !item.abhaId.isNullOrEmpty() + binding.role = role + + if (showBeneficiaries) { + if (item.spouseName == "Not Available" && item.fatherName == "Not Available") { + binding.father = true + binding.husband = false + binding.spouse = false + } else { + if (item.genderName == "MALE") { + binding.father = true + binding.husband = false + binding.spouse = false + } else if (item.genderName == "FEMALE") { + if (item.ben_age_val!! > 15) { + binding.father = + item.fatherName != "Not Available" && item.spouseName == "Not Available" + binding.husband = item.spouseName != "Not Available" + binding.spouse = false + } else { + binding.father = true + binding.husband = false + binding.spouse = false + } + } else { + binding.father = + item.fatherName != "Not Available" && item.spouseName == "Not Available" + binding.spouse = item.spouseName != "Not Available" + binding.husband = false + } + } + } else { + binding.father = false + binding.husband = false + binding.spouse = false + } + binding.executePendingBindings() + + } + } + + override fun onCreateViewHolder( + parent: ViewGroup, viewType: Int + ) = BenViewHolder.from(parent) + + override fun onBindViewHolder(holder: BenViewHolder, position: Int) { + holder.bind( + getItem(position), + clickListener, + showAbha, + showSyncIcon, + showRegistrationDate, + showBeneficiaries, + role + ) + } + + + class CallClickListener( + private val clickedCall: (benId: Long, mobileno: String) -> Unit, + ) { + + fun onClickCall(item: GeneralOPEDBeneficiary) = clickedCall(item.beneficiaryId, + item.preferredPhoneNum.toString() + ) + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/HRPAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/HRPAdapter.kt index 640e5724f..e205cc15e 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/adapters/HRPAdapter.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/HRPAdapter.kt @@ -4,16 +4,21 @@ import android.os.Build import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.LinearLayout import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao import org.piramalswasthya.sakhi.databinding.RvItemBenWithHrpaFormBinding import org.piramalswasthya.sakhi.model.BenWithHRPADomain +import org.piramalswasthya.sakhi.utils.RoleConstants class HRPAdapter( private val clickListener: HRPAClickListener? = null, private vararg val formButtonText: String, - private val role: Int? = 0 + private val role: Int? = 0, + private val pref: PreferenceDao? = null ) : ListAdapter (BenDiffUtilCallBack) { @@ -44,8 +49,20 @@ class HRPAdapter( item: BenWithHRPADomain, clickListener: HRPAClickListener?, vararg btnText: String, - role: Int? + role: Int?, + pref: PreferenceDao? ) { + + if (pref?.getLoggedInUser()?.role.equals(RoleConstants.ROLE_ASHA_SUPERVISOR, true)) { + binding.btnForm3.visibility = View.INVISIBLE + binding.btnForm2.visibility = View.INVISIBLE + binding.btnForm1.visibility = View.INVISIBLE + } else { + binding.btnForm3.visibility = View.VISIBLE + binding.btnForm2.visibility = View.VISIBLE + binding.btnForm1.visibility = View.VISIBLE + } + binding.benWithhrpa = item binding.hasLmp = false item.assess?.let { @@ -82,6 +99,10 @@ class HRPAdapter( var hasForm: Boolean = false var completelyFilled: Boolean = false var formEnabled: Boolean = false + buttonFlexibleWidth() + binding.btnForm3.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.icon_whatsapp, 0); + + val formButton = when (formNumber) { 1 -> { binding.btnForm1.also { @@ -111,14 +132,27 @@ class HRPAdapter( 3 -> { binding.btnForm3.also { -// hasForm = item.assess.noOfDeliveries.form3Filled -// formEnabled = item.ben.form3Enabled + hasForm = item.mbp != null + if (hasForm) { + + formEnabled = true + buttonFlexibleWidth() + + } else { + + formEnabled = false + (binding.btnForm2.layoutParams as LinearLayout.LayoutParams).weight=1.8f + (binding.btnForm1.layoutParams as LinearLayout.LayoutParams).weight=1.2f + + } + } } else -> throw IllegalStateException("FormNumber>3") } - formButton.visibility = if (formEnabled) View.VISIBLE else View.INVISIBLE + formButton.visibility = if (formEnabled) View.VISIBLE else View.GONE + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (hasForm) { @@ -137,11 +171,17 @@ class HRPAdapter( ) ) } + binding.btnForm3.setBackgroundColor( + binding.btnForm3.resources.getColor( + android.R.color.holo_purple, + binding.root.context.theme + ) + ) } else { formButton.setBackgroundColor( binding.root.resources.getColor( - android.R.color.holo_red_light, + android.R.color.holo_red_dark, binding.root.context.theme ) ) @@ -153,7 +193,7 @@ class HRPAdapter( else formButton.setBackgroundColor( binding.root.resources.getColor( - android.R.color.holo_red_light, + android.R.color.holo_red_dark, ) ) @@ -165,6 +205,12 @@ class HRPAdapter( ) } + + private fun buttonFlexibleWidth() { + (binding.btnForm2.layoutParams as LinearLayout.LayoutParams).weight=1.2f + (binding.btnForm1.layoutParams as LinearLayout.LayoutParams).weight=0.8f + (binding.btnForm3.layoutParams as LinearLayout.LayoutParams).weight=1.0f + } } override fun onCreateViewHolder( @@ -174,7 +220,7 @@ class HRPAdapter( HRPAViewHolder.from(parent) override fun onBindViewHolder(holder: HRPAViewHolder, position: Int) { - holder.bind(getItem(position), clickListener, btnText = formButtonText, role = role) + holder.bind(getItem(position), clickListener, btnText = formButtonText, role = role, pref = pref) } @@ -185,7 +231,6 @@ class HRPAdapter( private val clickedForm3: ((hhId: Long, benId: Long) -> Unit)? = null ) { - // fun onClickedBen(item: HRPAViewHolder) = clickedBen?.let { it() }(item.benId) fun onClickForm1(item: BenWithHRPADomain) = clickedForm1?.let { it(item.ben.hhId, item.ben.benId) } diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/HouseHoldListAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/HouseHoldListAdapter.kt index 53f7bc004..31637bc11 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/adapters/HouseHoldListAdapter.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/HouseHoldListAdapter.kt @@ -1,18 +1,27 @@ package org.piramalswasthya.sakhi.adapters +import android.content.res.ColorStateList import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup +import androidx.core.content.ContextCompat import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.configuration.IconDataset +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao import org.piramalswasthya.sakhi.databinding.RvItemHouseholdBinding import org.piramalswasthya.sakhi.model.HouseHoldBasicDomain +import org.piramalswasthya.sakhi.ui.getTitle +import org.piramalswasthya.sakhi.ui.getTitleRes -class HouseHoldListAdapter(private val clickListener: HouseholdClickListener) : +class HouseHoldListAdapter(private val diseaseType: String, private var isDisease: Boolean, val pref: PreferenceDao,private val isSoftDeleteEnabled:Boolean = false, private val clickListener: HouseholdClickListener) : ListAdapter( HouseHoldDiffUtilCallBack ) { + private object HouseHoldDiffUtilCallBack : DiffUtil.ItemCallback() { override fun areItemsTheSame( oldItem: HouseHoldBasicDomain, @@ -36,11 +45,66 @@ class HouseHoldListAdapter(private val clickListener: HouseholdClickListener) : } } - fun bind(item: HouseHoldBasicDomain, clickListener: HouseholdClickListener) { + fun bind( + item: HouseHoldBasicDomain, + clickListener: HouseholdClickListener, + isDisease: Boolean, + pref: PreferenceDao, + diseaseType: String, + isSoftDeleteEnabled: Boolean + ) { binding.household = item binding.clickListener = clickListener binding.executePendingBindings() + + if (isDisease) { + binding.button4.visibility = View.GONE + if (diseaseType == IconDataset.Disease.FILARIA.getTitle(binding.root.context) ) { + binding.button4.visibility = View.INVISIBLE + binding.btnMda.visibility = View.VISIBLE + } else { + binding.btnMda.visibility = View.GONE + } + } else if (!isDisease) { + binding.button4.visibility = View.VISIBLE + binding.btnMda.visibility = View.GONE + } else { + binding.button4.visibility = View.GONE + binding.btnMda.visibility = View.GONE + } + + + if (isSoftDeleteEnabled){ + binding.ivSoftDelete.visibility = View.VISIBLE + + if (item.isDeactivate){ + //binding.ivSoftDelete.setImageResource(R.drawable.ic_group_on) + binding.parentCard.setBackgroundColor(ContextCompat.getColor(binding.parentCard.context, R.color.Quartenary)) + + binding.ivSoftDelete.visibility = View.GONE + binding.button4.visibility = View.INVISIBLE + binding.tvTitleDuplicaterecord.visibility = View.VISIBLE + binding.button3.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(binding.root.context, R.color.md_theme_dark_outline)) + + } else{ + // binding.ivSoftDelete.setImageResource(R.drawable.ic_group_off) + binding.parentCard.setBackgroundColor(ContextCompat.getColor(binding.parentCard.context, R.color.md_theme_light_primary)) + + binding.ivSoftDelete.visibility = View.VISIBLE + binding.button4.visibility = View.VISIBLE + binding.tvTitleDuplicaterecord.visibility = View.GONE + binding.button3.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(binding.root.context, R.color.holo_green_dark)) + + } + + } else { + binding.ivSoftDelete.visibility = View.GONE + } + + + binding.clickListener = clickListener + binding.executePendingBindings() } } @@ -50,18 +114,21 @@ class HouseHoldListAdapter(private val clickListener: HouseholdClickListener) : } override fun onBindViewHolder(holder: HouseHoldViewHolder, position: Int) { - holder.bind(getItem(position), clickListener) + holder.bind(getItem(position), clickListener,isDisease, pref, diseaseType,isSoftDeleteEnabled) } class HouseholdClickListener( - val hhDetails: (hhId: Long) -> Unit, - val showMember: (hhId: Long) -> Unit, - val newBen: (hh: HouseHoldBasicDomain) -> Unit + val hhDetails: (hh: HouseHoldBasicDomain) -> Unit, + val showMember: (hh: HouseHoldBasicDomain) -> Unit, + val newBen: (hh: HouseHoldBasicDomain) -> Unit, + val addMDA: (hh: HouseHoldBasicDomain) -> Unit, + val softDeleteHh: (hh: HouseHoldBasicDomain) -> Unit, ) { - fun onClickedForHHDetails(item: HouseHoldBasicDomain) = hhDetails(item.hhId) - fun onClickedForMembers(item: HouseHoldBasicDomain) = showMember(item.hhId) + fun onClickedForHHDetails(item: HouseHoldBasicDomain) = hhDetails(item) + fun onClickedForMembers(item: HouseHoldBasicDomain) = showMember(item) fun onClickedForNewBen(item: HouseHoldBasicDomain) = newBen(item) - + fun onClickedAddMDA(item: HouseHoldBasicDomain) = addMDA(item) + fun onClickSoftDeleteHh(item: HouseHoldBasicDomain) = softDeleteHh(item) } } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/ImmunizationBenListAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/ImmunizationBenListAdapter.kt index c5c5d3623..aff98c998 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/adapters/ImmunizationBenListAdapter.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/ImmunizationBenListAdapter.kt @@ -1,16 +1,20 @@ package org.piramalswasthya.sakhi.adapters import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao import org.piramalswasthya.sakhi.databinding.RvItemImmunizationBenBinding import org.piramalswasthya.sakhi.model.BenBasicDomain +import org.piramalswasthya.sakhi.model.BenWithAncListDomain import org.piramalswasthya.sakhi.model.ImmunizationDetailsDomain class ImmunizationBenListAdapter( - private val clickListener: VaccinesClickListener? = null + private val clickListener: VaccinesClickListener? = null, + private val pref: PreferenceDao? = null ) : ListAdapter( BenDiffUtilCallBack ) { @@ -36,8 +40,9 @@ class ImmunizationBenListAdapter( } fun bind( - item: ImmunizationDetailsDomain, clickListener: VaccinesClickListener? + item: ImmunizationDetailsDomain, clickListener: VaccinesClickListener?, pref: PreferenceDao? ) { + binding.temp = item.ben binding.clickListener = clickListener binding.executePendingBindings() @@ -45,22 +50,27 @@ class ImmunizationBenListAdapter( } } + + override fun onCreateViewHolder( parent: ViewGroup, viewType: Int ): BenVaccineViewHolder = BenVaccineViewHolder.from(parent) override fun onBindViewHolder(holder: BenVaccineViewHolder, position: Int) { - holder.bind(getItem(position), clickListener) + holder.bind(getItem(position), clickListener, pref) + } class VaccinesClickListener( private val clickedVaccine: (benId: Long) -> Unit, + private val callBen: (ben: BenBasicDomain) -> Unit ) { fun onClickedBen(item: BenBasicDomain) = clickedVaccine( item.benId ) + fun onClickedForCall(item: BenBasicDomain) = callBen(item) } diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/ImmunizationBirthDoseCategoryAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/ImmunizationBirthDoseCategoryAdapter.kt new file mode 100644 index 000000000..f0d3e8d23 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/ImmunizationBirthDoseCategoryAdapter.kt @@ -0,0 +1,69 @@ +package org.piramalswasthya.sakhi.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.databinding.RvTabBinding +import org.piramalswasthya.sakhi.ui.home_activity.immunization_due.child_immunization.list.ChildImmunizationListViewModel + + +class ImmunizationBirthDoseCategoryAdapter( + private val catDataList: ArrayList, + private val clickListener: CategoryClickListener? = null, + var viewModel: ChildImmunizationListViewModel +) : RecyclerView.Adapter(){ + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = + CategoryViewHolder.from(parent) + + + override fun onBindViewHolder( + holder: CategoryViewHolder, + position: Int, + ) { + + holder.bind(catDataList[position]) + holder.binding.cMainLayout.setOnClickListener { + viewModel.selectedPosition = position + clickListener?.onClicked(catDataList[position]) + notifyDataSetChanged() + + } + if (viewModel.selectedPosition == position) { + holder.binding.cMainLayout.setBackgroundResource(R.drawable.tab_selection) + } else { + holder.binding.cMainLayout.setBackgroundResource(R.drawable.tab_unselection) + } + + } + + + class CategoryViewHolder private constructor(val binding: RvTabBinding) : + RecyclerView.ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): CategoryViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = RvTabBinding.inflate(layoutInflater, parent, false) + return CategoryViewHolder(binding) + } + } + + fun bind( + catDataList: String, + ) { + + binding.monthText.text = catDataList + + } + + } + override fun getItemCount(): Int { + return catDataList.size + } + + fun interface CategoryClickListener + { + fun onClicked(catDataList: String) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/ImmunizationCategoryAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/ImmunizationCategoryAdapter.kt index 9d3c686d9..ef112786a 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/adapters/ImmunizationCategoryAdapter.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/ImmunizationCategoryAdapter.kt @@ -1,8 +1,10 @@ package org.piramalswasthya.sakhi.adapters +import android.util.Log import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import com.google.android.flexbox.FlexboxLayoutManager @@ -45,9 +47,14 @@ class ImmunizationCategoryAdapter(private val clickListener: ImmunizationIconCli fun bind(item: VaccineCategoryDomain, clickListener: ImmunizationIconClickListener?) { binding.category = item binding.clickListener = clickListener - val layoutManager = FlexboxLayoutManager(binding.root.context) + // val layoutManager = FlexboxLayoutManager(binding.root.context) + val layoutManager = LinearLayoutManager(binding.root.context) + + Log.d("ImmnunigationVaccineAdapter", "isDeath: ${item.isBenDeath}") + + binding.rvVaccine.layoutManager = layoutManager - val adapter = ImmunizationVaccineAdapter( + val adapter = ImmunizationVaccineAdapter(item.isBenDeath, clickListener ) binding.rvVaccine.adapter = adapter @@ -66,10 +73,11 @@ class ImmunizationCategoryAdapter(private val clickListener: ImmunizationIconCli override fun onBindViewHolder(holder: VaccineCategoryViewHolder, position: Int) { holder.bind(getItem(position), clickListener) - } + Log.d("ImmnunigationVaccineAdapter", "onBindViewHolder called at position $position") - class ImmunizationIconClickListener(val selectedListener: (vaccineId: Int) -> Unit) { - fun onClicked(vaccine: VaccineDomain) = selectedListener(vaccine.vaccineId) + } + class ImmunizationIconClickListener(val selectedListener: (vaccineId: VaccineDomain) -> Unit) { + fun onClicked(vaccine: VaccineDomain) = selectedListener(vaccine) } } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/ImmunizationVaccineAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/ImmunizationVaccineAdapter.kt index d42fc3759..ce9e36f23 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/adapters/ImmunizationVaccineAdapter.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/ImmunizationVaccineAdapter.kt @@ -1,5 +1,6 @@ package org.piramalswasthya.sakhi.adapters +import android.util.Log import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil @@ -7,8 +8,9 @@ import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import org.piramalswasthya.sakhi.databinding.RvItemImmVaccineBinding import org.piramalswasthya.sakhi.model.VaccineDomain +import org.piramalswasthya.sakhi.model.VaccineState -class ImmunizationVaccineAdapter(private val clickListener: ImmunizationCategoryAdapter.ImmunizationIconClickListener? = null) : +class ImmunizationVaccineAdapter(var isDeath: Boolean,private val clickListener: ImmunizationCategoryAdapter.ImmunizationIconClickListener? = null) : ListAdapter( ImmunizationIconDiffCallback ) { @@ -35,11 +37,13 @@ class ImmunizationVaccineAdapter(private val clickListener: ImmunizationCategory fun bind( item: VaccineDomain, - clickListener: ImmunizationCategoryAdapter.ImmunizationIconClickListener? + clickListener: ImmunizationCategoryAdapter.ImmunizationIconClickListener?,isDeath: Boolean ) { binding.vaccine = item + binding.isDeath = isDeath binding.clickListener = clickListener binding.executePendingBindings() + } } @@ -48,6 +52,6 @@ class ImmunizationVaccineAdapter(private val clickListener: ImmunizationCategory IconViewHolder.from(parent) override fun onBindViewHolder(holder: IconViewHolder, position: Int) { - holder.bind(getItem(position), clickListener) + holder.bind(getItem(position), clickListener,isDeath) } } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/IncentiveGroupedAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/IncentiveGroupedAdapter.kt new file mode 100644 index 000000000..32c17f02e --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/IncentiveGroupedAdapter.kt @@ -0,0 +1,56 @@ +package org.piramalswasthya.sakhi.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.BuildConfig +import org.piramalswasthya.sakhi.databinding.RvItemIncentiveGroupedBinding +import org.piramalswasthya.sakhi.model.IncentiveGrouped + +class IncentiveGroupedAdapter( + private val onItemClick: (Long, String) -> Unit +) : + ListAdapter( + IncentiveGroupedDiffUtilCallBack + ) { + + private object IncentiveGroupedDiffUtilCallBack : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: IncentiveGrouped, newItem: IncentiveGrouped) = + oldItem.activityName == newItem.activityName + + override fun areContentsTheSame(oldItem: IncentiveGrouped, newItem: IncentiveGrouped) = + oldItem == newItem + } + + class IncentiveGroupedViewHolder( private val binding: RvItemIncentiveGroupedBinding, private val onItemClick: (Long, String) -> Unit) : + RecyclerView.ViewHolder(binding.root) { + private val isMitanin = BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true) + + + fun bind(item: IncentiveGrouped, clickListener: (Long, String) -> Unit,serialNo : Int) { + binding.item = item + binding.serialNo = serialNo + binding.clickListener = clickListener + binding.isMitanin = isMitanin + + if (isMitanin) { + binding.guideline75.setGuidelinePercent(0.9f) + } + binding.executePendingBindings() + + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): IncentiveGroupedViewHolder { + val binding = RvItemIncentiveGroupedBinding.inflate( + LayoutInflater.from(parent.context), parent, false + ) + return IncentiveGroupedViewHolder(binding,onItemClick) + } + + override fun onBindViewHolder(holder: IncentiveGroupedViewHolder, position: Int) { + holder.bind(getItem(position),onItemClick,position+ 1) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/IncentiveListAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/IncentiveListAdapter.kt index e63424d89..392625abb 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/adapters/IncentiveListAdapter.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/IncentiveListAdapter.kt @@ -5,12 +5,15 @@ import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.BuildConfig import org.piramalswasthya.sakhi.databinding.RvItemIncentiveBinding import org.piramalswasthya.sakhi.model.IncentiveDomain -class IncentiveListAdapter : +class IncentiveListAdapter( private val fileClickListener: FileClickListener) : ListAdapter(IncentiveDiffUtilCallBack) { + private val isMitanin = BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true) + private object IncentiveDiffUtilCallBack : DiffUtil.ItemCallback() { override fun areItemsTheSame( oldItem: IncentiveDomain, newItem: IncentiveDomain @@ -33,9 +36,13 @@ class IncentiveListAdapter : } fun bind( - item: IncentiveDomain, + item: IncentiveDomain,serialNo : Int, clickListener: FileClickListener,isMitanin: Boolean ) { binding.item = item + binding.serialNo = serialNo + binding.clickListener = clickListener + binding.isMitanin = isMitanin + binding.executePendingBindings() } @@ -47,10 +54,21 @@ class IncentiveListAdapter : override fun onBindViewHolder(holder: IncentiveViewHolder, position: Int) { holder.bind( - getItem(position) + getItem(position),position+1,fileClickListener,isMitanin + ) } + class FileClickListener( + private val onUpload: (item: IncentiveDomain) -> Unit, + private val onView: (item: IncentiveDomain) -> Unit, + private val onSubmit: (item: IncentiveDomain) -> Unit + ) { + fun onUploadClick(item: IncentiveDomain) = onUpload(item) + fun onViewClick(item: IncentiveDomain) = onView(item) + fun onSubmitClick(item: IncentiveDomain) = onSubmit(item) + } + // // class BenClickListener( // private val clickedBen: (hhId: Long, benId: Long, relToHeadId: Int) -> Unit, diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/InfantListAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/InfantListAdapter.kt index 19706c8a5..f3cd42403 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/adapters/InfantListAdapter.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/InfantListAdapter.kt @@ -1,12 +1,14 @@ package org.piramalswasthya.sakhi.adapters import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import org.piramalswasthya.sakhi.databinding.RvItemBenChildCareInfantBinding import org.piramalswasthya.sakhi.model.BenBasicDomain +import java.util.concurrent.TimeUnit class InfantListAdapter( @@ -44,8 +46,20 @@ class InfantListAdapter( binding.ben = item binding.clickListener = clickListener binding.executePendingBindings() - + setOverDueStatus(item.dob) } + + private fun setOverDueStatus(dob: Long) { + + val diffLong = System.currentTimeMillis() - dob + if (TimeUnit.MILLISECONDS.toDays(diffLong).toInt() > 42) { + binding.dueIcon.visibility = View.VISIBLE + } else { + binding.dueIcon.visibility = View.GONE + } + + } + } override fun onCreateViewHolder( @@ -54,6 +68,7 @@ class InfantListAdapter( override fun onBindViewHolder(holder: BenViewHolder, position: Int) { holder.bind(getItem(position), clickListener, showSyncIcon) + } diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/InfantRegistrationAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/InfantRegistrationAdapter.kt index 06577e447..28b988679 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/adapters/InfantRegistrationAdapter.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/InfantRegistrationAdapter.kt @@ -5,6 +5,7 @@ import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.R import org.piramalswasthya.sakhi.databinding.RvItemBenInfantRegBinding import org.piramalswasthya.sakhi.model.InfantRegDomain @@ -42,7 +43,7 @@ class InfantRegistrationAdapter( clickListener: ClickListener?, ) { binding.item = item - binding.btnFormEc1.text = if (item.savedIr == null) "Register" else "View" + binding.btnFormEc1.text = if (item.savedIr == null) binding.root.resources.getString(R.string.register) else binding.root.resources.getString(R.string.view) binding.btnFormEc1.setBackgroundColor(binding.root.resources.getColor(if (item.savedIr == null) android.R.color.holo_red_dark else android.R.color.holo_green_dark)) binding.clickListener = clickListener diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/IrsRoundListAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/IrsRoundListAdapter.kt new file mode 100644 index 000000000..1197e11d7 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/IrsRoundListAdapter.kt @@ -0,0 +1,57 @@ +package org.piramalswasthya.sakhi.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.databinding.RvItemIrsRoundBinding +import org.piramalswasthya.sakhi.model.BenWithScreeningRoundDomain +import org.piramalswasthya.sakhi.model.IRSRoundScreening + +class IrsRoundListAdapter () : + ListAdapter( + MyDiffUtilCallBack + ) { + private object MyDiffUtilCallBack : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: IRSRoundScreening, newItem: IRSRoundScreening + ) = oldItem.householdId == newItem.householdId + + override fun areContentsTheSame( + oldItem: IRSRoundScreening, newItem: IRSRoundScreening + ) = oldItem == newItem + + } + + class ViewHolder private constructor(private val binding: RvItemIrsRoundBinding) : + RecyclerView.ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): ViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = RvItemIrsRoundBinding.inflate(layoutInflater, parent, false) + return ViewHolder(binding) + } + } + + fun bind( + item: IRSRoundScreening + ) { + binding.irs = item + binding.position = position + binding.executePendingBindings() + + } + } + + override fun onCreateViewHolder( + parent: ViewGroup, viewType: Int + ) = ViewHolder.from(parent) + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.bind(getItem(position)) + } + + + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/KalaAzarMemberListAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/KalaAzarMemberListAdapter.kt new file mode 100644 index 000000000..b7dfc29c0 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/KalaAzarMemberListAdapter.kt @@ -0,0 +1,119 @@ +package org.piramalswasthya.sakhi.adapters + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.databinding.RvItemKalaAzarMemberListBinding +import org.piramalswasthya.sakhi.model.BenWithKALAZARScreeningDomain + +class KalaAzarMemberListAdapter( + private val clickListener: ClickListener? = null +) : + ListAdapter + (BenDiffUtilCallBack) { + private object BenDiffUtilCallBack : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: BenWithKALAZARScreeningDomain, + newItem: BenWithKALAZARScreeningDomain + ) = oldItem.ben.benId == newItem.ben.benId + + override fun areContentsTheSame( + oldItem: BenWithKALAZARScreeningDomain, + newItem: BenWithKALAZARScreeningDomain + ) = oldItem == newItem + + } + + class BenViewHolder private constructor(private val binding: RvItemKalaAzarMemberListBinding) : + RecyclerView.ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): BenViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = RvItemKalaAzarMemberListBinding.inflate(layoutInflater, parent, false) + return BenViewHolder(binding) + } + } + + fun bind( + item: BenWithKALAZARScreeningDomain, + clickListener: ClickListener?, + ) { + binding.benWithKalaAzar = item + + /*if(item.tb?.historyOfTb == true){ + binding.cvContent.visibility = View.GONE + }*/ + + binding.ivSyncState.visibility = if (item.kala == null) View.GONE else View.VISIBLE + + if (item.ben.spouseName == "Not Available" && item.ben.fatherName == "Not Available") { + binding.father = true + binding.husband = false + binding.spouse = false + } else { + if (item.ben.gender == "MALE") { + binding.father = true + binding.husband = false + binding.spouse = false + } else if (item.ben.gender == "FEMALE") { + if (item.ben.ageInt > 15) { + binding.father = + item.ben.fatherName != "Not Available" && item.ben.spouseName == "Not Available" + binding.husband = item.ben.spouseName != "Not Available" + binding.spouse = false + } else { + binding.father = true + binding.husband = false + binding.spouse = false + } + } else { + binding.father = + item.ben.fatherName != "Not Available" && item.ben.spouseName == "Not Available" + binding.spouse = item.ben.spouseName != "Not Available" + binding.husband = false + } + } + if ( item.kala != null && item.ben.isDeath) { + binding.btnFormTb.visibility = View.VISIBLE + } else if (item.kala == null && !item.ben.isDeath){ + binding.btnFormTb.visibility = View.VISIBLE + } else if (item.kala != null && !item.ben.isDeath){ + binding.btnFormTb.visibility = View.VISIBLE + } + else { + binding.btnFormTb.visibility = View.INVISIBLE + } + binding.btnFormTb.text = if (item.kala == null) binding.root.resources.getString(R.string.register) else binding.root.resources.getString(R.string.view) + binding.btnFormTb.setBackgroundColor(binding.root.resources.getColor(if (item.kala == null) android.R.color.holo_red_dark else android.R.color.holo_green_dark)) + binding.clickListener = clickListener + + binding.executePendingBindings() + + } + + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ) = + BenViewHolder.from(parent) + + override fun onBindViewHolder(holder: BenViewHolder, position: Int) { + holder.bind(getItem(position), clickListener) + } + + + class ClickListener( + private val clickedForm: ((hhId: Long, benId: Long) -> Unit)? = null + + ) { + fun onClickForm(item: BenWithKALAZARScreeningDomain) = + clickedForm?.let { it(item.ben.hhId, item.ben.benId) } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/LeprosyMemberListAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/LeprosyMemberListAdapter.kt new file mode 100644 index 000000000..75369c311 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/LeprosyMemberListAdapter.kt @@ -0,0 +1,168 @@ +package org.piramalswasthya.sakhi.adapters + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.databinding.RvItemLeprosyMemberListBinding +import org.piramalswasthya.sakhi.model.BenWithKALAZARScreeningDomain +import org.piramalswasthya.sakhi.model.BenWithLeprosyScreeningDomain + +class LeprosyMemberListAdapter( + private val clickListener: ClickListener? = null, + private val showExtraButton: Boolean? = null + +) : + ListAdapter + (BenDiffUtilCallBack) { + private object BenDiffUtilCallBack : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: BenWithLeprosyScreeningDomain, + newItem: BenWithLeprosyScreeningDomain + ) = oldItem.ben.benId == newItem.ben.benId + + override fun areContentsTheSame( + oldItem: BenWithLeprosyScreeningDomain, + newItem: BenWithLeprosyScreeningDomain + ) = oldItem == newItem + + } + + class BenViewHolder private constructor(private val binding: RvItemLeprosyMemberListBinding) : + RecyclerView.ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): BenViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = RvItemLeprosyMemberListBinding.inflate(layoutInflater, parent, false) + return BenViewHolder(binding) + } + } + + fun bind( + item: BenWithLeprosyScreeningDomain, + clickListener: ClickListener?, + showExtraButton: Boolean? + ) { + binding.benWithLeprosy = item + + if(showExtraButton == true && item.leprosy != null && item.leprosy.currentVisitNumber >1) + { + binding.btnVisits.visibility = View.VISIBLE + + val green = ContextCompat.getColor( + binding.root.context, + android.R.color.holo_green_dark + ) + binding.btnVisits.setBackgroundColor(green) + } + else + { + binding.btnVisits.visibility = View.GONE + } + + /*if(item.tb?.historyOfTb == true){ + binding.cvContent.visibility = View.GONE + }*/ + + + binding.ivSyncState.visibility = if (item.leprosy == null) View.GONE else View.VISIBLE + + if (item.ben.spouseName == "Not Available" && item.ben.fatherName == "Not Available") { + binding.father = true + binding.husband = false + binding.spouse = false + } else { + if (item.ben.gender == "MALE") { + binding.father = true + binding.husband = false + binding.spouse = false + } else if (item.ben.gender == "FEMALE") { + if (item.ben.ageInt > 15) { + binding.father = + item.ben.fatherName != "Not Available" && item.ben.spouseName == "Not Available" + binding.husband = item.ben.spouseName != "Not Available" + binding.spouse = false + } else { + binding.father = true + binding.husband = false + binding.spouse = false + } + } else { + binding.father = + item.ben.fatherName != "Not Available" && item.ben.spouseName == "Not Available" + binding.spouse = item.ben.spouseName != "Not Available" + binding.husband = false + } + } + if ( item.leprosy != null && item.ben.isDeath) { + binding.btnFormTb.visibility = View.VISIBLE + } else if (item.leprosy == null && !item.ben.isDeath){ + binding.btnFormTb.visibility = View.VISIBLE + } else if (item.leprosy != null && !item.ben.isDeath){ + binding.btnFormTb.visibility = View.VISIBLE + } else { + binding.btnFormTb.visibility = View.INVISIBLE + } + val (text, colorRes) = when { + + item.leprosy == null -> { + R.string.screening to android.R.color.holo_green_dark + } + item.leprosy.leprosySymptomsPosition == 1 -> { + R.string.screening to android.R.color.holo_green_dark + } + item.leprosy.leprosySymptomsPosition == 0 && item.leprosy.isConfirmed -> { + R.string.follow_up to android.R.color.holo_red_dark + } + item.leprosy.leprosySymptomsPosition == 0 -> { + R.string.suspected to android.R.color.holo_red_dark + } + else -> { + R.string.view to android.R.color.holo_green_dark + } + } + + binding.btnFormTb.text = binding.root.context.getString(text) + binding.btnFormTb.setBackgroundColor( + binding.root.resources.getColor(colorRes, binding.root.context.theme) + ) + /* binding.btnFormTb.text = if (item.leprosy == null) "Register" else "View" + item.leprosy.leprosySymptomsPosition + binding.btnFormTb.setBackgroundColor(binding.root.resources.getColor(if (item.leprosy == null) android.R.color.holo_red_dark else android.R.color.holo_green_dark))*/ + binding.clickListener = clickListener + + binding.executePendingBindings() + + } + + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ) = + BenViewHolder.from(parent) + + override fun onBindViewHolder(holder: BenViewHolder, position: Int) { + holder.bind(getItem(position), clickListener,showExtraButton) + } + + + class ClickListener( + private val clickedForm: ((hhId: Long, benId: Long) -> Unit)? = null, + private val clickedVisits: ((benWithLeprosy: BenWithLeprosyScreeningDomain) -> Unit)? = null + + + ) { + fun onClickForm(item: BenWithLeprosyScreeningDomain) = + clickedForm?.let { it(item.ben.hhId, item.ben.benId) } + + fun onClickVisits(item: BenWithLeprosyScreeningDomain) = + clickedVisits?.let { it(item) } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/MaaMeetingAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/MaaMeetingAdapter.kt new file mode 100644 index 000000000..aed4368f6 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/MaaMeetingAdapter.kt @@ -0,0 +1,56 @@ +package org.piramalswasthya.sakhi.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.databinding.LayoutMaameetingItemBinding +import org.piramalswasthya.sakhi.model.MaaMeetingEntity + +class MaaMeetingAdapter( + private val clickListener: MaaMeetingAdapterClickListener? = null, +) : ListAdapter(BenDiffUtilCallBack) { + + private object BenDiffUtilCallBack : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: MaaMeetingEntity, + newItem: MaaMeetingEntity + ) = oldItem.id == newItem.id + + override fun areContentsTheSame( + oldItem: MaaMeetingEntity, + newItem: MaaMeetingEntity + ) = oldItem == newItem + } + + class MaaMeetingViewHolder private constructor(private val binding: LayoutMaameetingItemBinding) : + RecyclerView.ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): MaaMeetingViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = LayoutMaameetingItemBinding.inflate(layoutInflater, parent, false) + return MaaMeetingViewHolder(binding) + } + } + + fun bind(item: MaaMeetingEntity, clickListener: MaaMeetingAdapterClickListener?) { + binding.maaMeeting = item + + binding.clickListener = clickListener + binding.executePendingBindings() + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MaaMeetingViewHolder { + return MaaMeetingViewHolder.from(parent) + } + + override fun onBindViewHolder(holder: MaaMeetingViewHolder, position: Int) { + holder.bind(getItem(position), clickListener) + } + + class MaaMeetingAdapterClickListener(val clickListener: (id: Long) -> Unit) { + fun onClick(id: Long) = clickListener(id) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/MalariaConfirmedCasesListAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/MalariaConfirmedCasesListAdapter.kt new file mode 100644 index 000000000..ced1e3cc6 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/MalariaConfirmedCasesListAdapter.kt @@ -0,0 +1,142 @@ +package org.piramalswasthya.sakhi.adapters + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.databinding.RvItemMalariaCasesConfirmedListBinding +import org.piramalswasthya.sakhi.model.BenWithMalariaConfirmedDomain + +class MalariaConfirmedCasesListAdapter( + private val clickListener: ClickListener? = null +) : + ListAdapter + (BenDiffUtilCallBack) { + private object BenDiffUtilCallBack : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: BenWithMalariaConfirmedDomain, + newItem: BenWithMalariaConfirmedDomain + ) = oldItem.ben.benId == newItem.ben.benId + + override fun areContentsTheSame( + oldItem: BenWithMalariaConfirmedDomain, + newItem: BenWithMalariaConfirmedDomain + ) = oldItem == newItem + + } + + class BenViewHolder private constructor(private val binding: RvItemMalariaCasesConfirmedListBinding) : + RecyclerView.ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): BenViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = RvItemMalariaCasesConfirmedListBinding.inflate(layoutInflater, parent, false) + return BenViewHolder(binding) + } + } + + fun bind( + item: BenWithMalariaConfirmedDomain, + clickListener: ClickListener?, + ) { + binding.benWithMalariaConfirmed = item + + + binding.ivSyncState.visibility = if (item.malariaConfirmed == null) View.GONE else View.VISIBLE + +/* try { + if(item.malariaConfirmed!!.caseStatus != null) { + binding.ivMalariaStatus.visibility = View.VISIBLE + if (item.malariaConfirmed.caseStatus == "Confirmed") { + Glide.with(binding.ivSyncState).load(R.drawable.mosquito).into(binding.ivMalariaStatus) + } else if (item.malariaConfirmed.caseStatus == "Suspected") { + Glide.with(binding.ivSyncState).load(R.drawable.warning).into(binding.ivMalariaStatus) + }else if (item.malariaConfirmed.caseStatus == "Not Confirmed") { + Glide.with(binding.ivSyncState).load(R.drawable.ic_check_circle).into(binding.ivMalariaStatus) + } else if (item.malariaConfirmed.caseStatus == "Treatment Started"){ + Glide.with(binding.ivSyncState).load(R.drawable.pill).into(binding.ivMalariaStatus) + } else { + Glide.with(binding.ivSyncState).load(R.drawable.warning).into(binding.ivMalariaStatus) + + } + + } else { + binding.ivMalariaStatus.visibility = View.INVISIBLE + } + } catch (e:Exception) { + Timber.d("Exception at case status : $e collected") + + }*/ + + + + if (item.ben.spouseName == "Not Available" && item.ben.fatherName == "Not Available") { + binding.father = true + binding.husband = false + binding.spouse = false + } else { + if (item.ben.gender == "MALE") { + binding.father = true + binding.husband = false + binding.spouse = false + } else if (item.ben.gender == "FEMALE") { + if (item.ben.ageInt > 15) { + binding.father = + item.ben.fatherName != "Not Available" && item.ben.spouseName == "Not Available" + binding.husband = item.ben.spouseName != "Not Available" + binding.spouse = false + } else { + binding.father = true + binding.husband = false + binding.spouse = false + } + } else { + binding.father = + item.ben.fatherName != "Not Available" && item.ben.spouseName == "Not Available" + binding.spouse = item.ben.spouseName != "Not Available" + binding.husband = false + } + } + if ( item.malariaConfirmed != null && item.ben.isDeath) { + binding.btnFormTb.visibility = View.VISIBLE + } else if (item.malariaConfirmed == null && !item.ben.isDeath){ + binding.btnFormTb.visibility = View.VISIBLE + } else if (item.malariaConfirmed != null && !item.ben.isDeath){ + binding.btnFormTb.visibility = View.VISIBLE + } else { + binding.btnFormTb.visibility = View.INVISIBLE + } + + binding.btnFormTb.text = if (item.malariaConfirmed == null) binding.root.resources.getString(R.string.follow_up) else item.malariaConfirmed.day + binding.btnFormTb.setBackgroundColor(binding.root.resources.getColor(if (item.malariaConfirmed == null) android.R.color.holo_red_dark else android.R.color.holo_green_dark)) + binding.clickListener = clickListener + + binding.executePendingBindings() + + } + + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ) = + BenViewHolder.from(parent) + + override fun onBindViewHolder(holder: BenViewHolder, position: Int) { + holder.bind(getItem(position), clickListener) + } + + + class ClickListener( + private val clickedForm: ((hhId: Long, benId: Long) -> Unit)? = null + + ) { + fun onClickForm(item: BenWithMalariaConfirmedDomain) = + clickedForm?.let { it(item.ben.hhId, item.ben.benId) } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/MalariaMemberListAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/MalariaMemberListAdapter.kt new file mode 100644 index 000000000..2d973978e --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/MalariaMemberListAdapter.kt @@ -0,0 +1,165 @@ +package org.piramalswasthya.sakhi.adapters + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.databinding.RvItemMalariaMembersListBinding +import org.piramalswasthya.sakhi.model.BenWithMalariaScreeningDomain +import timber.log.Timber + +class MalariaMemberListAdapter( + private val clickListener: ClickListener? = null +) : + ListAdapter + (BenDiffUtilCallBack) { + private object BenDiffUtilCallBack : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: BenWithMalariaScreeningDomain, + newItem: BenWithMalariaScreeningDomain + ) = oldItem.ben.benId == newItem.ben.benId + + override fun areContentsTheSame( + oldItem: BenWithMalariaScreeningDomain, + newItem: BenWithMalariaScreeningDomain + ) = oldItem == newItem + + } + + class BenViewHolder private constructor(private val binding: RvItemMalariaMembersListBinding) : + RecyclerView.ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): BenViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = RvItemMalariaMembersListBinding.inflate(layoutInflater, parent, false) + return BenViewHolder(binding) + } + } + + fun bind( + item: BenWithMalariaScreeningDomain, + clickListener: ClickListener?, + ) { + binding.benWithMalaria = item + + /*if(item.tb?.historyOfTb == true){ + binding.cvContent.visibility = View.GONE + }*/ + + binding.ivSyncState.visibility = if (item.tb == null) View.INVISIBLE else View.VISIBLE + try { + if (item.tb == null) { + binding.btnFormTb.text = binding.root.resources.getString(R.string.screening) + binding.btnFormTb.setBackgroundColor(binding.root.resources.getColor(android.R.color.holo_red_dark)) + + } else { + if(item.tb.caseStatus != null) { + + binding.ivMalariaStatus.visibility = View.VISIBLE + if (item.tb.caseStatus == "Confirmed") { + binding.btnFormTb.setBackgroundColor(binding.root.resources.getColor(android.R.color.holo_green_dark)) + Glide.with(binding.ivSyncState).load(R.drawable.mosquito).into(binding.ivMalariaStatus) + binding.btnFormTb.text = binding.root.resources.getString(R.string.malaria_confirmed) + } else if (item.tb.caseStatus == "Suspected") { + binding.btnFormTb.setBackgroundColor(binding.root.resources.getColor(android.R.color.holo_orange_light)) + Glide.with(binding.ivSyncState).load(R.drawable.warning).into(binding.ivMalariaStatus) + binding.btnFormTb.text = binding.root.resources.getString(R.string.suspected) + }else if (item.tb.caseStatus == "Not Confirmed") { + binding.btnFormTb.setBackgroundColor(binding.root.resources.getColor(android.R.color.tertiary_text_light)) + Glide.with(binding.ivSyncState).load(R.drawable.ic_check_circle).into(binding.ivMalariaStatus) + binding.btnFormTb.text = binding.root.resources.getString(R.string.malaria_not_confirmed) + } else if (item.tb.caseStatus == "Treatment Started"){ + binding.btnFormTb.setBackgroundColor(binding.root.resources.getColor(android.R.color.holo_purple)) + Glide.with(binding.ivSyncState).load(R.drawable.pill).into(binding.ivMalariaStatus) + binding.btnFormTb.text = binding.root.resources.getString(R.string.malaria_treatment_started) + + } else { + Glide.with(binding.ivSyncState).load(R.drawable.warning).into(binding.ivMalariaStatus) + } + + } else { + + binding.ivMalariaStatus.visibility = View.INVISIBLE + binding.btnFormTb.text = binding.root.resources.getString(R.string.view) + binding.btnFormTb.setBackgroundColor(binding.root.resources.getColor(android.R.color.holo_green_dark)) + + } + } + + } catch (e:Exception) { + Timber.d("Exception at case status : $e collected") + + } + + + if (item.ben.spouseName == "Not Available" && item.ben.fatherName == "Not Available") { + binding.father = true + binding.husband = false + binding.spouse = false + } else { + if (item.ben.gender == "MALE") { + binding.father = true + binding.husband = false + binding.spouse = false + } else if (item.ben.gender == "FEMALE") { + if (item.ben.ageInt > 15) { + binding.father = + item.ben.fatherName != "Not Available" && item.ben.spouseName == "Not Available" + binding.husband = item.ben.spouseName != "Not Available" + binding.spouse = false + } else { + binding.father = true + binding.husband = false + binding.spouse = false + } + } else { + binding.father = + item.ben.fatherName != "Not Available" && item.ben.spouseName == "Not Available" + binding.spouse = item.ben.spouseName != "Not Available" + binding.husband = false + } + } + + if ( item.tb != null && item.ben.isDeath) { + binding.btnFormTb.visibility = View.VISIBLE + } else if (item.tb == null && !item.ben.isDeath){ + binding.btnFormTb.visibility = View.VISIBLE + } else if (item.tb != null && !item.ben.isDeath){ + binding.btnFormTb.visibility = View.VISIBLE + } else { + binding.btnFormTb.visibility = View.INVISIBLE + } +// binding.btnFormTb.text = if (item.tb == null) "Screening" else "View" +// binding.btnFormTb.setBackgroundColor(binding.root.resources.getColor(if (item.tb == null) android.R.color.holo_red_dark else android.R.color.holo_green_dark)) + binding.clickListener = clickListener + + binding.executePendingBindings() + + } + + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ) = + BenViewHolder.from(parent) + + override fun onBindViewHolder(holder: BenViewHolder, position: Int) { + holder.bind(getItem(position), clickListener) + } + + + class ClickListener( + private val clickedForm: ((hhId: Long, benId: Long) -> Unit)? = null + + ) { + fun onClickForm(item: BenWithMalariaScreeningDomain) = + clickedForm?.let { it(item.ben.hhId, item.ben.benId) } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/NCDCategoryAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/NCDCategoryAdapter.kt new file mode 100644 index 000000000..27652e0f8 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/NCDCategoryAdapter.kt @@ -0,0 +1,68 @@ +package org.piramalswasthya.sakhi.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.databinding.RvTabBinding +import org.piramalswasthya.sakhi.ui.home_activity.non_communicable_diseases.ncd_eligible_list.NcdEligibleListViewModel + +class NCDCategoryAdapter( + private val dataList: ArrayList, + private val listener: ClickListener? = null, + var viewModel: NcdEligibleListViewModel +) : RecyclerView.Adapter(){ + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = + ViewHolder.from(parent) + + + override fun onBindViewHolder( + holder: ViewHolder, + position: Int, + ) { + + holder.bind(dataList[position]) + holder.binding.cMainLayout.setOnClickListener { + viewModel.clickedPosition = position + listener?.onClicked(dataList[position]) + notifyDataSetChanged() + + } + if (viewModel.clickedPosition == position) { + holder.binding.cMainLayout.setBackgroundResource(R.drawable.tab_selection) + } else { + holder.binding.cMainLayout.setBackgroundResource(R.drawable.tab_unselection) + } + + } + + + class ViewHolder private constructor(val binding: RvTabBinding) : + RecyclerView.ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): ViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = RvTabBinding.inflate(layoutInflater, parent, false) + return ViewHolder(binding) + } + } + + fun bind( + dataObject: String, + ) { + + binding.monthText.text = dataObject + + } + + } + override fun getItemCount(): Int { + return dataList.size + } + + fun interface ClickListener + { + fun onClicked(catDataList: String) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/NCDReferTypeFilterAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/NCDReferTypeFilterAdapter.kt new file mode 100644 index 000000000..20b723a3f --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/NCDReferTypeFilterAdapter.kt @@ -0,0 +1,68 @@ +package org.piramalswasthya.sakhi.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.databinding.RvTabBinding +import org.piramalswasthya.sakhi.ui.home_activity.non_communicable_diseases.ncd_referred.list.NcdRefferedListViewModel + +class NCDReferTypeFilterAdapter( + private val catDataList: ArrayList, + private val clickListener: CategoryClickListener? = null, + var viewModel: NcdRefferedListViewModel +) : RecyclerView.Adapter(){ + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = + CategoryViewHolder.from(parent) + + + override fun onBindViewHolder( + holder: CategoryViewHolder, + position: Int, + ) { + + holder.bind(catDataList[position]) + holder.binding.cMainLayout.setOnClickListener { + viewModel.selectedPosition = position + clickListener?.onClicked(catDataList[position]) + notifyDataSetChanged() + + } + if (viewModel.selectedPosition == position) { + holder.binding.cMainLayout.setBackgroundResource(R.drawable.tab_selection) + } else { + holder.binding.cMainLayout.setBackgroundResource(R.drawable.tab_unselection) + } + + } + + + class CategoryViewHolder private constructor(val binding: RvTabBinding) : + RecyclerView.ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): CategoryViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = RvTabBinding.inflate(layoutInflater, parent, false) + return CategoryViewHolder(binding) + } + } + + fun bind( + catDataList: String, + ) { + + binding.monthText.text = catDataList + + } + + } + override fun getItemCount(): Int { + return catDataList.size + } + + fun interface CategoryClickListener + { + fun onClicked(catDataList: String) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/NcdReferListAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/NcdReferListAdapter.kt new file mode 100644 index 000000000..c5b3c7d76 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/NcdReferListAdapter.kt @@ -0,0 +1,106 @@ + package org.piramalswasthya.sakhi.adapters + + import android.view.LayoutInflater + import android.view.View + import android.view.ViewGroup + import androidx.recyclerview.widget.DiffUtil + import androidx.recyclerview.widget.ListAdapter + import androidx.recyclerview.widget.RecyclerView + import org.piramalswasthya.sakhi.databinding.RvItemNcdReferBinding + import org.piramalswasthya.sakhi.model.BenBasicDomain + import org.piramalswasthya.sakhi.model.BenWithCbacReferDomain + + class NcdReferListAdapter(var userName: String, private val listener: NcdReferallickListener, private val visible: Boolean ) : ListAdapter( + BenDiffUtilCallBack + ) { + private object BenDiffUtilCallBack : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: BenWithCbacReferDomain, newItem: BenWithCbacReferDomain + ) = oldItem.ben.benId == newItem.ben.benId + + override fun areContentsTheSame( + oldItem: BenWithCbacReferDomain, newItem: BenWithCbacReferDomain + ) = oldItem == newItem + + } + + class BenCbacViewHolder private constructor(private val binding: RvItemNcdReferBinding) : + RecyclerView.ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): BenCbacViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = RvItemNcdReferBinding.inflate(layoutInflater, parent, false) + return BenCbacViewHolder(binding) + } + } + + fun bind( + item: BenWithCbacReferDomain, + userName: String, + listener: NcdReferallickListener, + visible: Boolean + ) { + binding.benWithCbac = item + binding.referredFrom.text = userName + + if (item.ben.spouseName == "Not Available" && item.ben.fatherName == "Not Available") { + binding.father = true + binding.husband = false + binding.spouse = false + } else { + if (item.ben.gender == "MALE") { + binding.father = true + binding.husband = false + binding.spouse = false + } else if (item.ben.gender == "FEMALE") { + if (item.ben.ageInt > 15) { + binding.father = + item.ben.fatherName != "Not Available" && item.ben.spouseName == "Not Available" + binding.husband = item.ben.spouseName != "Not Available" + binding.spouse = false + } else { + binding.father = true + binding.husband = false + binding.spouse = false + } + } else { + binding.father = + item.ben.fatherName != "Not Available" && item.ben.spouseName == "Not Available" + binding.spouse = item.ben.spouseName != "Not Available" + binding.husband = false + } + } + + binding.executePendingBindings() + binding.btnFollowUp.visibility = if (visible) View.VISIBLE else View.GONE + binding.btnFollowUp.setOnClickListener { + listener.onClickedFollowUp(item.ben) + } + + + } + } + + override fun onCreateViewHolder( + parent: ViewGroup, viewType: Int + ): BenCbacViewHolder = BenCbacViewHolder.from(parent) + + override fun onBindViewHolder(holder: BenCbacViewHolder, position: Int) { + holder.bind(getItem(position) , userName,listener,visible) + } + + + + class NcdReferallickListener( + val goToFollowUp: (benId: Long,hhId:Long) -> Unit + + ) { + fun onClickedFollowUp(item: BenBasicDomain) = goToFollowUp( + item.benId,item.hhId + ) + } + + + + + } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/ORSCampaignAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/ORSCampaignAdapter.kt new file mode 100644 index 000000000..616035c97 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/ORSCampaignAdapter.kt @@ -0,0 +1,61 @@ +package org.piramalswasthya.sakhi.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.databinding.LayoutOrsCampaignItemBinding +import org.piramalswasthya.sakhi.model.ORSCampaignCache +import org.piramalswasthya.sakhi.utils.HelperUtil + +class ORSCampaignAdapter( + private val clickListener: ORSCampaignClickListener? = null, +) : ListAdapter(BenDiffUtilCallBack) { + + private object BenDiffUtilCallBack : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: ORSCampaignCache, + newItem: ORSCampaignCache + ) = oldItem.id == newItem.id + + override fun areContentsTheSame( + oldItem: ORSCampaignCache, + newItem: ORSCampaignCache + ) = oldItem == newItem + } + + class ORSCampaignViewHolder private constructor(private val binding: LayoutOrsCampaignItemBinding) : + RecyclerView.ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): ORSCampaignViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = LayoutOrsCampaignItemBinding.inflate(layoutInflater, parent, false) + return ORSCampaignViewHolder(binding) + } + } + + fun bind(item: ORSCampaignCache, clickListener: ORSCampaignClickListener?) { + binding.orsCampaign = item + binding.clickListener = clickListener + binding.executePendingBindings() + val startDate = HelperUtil.extractFieldValue(item.formDataJson,"start_date") + val numberOfChildren = HelperUtil.extractFieldValue(item.formDataJson,"number_of_families") + binding.tvDate.text = startDate + binding.numberOfChildren.text = binding.root.context.getString(R.string.number_of_families_label, numberOfChildren) + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ORSCampaignViewHolder { + return ORSCampaignViewHolder.from(parent) + } + + override fun onBindViewHolder(holder: ORSCampaignViewHolder, position: Int) { + holder.bind(getItem(position), clickListener) + } + + class ORSCampaignClickListener(val clickListener: (id: Int) -> Unit) { + fun onClick(id: Int) = clickListener(id) + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/PHCAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/PHCAdapter.kt new file mode 100644 index 000000000..c8bf4b71b --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/PHCAdapter.kt @@ -0,0 +1,182 @@ +package org.piramalswasthya.sakhi.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.databinding.LayoutPhcReviewItemBinding +import org.piramalswasthya.sakhi.databinding.LayoutVhncItemBinding +import org.piramalswasthya.sakhi.model.PHCReviewMeetingCache +import org.piramalswasthya.sakhi.model.VHNCCache + +class PHCAdapter( + private val clickListener: PHCClickListener? = null, +) : + ListAdapter + (BenDiffUtilCallBack) { + private object BenDiffUtilCallBack : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: PHCReviewMeetingCache, + newItem: PHCReviewMeetingCache + ) = oldItem.id == newItem.id + + override fun areContentsTheSame(oldItem: PHCReviewMeetingCache, newItem: PHCReviewMeetingCache)= oldItem == newItem + + + } + + class PHCViewHolder private constructor(private val binding: LayoutPhcReviewItemBinding) : + RecyclerView.ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): PHCViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = LayoutPhcReviewItemBinding.inflate(layoutInflater, parent, false) + return PHCViewHolder(binding) + } + } + fun bind(item: PHCReviewMeetingCache, clickListener: PHCClickListener?,) { + binding.phc = item + binding.clickListener = clickListener + binding.executePendingBindings() + + } + + + +// private fun setFormButtonColor(formNumber: Int, item: BenWithHRPADomain, role: Int?) { +// var hasForm: Boolean = false +// var completelyFilled: Boolean = false +// var formEnabled: Boolean = false +// buttonFlexibleWidth() +//// binding.btnForm3.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.icon_whatsapp, 0); +// +// +//// val formButton = when (formNumber) { +//// 1 -> { +//// binding.btnForm1.also { +//// hasForm = item.assess != null +//// item.assess?.let { +//// completelyFilled = +//// it.noOfDeliveries != null && +//// it.timeLessThan18m != null && +//// it.heightShort != null && +//// it.age != null && +//// it.rhNegative != null && +//// it.homeDelivery != null && +//// it.badObstetric != null && +//// it.multiplePregnancy != null +//// } +//// +//// formEnabled = true +//// } +//// } +//// +////// 2 -> { +////// binding.btnForm2.also { +////// hasForm = item.mbp != null +////// formEnabled = true +////// } +////// } +////// +////// 3 -> { +////// binding.btnForm3.also { +////// hasForm = item.mbp != null +////// if (hasForm) { +////// +////// formEnabled = true +////// buttonFlexibleWidth() +////// +////// } else { +////// +////// formEnabled = false +////// (binding.btnForm2.layoutParams as LinearLayout.LayoutParams).weight=1.8f +////// (binding.btnForm1.layoutParams as LinearLayout.LayoutParams).weight=1.2f +////// +////// } +////// +////// } +////// } +//// +//// else -> throw IllegalStateException("FormNumber>3") +//// } +//// formButton.visibility = if (formEnabled) View.VISIBLE else View.GONE +// +// +//// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { +//// if (hasForm) { +//// if (completelyFilled) { +//// formButton.setBackgroundColor( +//// binding.root.resources.getColor( +//// android.R.color.holo_green_dark, +//// binding.root.context.theme +//// ) +//// ) +//// } else { +//// formButton.setBackgroundColor( +//// binding.root.resources.getColor( +//// android.R.color.holo_orange_dark, +//// binding.root.context.theme +//// ) +//// ) +//// } +//// binding.btnForm3.setBackgroundColor( +//// binding.btnForm3.resources.getColor( +//// android.R.color.holo_purple, +//// binding.root.context.theme +//// ) +//// ) +//// +//// } else { +//// formButton.setBackgroundColor( +//// binding.root.resources.getColor( +//// android.R.color.holo_red_light, +//// binding.root.context.theme +//// ) +//// ) +//// } +//// +//// } else +//// if (hasForm) +//// formButton.setBackgroundColor(binding.root.resources.getColor(android.R.color.holo_green_dark)) +//// else +//// formButton.setBackgroundColor( +//// binding.root.resources.getColor( +//// android.R.color.holo_red_light, +//// ) +//// ) +//// +//// formButton.setTextColor( +//// binding.root.resources.getColor( +//// com.google.android.material.R.color.design_default_color_on_primary, +//// binding.root.context.theme +//// ) +//// ) +// +// } + +// private fun buttonFlexibleWidth() { +// (binding.btnForm2.layoutParams as LinearLayout.LayoutParams).weight=1.2f +// (binding.btnForm1.layoutParams as LinearLayout.LayoutParams).weight=0.8f +// (binding.btnForm3.layoutParams as LinearLayout.LayoutParams).weight=1.0f +// } + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ) = + PHCViewHolder.from(parent) + + override fun onBindViewHolder(holder: PHCViewHolder, position: Int) { + holder.bind(getItem(position), clickListener) + } + + + class PHCClickListener( + private val clickedForm: ((id: Int) -> Unit)? = null + ) { + fun onClickForm1(item: PHCReviewMeetingCache) = clickedForm?.let { it(item.id!!) } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/PmsmaVisitAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/PmsmaVisitAdapter.kt new file mode 100644 index 000000000..6e3cfac96 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/PmsmaVisitAdapter.kt @@ -0,0 +1,69 @@ +package org.piramalswasthya.sakhi.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.databinding.RvItemPregnancyAncBinding +import org.piramalswasthya.sakhi.databinding.RvItemPregnancyAncPmsmaBinding +import org.piramalswasthya.sakhi.model.AncStatus + +class PmsmaVisitAdapter(private val clickListener: PmsmaVisitClickListener) : + ListAdapter( + MyDiffUtilCallBack + ) { + private object MyDiffUtilCallBack : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: AncStatus, newItem: AncStatus + ) = oldItem.benId == newItem.benId + + override fun areContentsTheSame( + oldItem: AncStatus, newItem: AncStatus + ) = oldItem == newItem + + } + + class AncViewHolder private constructor(private val binding: RvItemPregnancyAncPmsmaBinding) : + RecyclerView.ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): AncViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = RvItemPregnancyAncPmsmaBinding.inflate(layoutInflater, parent, false) + return AncViewHolder(binding) + } + } + + fun bind( + item: AncStatus, clickListener: PmsmaVisitClickListener, isLastItem: Boolean + ) { + binding.visit = item + binding.clickListener = clickListener + binding.executePendingBindings() + + binding.btnView.setOnClickListener { + clickListener.onClickedVisit(item, true) + } + + } + } + + override fun onCreateViewHolder( + parent: ViewGroup, viewType: Int + ) = AncViewHolder.from(parent) + + override fun onBindViewHolder(holder: AncViewHolder, position: Int) { + val isLastItem = position == itemCount - 1 + holder.bind(getItem(position), clickListener, isLastItem) + } + + + class PmsmaVisitClickListener( + private val clickedForm: (benId: Long, visitNumber: Int, isLast: Boolean) -> Unit + ) { + fun onClickedVisit(item: AncStatus, isLast: Boolean) = clickedForm( + item.benId, item.visitNumber, isLast + ) + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/PncVisitListAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/PncVisitListAdapter.kt index 76005d1f5..70c7a235e 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/adapters/PncVisitListAdapter.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/PncVisitListAdapter.kt @@ -1,13 +1,17 @@ package org.piramalswasthya.sakhi.adapters +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.Toast import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import org.piramalswasthya.sakhi.databinding.RvItemPncVisitBinding import org.piramalswasthya.sakhi.model.BenPncDomain +import org.piramalswasthya.sakhi.utils.HelperUtil +import java.util.concurrent.TimeUnit class PncVisitListAdapter(private val clickListener: PncVisitClickListener? = null) : ListAdapter( @@ -37,11 +41,26 @@ class PncVisitListAdapter(private val clickListener: PncVisitClickListener? = nu fun bind( item: BenPncDomain, clickListener: PncVisitClickListener? ) { + + if (item.pncDate == 0L) { + binding.ivFollowState.visibility = View.GONE + binding.llBenPwrTrackingDetails3.visibility = View.GONE + } else if (item.pncDate < System.currentTimeMillis() - TimeUnit.DAYS.toMillis(90) && + item.pncDate > System.currentTimeMillis() - TimeUnit.DAYS.toMillis(365)) { + binding.ivFollowState.visibility = View.VISIBLE + binding.llBenPwrTrackingDetails3.visibility = View.VISIBLE + binding.benVisitDate.text = HelperUtil.getDateStringFromLongStraight(item.pncDate) + } else { + binding.ivFollowState.visibility = View.GONE + binding.llBenPwrTrackingDetails3.visibility = View.VISIBLE + binding.benVisitDate.text = HelperUtil.getDateStringFromLongStraight(item.pncDate) + } + binding.visit = item binding.btnViewVisits.visibility = if (item.savedPncRecords.isEmpty()) View.INVISIBLE else View.VISIBLE binding.btnAddPnc.visibility = - if (item.allowFill) View.INVISIBLE else View.VISIBLE + if (item.allowFill) View.VISIBLE else View.INVISIBLE binding.clickListener = clickListener binding.executePendingBindings() @@ -59,14 +78,15 @@ class PncVisitListAdapter(private val clickListener: PncVisitClickListener? = nu class PncVisitClickListener( private val showVisits: (benId: Long) -> Unit, - private val addVisit: (benId: Long, visitNumber: Int) -> Unit, + private val addVisit: (benId: Long, hhId: Long ,visitNumber: Int) -> Unit, ) { fun showVisits(item: BenPncDomain) = showVisits( item.ben.benId, + ) - fun addVisit(item: BenPncDomain) = addVisit(item.ben.benId, + fun addVisit(item: BenPncDomain) = addVisit(item.ben.benId,item.ben.hhId, if (item.savedPncRecords.isEmpty()) 1 else item.savedPncRecords.maxOf { it.pncPeriod } + 1) } diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/PreviewAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/PreviewAdapter.kt new file mode 100644 index 000000000..d998b5e6f --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/PreviewAdapter.kt @@ -0,0 +1,55 @@ +package org.piramalswasthya.sakhi.adapters + + +import android.net.Uri +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.model.PreviewItem + +class PreviewAdapter( + private val items: List, + private val imageLoader: ((ImageView, Uri?) -> Unit)? = null // injectable for production (Glide/Picasso) +) : RecyclerView.Adapter() { + + class PreviewVH(view: View) : RecyclerView.ViewHolder(view) { + val tvLabel: TextView = view.findViewById(R.id.tvLabel) + val tvValue: TextView = view.findViewById(R.id.tvValue) + val ivImage: ImageView = view.findViewById(R.id.ivPreviewImage) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PreviewVH { + val v = LayoutInflater.from(parent.context).inflate(R.layout.item_preview_row, parent, false) + return PreviewVH(v) + } + + override fun onBindViewHolder(holder: PreviewVH, position: Int) { + val it = items[position] + holder.tvLabel.text = it.label + if (it.isImage) { + // show image view, hide text value + holder.tvValue.visibility = View.GONE + holder.ivImage.visibility = View.VISIBLE + imageLoader?.invoke(holder.ivImage, it.imageUri) ?: run { + // Minimal default loader (URI -> ImageView). Use Glide/Picasso in prod. + try { + it.imageUri?.let { uri -> + holder.ivImage.setImageURI(uri) + } ?: holder.ivImage.setImageResource(android.R.color.darker_gray) + } catch (e: Exception) { + holder.ivImage.setImageResource(android.R.color.darker_gray) + } + } + } else { + holder.ivImage.visibility = View.GONE + holder.tvValue.visibility = View.VISIBLE + holder.tvValue.text = it.value + } + } + + override fun getItemCount(): Int = items.size +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/PulsePolioCampaignAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/PulsePolioCampaignAdapter.kt new file mode 100644 index 000000000..b8e36bda1 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/PulsePolioCampaignAdapter.kt @@ -0,0 +1,61 @@ +package org.piramalswasthya.sakhi.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.databinding.LayoutPulsePolioCampaignItemBinding +import org.piramalswasthya.sakhi.model.PulsePolioCampaignCache +import org.piramalswasthya.sakhi.utils.HelperUtil + +class PulsePolioCampaignAdapter( + private val clickListener: PulsePolioCampaignClickListener? = null, +) : ListAdapter(BenDiffUtilCallBack) { + + private object BenDiffUtilCallBack : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: PulsePolioCampaignCache, + newItem: PulsePolioCampaignCache + ) = oldItem.id == newItem.id + + override fun areContentsTheSame( + oldItem: PulsePolioCampaignCache, + newItem: PulsePolioCampaignCache + ) = oldItem == newItem + } + + class PulsePolioCampaignViewHolder private constructor(private val binding: LayoutPulsePolioCampaignItemBinding) : + RecyclerView.ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): PulsePolioCampaignViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = LayoutPulsePolioCampaignItemBinding.inflate(layoutInflater, parent, false) + return PulsePolioCampaignViewHolder(binding) + } + } + + fun bind(item: PulsePolioCampaignCache, clickListener: PulsePolioCampaignClickListener?) { + binding.pulsePolioCampaign = item + binding.clickListener = clickListener + binding.executePendingBindings() + val startDate = HelperUtil.extractFieldValue(item.formDataJson,"start_date") + val numberOfChildren = HelperUtil.extractFieldValue(item.formDataJson,"children_vaccinated") + binding.tvDate.text = startDate + binding.numberOfChildren.text = binding.root.context.getString(R.string.number_of_children_label, numberOfChildren) + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PulsePolioCampaignViewHolder { + return PulsePolioCampaignViewHolder.from(parent) + } + + override fun onBindViewHolder(holder: PulsePolioCampaignViewHolder, position: Int) { + holder.bind(getItem(position), clickListener) + } + + class PulsePolioCampaignClickListener(val clickListener: (id: Int) -> Unit) { + fun onClick(id: Int) = clickListener(id) + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/PwRegistrationListAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/PwRegistrationListAdapter.kt index 7dd1fa1cd..3568fb5b5 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/adapters/PwRegistrationListAdapter.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/PwRegistrationListAdapter.kt @@ -6,11 +6,15 @@ import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao import org.piramalswasthya.sakhi.databinding.RvItemBenPwRegistrationListWithFormBinding import org.piramalswasthya.sakhi.model.BenWithPwrDomain +import org.piramalswasthya.sakhi.utils.RoleConstants class PwRegistrationListAdapter( - private val clickListener: ClickListener? = null + private val clickListener: ClickListener? = null, + private val pref: PreferenceDao? = null ) : ListAdapter (BenDiffUtilCallBack) { @@ -44,10 +48,18 @@ class PwRegistrationListAdapter( fun bind( item: BenWithPwrDomain, clickListener: ClickListener?, + pref: PreferenceDao? ) { + + if (pref?.getLoggedInUser()?.role.equals(RoleConstants.ROLE_ASHA_SUPERVISOR, true)) { + binding.btnFormEc1.visibility = View.GONE + } else { + binding.btnFormEc1.visibility = View.VISIBLE + } + binding.benWithPwr = item binding.ivSyncState.visibility = if (item.pwr == null) View.INVISIBLE else View.VISIBLE - binding.btnFormEc1.text = if (item.pwr == null) "Register" else "View" + binding.btnFormEc1.text = if (item.pwr == null) binding.root.resources.getString(R.string.register) else binding.root.resources.getString(R.string.view) binding.btnFormEc1.setBackgroundColor(binding.root.resources.getColor(if (item.pwr == null) android.R.color.holo_red_dark else android.R.color.holo_green_dark)) binding.clickListener = clickListener @@ -65,7 +77,7 @@ class PwRegistrationListAdapter( BenViewHolder.from(parent) override fun onBindViewHolder(holder: BenViewHolder, position: Int) { - holder.bind(getItem(position), clickListener) + holder.bind(getItem(position), clickListener, pref) } diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/SaasBahuSammelanAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/SaasBahuSammelanAdapter.kt new file mode 100644 index 000000000..5fafd0508 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/SaasBahuSammelanAdapter.kt @@ -0,0 +1,55 @@ +package org.piramalswasthya.sakhi.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.databinding.LayoutSaasBahuListBinding +import org.piramalswasthya.sakhi.model.SaasBahuSammelanCache + +class SaasBahuSammelanAdapter( + private val clickListener: SaasBahuSammelanAdapterClickListener? = null, +) : ListAdapter(BenDiffUtilCallBack) { + + private object BenDiffUtilCallBack : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: SaasBahuSammelanCache, + newItem: SaasBahuSammelanCache + ) = oldItem.id == newItem.id + + override fun areContentsTheSame( + oldItem: SaasBahuSammelanCache, + newItem: SaasBahuSammelanCache + ) = oldItem == newItem + } + + class SaasBahuSammelanViewHolder private constructor(private val binding: LayoutSaasBahuListBinding) : + RecyclerView.ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): SaasBahuSammelanViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = LayoutSaasBahuListBinding.inflate(layoutInflater, parent, false) + return SaasBahuSammelanViewHolder(binding) + } + } + + fun bind(item: SaasBahuSammelanCache, clickListener: SaasBahuSammelanAdapterClickListener?) { + binding.maaMeeting = item + binding.clickListener = clickListener + binding.executePendingBindings() + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SaasBahuSammelanViewHolder { + return SaasBahuSammelanViewHolder.from(parent) + } + + override fun onBindViewHolder(holder: SaasBahuSammelanViewHolder, position: Int) { + holder.bind(getItem(position), clickListener) + } + + class SaasBahuSammelanAdapterClickListener(val clickListener: (id: Long) -> Unit) { + fun onClick(id: Long) = clickListener(id) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/SyncDashboardStatusAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/SyncDashboardStatusAdapter.kt new file mode 100644 index 000000000..29ace8741 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/SyncDashboardStatusAdapter.kt @@ -0,0 +1,44 @@ +package org.piramalswasthya.sakhi.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.databinding.RvItemSyncDashboardStatusBinding +import org.piramalswasthya.sakhi.model.SyncStatusDomain + +class SyncDashboardStatusAdapter : + ListAdapter(DiffCallback) { + + private object DiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: SyncStatusDomain, newItem: SyncStatusDomain) = + oldItem.name == newItem.name + + override fun areContentsTheSame(oldItem: SyncStatusDomain, newItem: SyncStatusDomain) = + oldItem == newItem + } + + class ViewHolder( + private val binding: RvItemSyncDashboardStatusBinding + ) : RecyclerView.ViewHolder(binding.root) { + + fun bind(item: SyncStatusDomain) { + binding.tvEntityName.text = item.name + binding.tvSynced.text = item.synced.toString() + binding.tvUnsynced.text = item.notSynced.toString() + binding.tvSyncing.text = item.syncing.toString() + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val binding = RvItemSyncDashboardStatusBinding.inflate( + LayoutInflater.from(parent.context), parent, false + ) + return ViewHolder(binding) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.bind(getItem(position)) + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/SyncLogAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/SyncLogAdapter.kt new file mode 100644 index 000000000..9d98b5e2c --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/SyncLogAdapter.kt @@ -0,0 +1,73 @@ +package org.piramalswasthya.sakhi.adapters + +import android.graphics.Color +import android.graphics.drawable.GradientDrawable +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.databinding.RvItemSyncLogBinding +import org.piramalswasthya.sakhi.model.LogLevel +import org.piramalswasthya.sakhi.model.SyncLogEntry +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +class SyncLogAdapter : + ListAdapter(DiffCallback) { + + companion object { + private const val MAX_DISPLAY_LENGTH = 500 + } + + private object DiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: SyncLogEntry, newItem: SyncLogEntry) = + oldItem.id == newItem.id + + override fun areContentsTheSame(oldItem: SyncLogEntry, newItem: SyncLogEntry) = + oldItem == newItem + } + + private val timeFormat = SimpleDateFormat("HH:mm:ss.SSS", Locale.US) + + class ViewHolder( + private val binding: RvItemSyncLogBinding + ) : RecyclerView.ViewHolder(binding.root) { + + fun bind(item: SyncLogEntry, timeFormat: SimpleDateFormat) { + val (label, bgColor, textColor) = when (item.level) { + LogLevel.ERROR -> Triple("E", Color.parseColor("#D32F2F"), Color.parseColor("#D32F2F")) + LogLevel.WARN -> Triple("W", Color.parseColor("#F57C00"), Color.parseColor("#F57C00")) + LogLevel.INFO -> Triple("I", Color.parseColor("#1976D2"), Color.parseColor("#333333")) + LogLevel.DEBUG -> Triple("D", Color.parseColor("#757575"), Color.parseColor("#757575")) + } + + binding.tvLevel.text = label + val bg = GradientDrawable().apply { + shape = GradientDrawable.RECTANGLE + cornerRadius = 4f + setColor(bgColor) + } + binding.tvLevel.background = bg + + binding.tvTimestamp.text = "${timeFormat.format(Date(item.timestamp))} ${item.tag}" + binding.tvMessage.text = if (item.message.length > MAX_DISPLAY_LENGTH) + item.message.substring(0, MAX_DISPLAY_LENGTH) + "…" + else + item.message + binding.tvMessage.setTextColor(textColor) + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val binding = RvItemSyncLogBinding.inflate( + LayoutInflater.from(parent.context), parent, false + ) + return ViewHolder(binding) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.bind(getItem(position), timeFormat) + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/TBFollowUpDatesAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/TBFollowUpDatesAdapter.kt new file mode 100644 index 000000000..a84a684ac --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/TBFollowUpDatesAdapter.kt @@ -0,0 +1,58 @@ +package org.piramalswasthya.sakhi.adapters + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.adapters.FollowUpDatesAdapter +import org.piramalswasthya.sakhi.databinding.ItemFollowUpDateBinding +import org.piramalswasthya.sakhi.model.LeprosyFollowUpCache +import org.piramalswasthya.sakhi.model.TBConfirmedTreatmentCache +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +class TBFollowUpDatesAdapter : + ListAdapter(DiffCallback) { + + private val dateFormat = SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FollowUpViewHolder { + val binding = ItemFollowUpDateBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + return FollowUpViewHolder(binding) + } + + override fun onBindViewHolder(holder: FollowUpViewHolder, position: Int) { + val followUp = getItem(position) + holder.bind(followUp) + } + + inner class FollowUpViewHolder(private val binding: ItemFollowUpDateBinding) : + RecyclerView.ViewHolder(binding.root) { + + @SuppressLint("SuspiciousIndentation") + fun bind(followUp: TBConfirmedTreatmentCache) { + if(followUp.followUpDate!=null) + { binding.tvFollowUpDate.text = dateFormat.format(Date(followUp.followUpDate!!))} + binding.tvTreatmentStatus.visibility = View.GONE + binding.tvLastFollowUpDate.visibility = View.VISIBLE + } + } + + companion object DiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: TBConfirmedTreatmentCache, newItem: TBConfirmedTreatmentCache): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame(oldItem: TBConfirmedTreatmentCache, newItem: TBConfirmedTreatmentCache): Boolean { + return oldItem == newItem + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/TbConfirmedListAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/TbConfirmedListAdapter.kt new file mode 100644 index 000000000..c76698dbf --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/TbConfirmedListAdapter.kt @@ -0,0 +1,128 @@ +package org.piramalswasthya.sakhi.adapters + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao +import org.piramalswasthya.sakhi.databinding.RvItemTbConfirmedListBinding +import org.piramalswasthya.sakhi.databinding.RvItemTbSuspectedListBinding +import org.piramalswasthya.sakhi.model.BenWithTbSuspectedDomain + +class TbConfirmedListAdapter( private val clickListener: ClickListener? = null, +private val pref: PreferenceDao? = null +) : +ListAdapter +(BenDiffUtilCallBack) { + + + private object BenDiffUtilCallBack : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: BenWithTbSuspectedDomain, + newItem: BenWithTbSuspectedDomain + ) = oldItem.ben.benId == newItem.ben.benId + + override fun areContentsTheSame( + oldItem: BenWithTbSuspectedDomain, + newItem: BenWithTbSuspectedDomain + ) = oldItem == newItem + + } + + class BenViewHolder private constructor(private val binding: RvItemTbConfirmedListBinding) : + RecyclerView.ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): BenViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = RvItemTbConfirmedListBinding.inflate(layoutInflater, parent, false) + return BenViewHolder(binding) + } + } + + fun bind( + item: BenWithTbSuspectedDomain, + clickListener: ClickListener?, + pref: PreferenceDao? + ) { + + if (pref?.getLoggedInUser()?.role.equals("asha", true)) { + binding.btnFormTb.visibility = View.VISIBLE + } else { + binding.btnFormTb.visibility = View.INVISIBLE + } + + binding.benWithTb = item + + binding.ivSyncState.visibility = if (item.tbConfirmedList == null) View.INVISIBLE else View.VISIBLE + + if (item.ben.spouseName == "Not Available" && item.ben.fatherName == "Not Available") { + binding.father = true + binding.husband = false + binding.spouse = false + } else { + if (item.ben.gender == "MALE") { + binding.father = true + binding.husband = false + binding.spouse = false + } else if (item.ben.gender == "FEMALE") { + if (item.ben.ageInt > 15) { + binding.father = + item.ben.fatherName != "Not Available" && item.ben.spouseName == "Not Available" + binding.husband = item.ben.spouseName != "Not Available" + binding.spouse = false + } else { + binding.father = true + binding.husband = false + binding.spouse = false + } + } else { + binding.father = + item.ben.fatherName != "Not Available" && item.ben.spouseName == "Not Available" + binding.spouse = item.ben.spouseName != "Not Available" + binding.husband = false + } + } + + + binding.btnFormTb.setBackgroundColor(binding.root.resources.getColor(if (item.tbConfirmedList == null) android.R.color.holo_red_dark else android.R.color.holo_green_dark)) + binding.clickListener = clickListener + + binding.executePendingBindings() + + } + + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + )= BenViewHolder.from(parent) + + override fun onBindViewHolder( + holder: BenViewHolder, + position: Int + ) { + holder.bind(getItem(position), clickListener, pref) } + + /*override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ) = + BenViewHolder.from(parent) + + override fun onBindViewHolder(holder: BenViewHolder, position: Int) { + holder.bind(getItem(position), clickListener, pref) + }*/ + + + class ClickListener( + private val clickedForm: ((hhId: Long, benId: Long) -> Unit)? = null + + ) { + fun onClickForm(item: BenWithTbSuspectedDomain) = + clickedForm?.let { it(item.ben.hhId, item.ben.benId) } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/TbScreeningListAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/TbScreeningListAdapter.kt index c7dcda306..4f8c948b0 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/adapters/TbScreeningListAdapter.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/TbScreeningListAdapter.kt @@ -6,6 +6,7 @@ import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.R import org.piramalswasthya.sakhi.databinding.RvItemTbScreeningListBinding import org.piramalswasthya.sakhi.model.BenWithTbScreeningDomain @@ -43,6 +44,10 @@ class TbScreeningListAdapter( ) { binding.benWithTb = item + if(item.tb?.historyOfTb == true){ + binding.cvContent.visibility = View.GONE + } + binding.ivSyncState.visibility = if (item.tb == null) View.INVISIBLE else View.VISIBLE if (item.ben.spouseName == "Not Available" && item.ben.fatherName == "Not Available") { @@ -73,7 +78,7 @@ class TbScreeningListAdapter( } } - binding.btnFormTb.text = if (item.tb == null) "Screen" else "View Screen" + binding.btnFormTb.text = if (item.tb == null) binding.root.context.getString(R.string.screen) else binding.root.context.getString(R.string.view_screen) binding.btnFormTb.setBackgroundColor(binding.root.resources.getColor(if (item.tb == null) android.R.color.holo_red_dark else android.R.color.holo_green_dark)) binding.clickListener = clickListener diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/TbSuspectedListAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/TbSuspectedListAdapter.kt index de2229263..380394b2c 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/adapters/TbSuspectedListAdapter.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/TbSuspectedListAdapter.kt @@ -6,11 +6,15 @@ import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao import org.piramalswasthya.sakhi.databinding.RvItemTbSuspectedListBinding import org.piramalswasthya.sakhi.model.BenWithTbSuspectedDomain +import org.piramalswasthya.sakhi.utils.RoleConstants class TbSuspectedListAdapter( - private val clickListener: ClickListener? = null + private val clickListener: ClickListener? = null, + private val pref: PreferenceDao? = null ) : ListAdapter (BenDiffUtilCallBack) { @@ -40,10 +44,18 @@ class TbSuspectedListAdapter( fun bind( item: BenWithTbSuspectedDomain, clickListener: ClickListener?, + pref: PreferenceDao? ) { + + if (pref?.getLoggedInUser()?.role.equals(RoleConstants.ROLE_ASHA_SUPERVISOR, true)) { + binding.btnFormTb.visibility = View.INVISIBLE + } else { + binding.btnFormTb.visibility = View.VISIBLE + } + binding.benWithTb = item - binding.ivSyncState.visibility = if (item.tb == null) View.INVISIBLE else View.VISIBLE + binding.ivSyncState.visibility = if (item.tbSuspected == null) View.INVISIBLE else View.VISIBLE if (item.ben.spouseName == "Not Available" && item.ben.fatherName == "Not Available") { binding.father = true @@ -73,8 +85,8 @@ class TbSuspectedListAdapter( } } - binding.btnFormTb.text = if (item.tb == null) "Track" else "View" - binding.btnFormTb.setBackgroundColor(binding.root.resources.getColor(if (item.tb == null) android.R.color.holo_red_dark else android.R.color.holo_green_dark)) + binding.btnFormTb.text = if (item.tbSuspected == null) binding.root.resources.getString(R.string.track) else binding.root.resources.getString(R.string.view) + binding.btnFormTb.setBackgroundColor(binding.root.resources.getColor(if (item.tbSuspected == null) android.R.color.holo_red_dark else android.R.color.holo_green_dark)) binding.clickListener = clickListener binding.executePendingBindings() @@ -90,7 +102,7 @@ class TbSuspectedListAdapter( BenViewHolder.from(parent) override fun onBindViewHolder(holder: BenViewHolder, position: Int) { - holder.bind(getItem(position), clickListener) + holder.bind(getItem(position), clickListener, pref) } diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/UwinListAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/UwinListAdapter.kt new file mode 100644 index 000000000..d1e476356 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/UwinListAdapter.kt @@ -0,0 +1,53 @@ +package org.piramalswasthya.sakhi.adapters + + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.databinding.ItemUwinListBinding +import org.piramalswasthya.sakhi.model.UwinCache + +class UwinListAdapter( + private val clickListener: UwinClickListener? = null +) : ListAdapter(UwinDiffUtilCallback) { + + private object UwinDiffUtilCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: UwinCache, newItem: UwinCache): Boolean = + oldItem.id == newItem.id + + override fun areContentsTheSame(oldItem: UwinCache, newItem: UwinCache): Boolean = + oldItem == newItem + } + + class UwinViewHolder private constructor(private val binding: ItemUwinListBinding) : + RecyclerView.ViewHolder(binding.root) { + + fun bind(item: UwinCache, clickListener: UwinClickListener?) { + binding.uwin = item + binding.clickListener = clickListener + binding.executePendingBindings() + } + + companion object { + fun from(parent: ViewGroup): UwinViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = ItemUwinListBinding.inflate(layoutInflater, parent, false) + return UwinViewHolder(binding) + } + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UwinViewHolder { + return UwinViewHolder.from(parent) + } + + override fun onBindViewHolder(holder: UwinViewHolder, position: Int) { + holder.bind(getItem(position), clickListener) + } + + class UwinClickListener(val clickListener: (id: Int) -> Unit) { + fun onClick(id: Int) = clickListener(id) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/VHNCAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/VHNCAdapter.kt new file mode 100644 index 000000000..e192093bb --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/VHNCAdapter.kt @@ -0,0 +1,180 @@ +package org.piramalswasthya.sakhi.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.databinding.LayoutVhncItemBinding +import org.piramalswasthya.sakhi.model.VHNCCache + +class VHNCAdapter( + private val clickListener: VHNCClickListener? = null, +) : + ListAdapter + (BenDiffUtilCallBack) { + private object BenDiffUtilCallBack : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: VHNCCache, + newItem: VHNCCache + ) = oldItem.id == newItem.id + + override fun areContentsTheSame(oldItem: VHNCCache, newItem: VHNCCache)= oldItem == newItem + + + } + + class VHNCViewHolder private constructor(private val binding: LayoutVhncItemBinding) : + RecyclerView.ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): VHNCViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = LayoutVhncItemBinding.inflate(layoutInflater, parent, false) + return VHNCViewHolder(binding) + } + } + fun bind(item: VHNCCache, clickListener: VHNCClickListener?,) { + binding.vhnd = item + binding.clickListener = clickListener + binding.executePendingBindings() + + } + + + +// private fun setFormButtonColor(formNumber: Int, item: BenWithHRPADomain, role: Int?) { +// var hasForm: Boolean = false +// var completelyFilled: Boolean = false +// var formEnabled: Boolean = false +// buttonFlexibleWidth() +//// binding.btnForm3.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.icon_whatsapp, 0); +// +// +//// val formButton = when (formNumber) { +//// 1 -> { +//// binding.btnForm1.also { +//// hasForm = item.assess != null +//// item.assess?.let { +//// completelyFilled = +//// it.noOfDeliveries != null && +//// it.timeLessThan18m != null && +//// it.heightShort != null && +//// it.age != null && +//// it.rhNegative != null && +//// it.homeDelivery != null && +//// it.badObstetric != null && +//// it.multiplePregnancy != null +//// } +//// +//// formEnabled = true +//// } +//// } +//// +////// 2 -> { +////// binding.btnForm2.also { +////// hasForm = item.mbp != null +////// formEnabled = true +////// } +////// } +////// +////// 3 -> { +////// binding.btnForm3.also { +////// hasForm = item.mbp != null +////// if (hasForm) { +////// +////// formEnabled = true +////// buttonFlexibleWidth() +////// +////// } else { +////// +////// formEnabled = false +////// (binding.btnForm2.layoutParams as LinearLayout.LayoutParams).weight=1.8f +////// (binding.btnForm1.layoutParams as LinearLayout.LayoutParams).weight=1.2f +////// +////// } +////// +////// } +////// } +//// +//// else -> throw IllegalStateException("FormNumber>3") +//// } +//// formButton.visibility = if (formEnabled) View.VISIBLE else View.GONE +// +// +//// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { +//// if (hasForm) { +//// if (completelyFilled) { +//// formButton.setBackgroundColor( +//// binding.root.resources.getColor( +//// android.R.color.holo_green_dark, +//// binding.root.context.theme +//// ) +//// ) +//// } else { +//// formButton.setBackgroundColor( +//// binding.root.resources.getColor( +//// android.R.color.holo_orange_dark, +//// binding.root.context.theme +//// ) +//// ) +//// } +//// binding.btnForm3.setBackgroundColor( +//// binding.btnForm3.resources.getColor( +//// android.R.color.holo_purple, +//// binding.root.context.theme +//// ) +//// ) +//// +//// } else { +//// formButton.setBackgroundColor( +//// binding.root.resources.getColor( +//// android.R.color.holo_red_light, +//// binding.root.context.theme +//// ) +//// ) +//// } +//// +//// } else +//// if (hasForm) +//// formButton.setBackgroundColor(binding.root.resources.getColor(android.R.color.holo_green_dark)) +//// else +//// formButton.setBackgroundColor( +//// binding.root.resources.getColor( +//// android.R.color.holo_red_light, +//// ) +//// ) +//// +//// formButton.setTextColor( +//// binding.root.resources.getColor( +//// com.google.android.material.R.color.design_default_color_on_primary, +//// binding.root.context.theme +//// ) +//// ) +// +// } + +// private fun buttonFlexibleWidth() { +// (binding.btnForm2.layoutParams as LinearLayout.LayoutParams).weight=1.2f +// (binding.btnForm1.layoutParams as LinearLayout.LayoutParams).weight=0.8f +// (binding.btnForm3.layoutParams as LinearLayout.LayoutParams).weight=1.0f +// } + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ) = + VHNCViewHolder.from(parent) + + override fun onBindViewHolder(holder: VHNCViewHolder, position: Int) { + holder.bind(getItem(position), clickListener) + } + + + class VHNCClickListener( + private val clickedForm: ((id: Int) -> Unit)? = null + ) { + fun onClickForm1(item: VHNCCache) = clickedForm?.let { it(item.id!!) } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/VHNDAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/VHNDAdapter.kt new file mode 100644 index 000000000..f1c358b3a --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/VHNDAdapter.kt @@ -0,0 +1,180 @@ +package org.piramalswasthya.sakhi.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.databinding.LayoutVhndItemBinding +import org.piramalswasthya.sakhi.model.VHNDCache + +class VHNDAdapter( + private val clickListener: VHNDClickListener? = null, +) : + ListAdapter + (BenDiffUtilCallBack) { + private object BenDiffUtilCallBack : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: VHNDCache, + newItem: VHNDCache + ) = oldItem.id == newItem.id + + override fun areContentsTheSame(oldItem: VHNDCache, newItem: VHNDCache)= oldItem == newItem + + + } + + class VHNDViewHolder private constructor(private val binding: LayoutVhndItemBinding) : + RecyclerView.ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): VHNDViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = LayoutVhndItemBinding.inflate(layoutInflater, parent, false) + return VHNDViewHolder(binding) + } + } + fun bind(item: VHNDCache, clickListener: VHNDClickListener?,) { + binding.vhnd = item + binding.clickListener = clickListener + binding.executePendingBindings() + + } + + + +// private fun setFormButtonColor(formNumber: Int, item: BenWithHRPADomain, role: Int?) { +// var hasForm: Boolean = false +// var completelyFilled: Boolean = false +// var formEnabled: Boolean = false +// buttonFlexibleWidth() +//// binding.btnForm3.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.icon_whatsapp, 0); +// +// +//// val formButton = when (formNumber) { +//// 1 -> { +//// binding.btnForm1.also { +//// hasForm = item.assess != null +//// item.assess?.let { +//// completelyFilled = +//// it.noOfDeliveries != null && +//// it.timeLessThan18m != null && +//// it.heightShort != null && +//// it.age != null && +//// it.rhNegative != null && +//// it.homeDelivery != null && +//// it.badObstetric != null && +//// it.multiplePregnancy != null +//// } +//// +//// formEnabled = true +//// } +//// } +//// +////// 2 -> { +////// binding.btnForm2.also { +////// hasForm = item.mbp != null +////// formEnabled = true +////// } +////// } +////// +////// 3 -> { +////// binding.btnForm3.also { +////// hasForm = item.mbp != null +////// if (hasForm) { +////// +////// formEnabled = true +////// buttonFlexibleWidth() +////// +////// } else { +////// +////// formEnabled = false +////// (binding.btnForm2.layoutParams as LinearLayout.LayoutParams).weight=1.8f +////// (binding.btnForm1.layoutParams as LinearLayout.LayoutParams).weight=1.2f +////// +////// } +////// +////// } +////// } +//// +//// else -> throw IllegalStateException("FormNumber>3") +//// } +//// formButton.visibility = if (formEnabled) View.VISIBLE else View.GONE +// +// +//// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { +//// if (hasForm) { +//// if (completelyFilled) { +//// formButton.setBackgroundColor( +//// binding.root.resources.getColor( +//// android.R.color.holo_green_dark, +//// binding.root.context.theme +//// ) +//// ) +//// } else { +//// formButton.setBackgroundColor( +//// binding.root.resources.getColor( +//// android.R.color.holo_orange_dark, +//// binding.root.context.theme +//// ) +//// ) +//// } +//// binding.btnForm3.setBackgroundColor( +//// binding.btnForm3.resources.getColor( +//// android.R.color.holo_purple, +//// binding.root.context.theme +//// ) +//// ) +//// +//// } else { +//// formButton.setBackgroundColor( +//// binding.root.resources.getColor( +//// android.R.color.holo_red_light, +//// binding.root.context.theme +//// ) +//// ) +//// } +//// +//// } else +//// if (hasForm) +//// formButton.setBackgroundColor(binding.root.resources.getColor(android.R.color.holo_green_dark)) +//// else +//// formButton.setBackgroundColor( +//// binding.root.resources.getColor( +//// android.R.color.holo_red_light, +//// ) +//// ) +//// +//// formButton.setTextColor( +//// binding.root.resources.getColor( +//// com.google.android.material.R.color.design_default_color_on_primary, +//// binding.root.context.theme +//// ) +//// ) +// +// } + +// private fun buttonFlexibleWidth() { +// (binding.btnForm2.layoutParams as LinearLayout.LayoutParams).weight=1.2f +// (binding.btnForm1.layoutParams as LinearLayout.LayoutParams).weight=0.8f +// (binding.btnForm3.layoutParams as LinearLayout.LayoutParams).weight=1.0f +// } + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ) = + VHNDViewHolder.from(parent) + + override fun onBindViewHolder(holder: VHNDViewHolder, position: Int) { + holder.bind(getItem(position), clickListener) + } + + + class VHNDClickListener( + private val clickedForm: ((id: Int) -> Unit)? = null + ) { + fun onClickForm1(item: VHNDCache) = clickedForm?.let { it(item.id!!) } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/VisitsAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/VisitsAdapter.kt new file mode 100644 index 000000000..08fc872c5 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/VisitsAdapter.kt @@ -0,0 +1,39 @@ +package org.piramalswasthya.sakhi.adapters + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.R + +class VisitsAdapter( + private val visitNumbers: List, + private val onVisitClick: (Int) -> Unit +) : RecyclerView.Adapter() { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VisitViewHolder { + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.item_visit, parent, false) + return VisitViewHolder(view) + } + + override fun onBindViewHolder(holder: VisitViewHolder, position: Int) { + val visitNumber = visitNumbers[position] + holder.bind(visitNumber) + } + + override fun getItemCount() = visitNumbers.size + + inner class VisitViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + private val textView: TextView = itemView.findViewById(R.id.tvVisitNumber) + private val container: View = itemView.findViewById(R.id.container) + + fun bind(visitNumber: Int) { + textView.text = "Visit $visitNumber" + container.setOnClickListener { + onVisitClick(visitNumber) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/VisitsListAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/VisitsListAdapter.kt new file mode 100644 index 000000000..c27da6014 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/VisitsListAdapter.kt @@ -0,0 +1,60 @@ +package org.piramalswasthya.sakhi.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.databinding.RvItemVisitsBinding +import org.piramalswasthya.sakhi.helpers.Languages +import org.piramalswasthya.sakhi.model.MalariaScreeningCache +import org.piramalswasthya.sakhi.model.getDateStrFromLong +import org.piramalswasthya.sakhi.utils.HelperUtil +import java.text.SimpleDateFormat +import java.util.Locale + + +class VisitsListAdapter : + ListAdapter(DiffCallback) { + private val dateFormat = SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FollowUpViewHolder { + val binding = RvItemVisitsBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + return FollowUpViewHolder(binding) + } + + override fun onBindViewHolder(holder: FollowUpViewHolder, position: Int) { + val followUp = getItem(position) + holder.bind(followUp) + } + + inner class FollowUpViewHolder(private val binding: RvItemVisitsBinding) : + RecyclerView.ViewHolder(binding.root) { + + fun bind(followUp: MalariaScreeningCache) { + val ctx = binding.root.context + val englishArray = HelperUtil.getLocalizedResources(ctx, Languages.ENGLISH) + .getStringArray(R.array.dc_case_status) + val localizedArray = ctx.resources.getStringArray(R.array.dc_case_status) + val idx = englishArray.indexOf(followUp.caseStatus) + binding.tvVisitNumber.text = ctx.getString(R.string.visit_format, followUp.visitId) + binding.tvFollowUpDate.text = getDateStrFromLong(followUp.caseDate) + binding.tvTreatmentStatus.text = if (idx >= 0) localizedArray[idx] else localizedArray[0] + } + } + + companion object DiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: MalariaScreeningCache, newItem: MalariaScreeningCache): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame(oldItem: MalariaScreeningCache, newItem: MalariaScreeningCache): Boolean { + return oldItem == newItem + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/dynamicAdapter/BottleAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/dynamicAdapter/BottleAdapter.kt new file mode 100644 index 000000000..64f14467e --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/dynamicAdapter/BottleAdapter.kt @@ -0,0 +1,36 @@ +package org.piramalswasthya.sakhi.adapters.dynamicAdapter + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.model.BottleItem + +class BottleAdapter(private val items: List) : + RecyclerView.Adapter() { + + inner class BottleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val tvSrNo = itemView.findViewById(R.id.tvSrNo) + val tvBottleNumber = itemView.findViewById(R.id.tvBottleNumber) + val tvDate = itemView.findViewById(R.id.tvDate) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BottleViewHolder { + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.item_bottle_row, parent, false) + return BottleViewHolder(view) + } + + @SuppressLint("SetTextI18n") + override fun onBindViewHolder(holder: BottleViewHolder, position: Int) { + val item = items[position] + holder.tvSrNo.text = (position+1).toString() + holder.tvBottleNumber.text = item.bottleNumber + holder.tvDate.text = item.dateOfProvision + } + + override fun getItemCount(): Int = items.size +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/dynamicAdapter/FilariaMdaCampaignAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/dynamicAdapter/FilariaMdaCampaignAdapter.kt new file mode 100644 index 000000000..7296ce258 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/dynamicAdapter/FilariaMdaCampaignAdapter.kt @@ -0,0 +1,62 @@ +package org.piramalswasthya.sakhi.adapters.dynamicAdapter + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.databinding.LayoutMdaCampaignItemBinding +import org.piramalswasthya.sakhi.model.dynamicModel.MDACampaignItem + +class FilariaMdaCampaignAdapter ( + private val clickListener: MdaClickListener? = null, +) : + ListAdapter + (BenDiffUtilCallBack) { + private object BenDiffUtilCallBack : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: MDACampaignItem, + newItem: MDACampaignItem + ) = oldItem.srNo == newItem.srNo + + override fun areContentsTheSame(oldItem: MDACampaignItem, newItem: MDACampaignItem)= oldItem == newItem + + + } + + class PHCViewHolder private constructor(private val binding: LayoutMdaCampaignItemBinding) : + RecyclerView.ViewHolder(binding.root) { + companion object { + fun from(parent: ViewGroup): PHCViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = LayoutMdaCampaignItemBinding.inflate(layoutInflater, parent, false) + return PHCViewHolder(binding) + } + } + fun bind(item: MDACampaignItem, clickListener: MdaClickListener?,) { + binding.phc = item + binding.clickListener = clickListener + binding.executePendingBindings() + + } + + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ) = + PHCViewHolder.from(parent) + + override fun onBindViewHolder(holder: PHCViewHolder, position: Int) { + holder.bind(getItem(position), clickListener) + } + + + class MdaClickListener( + private val clickedForm: ((date: String) -> Unit)? = null + ) { + fun onClickForm1(item: MDACampaignItem) = clickedForm?.let { it(item.startDate!!) } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/dynamicAdapter/FollowUpVisitAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/dynamicAdapter/FollowUpVisitAdapter.kt new file mode 100644 index 000000000..41b844d49 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/dynamicAdapter/FollowUpVisitAdapter.kt @@ -0,0 +1,101 @@ +package org.piramalswasthya.sakhi.adapters.dynamicAdapter + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.databinding.ItemFollowUpHeaderBinding +import org.piramalswasthya.sakhi.databinding.ItemFollowUpVisitBinding + +class FollowUpVisitAdapter : ListAdapter( + DiffCallback +) { + + sealed class FollowUpVisitItem { + object Header : FollowUpVisitItem() + data class VisitDate(val sno: String, val date: String) : FollowUpVisitItem() + } + + override fun getItemViewType(position: Int): Int { + return when (getItem(position)) { + is FollowUpVisitItem.Header -> TYPE_HEADER + is FollowUpVisitItem.VisitDate -> TYPE_ITEM + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + return when (viewType) { + TYPE_HEADER -> { + val binding = ItemFollowUpHeaderBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + HeaderViewHolder(binding) + } + TYPE_ITEM -> { + val binding = ItemFollowUpVisitBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + VisitDateViewHolder(binding) + } + else -> throw IllegalArgumentException("Unknown view type: $viewType") + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is HeaderViewHolder -> holder.bind() + is VisitDateViewHolder -> { + val item = getItem(position) as FollowUpVisitItem.VisitDate + holder.bind(item) + } + } + } + + class HeaderViewHolder(private val binding: ItemFollowUpHeaderBinding) : + RecyclerView.ViewHolder(binding.root) { + + fun bind() { + binding.executePendingBindings() + } + } + + class VisitDateViewHolder(private val binding: ItemFollowUpVisitBinding) : + RecyclerView.ViewHolder(binding.root) { + + fun bind(item: FollowUpVisitItem.VisitDate) { + binding.sno = item.sno + binding.visitDate = item.date + binding.executePendingBindings() + } + } + + companion object { + private const val TYPE_HEADER = 0 + private const val TYPE_ITEM = 1 + + object DiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: FollowUpVisitItem, newItem: FollowUpVisitItem): Boolean { + return when { + oldItem is FollowUpVisitItem.Header && newItem is FollowUpVisitItem.Header -> true + oldItem is FollowUpVisitItem.VisitDate && newItem is FollowUpVisitItem.VisitDate -> + oldItem.sno == newItem.sno && oldItem.date == newItem.date + else -> false + } + } + + override fun areContentsTheSame(oldItem: FollowUpVisitItem, newItem: FollowUpVisitItem): Boolean { + return when { + oldItem is FollowUpVisitItem.Header && newItem is FollowUpVisitItem.Header -> true + oldItem is FollowUpVisitItem.VisitDate && newItem is FollowUpVisitItem.VisitDate -> + oldItem.sno == newItem.sno && oldItem.date == newItem.date + else -> false + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/dynamicAdapter/FormRendererAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/dynamicAdapter/FormRendererAdapter.kt new file mode 100644 index 000000000..5b94f3264 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/dynamicAdapter/FormRendererAdapter.kt @@ -0,0 +1,1142 @@ + package org.piramalswasthya.sakhi.adapters.dynamicAdapter + + import android.app.AlertDialog + import android.app.DatePickerDialog + import android.content.ActivityNotFoundException + import android.content.Context + import android.content.Intent + import android.graphics.BitmapFactory + import android.graphics.Color + import android.graphics.Typeface + import android.net.Uri + import android.text.* + import android.text.style.ForegroundColorSpan + import android.view.* + import android.view.inputmethod.InputMethodManager + import android.util.Base64 + import android.widget.* + import androidx.appcompat.content.res.AppCompatResources + import androidx.core.content.ContextCompat + import androidx.core.content.FileProvider + import androidx.recyclerview.widget.RecyclerView + import com.google.android.material.textfield.TextInputEditText + import com.google.android.material.textfield.TextInputLayout + import kotlinx.coroutines.CoroutineScope + import kotlinx.coroutines.Dispatchers + import kotlinx.coroutines.Job + import kotlinx.coroutines.MainScope + import kotlinx.coroutines.cancelChildren + import kotlinx.coroutines.delay + import kotlinx.coroutines.launch + import org.piramalswasthya.sakhi.R + import org.piramalswasthya.sakhi.configuration.dynamicDataSet.FormField + import org.piramalswasthya.sakhi.utils.HelperUtil + import org.piramalswasthya.sakhi.utils.HelperUtil.findFragmentActivity + import org.piramalswasthya.sakhi.utils.dynamicFormConstants.FormConstants + import timber.log.Timber + import java.io.File + import java.io.FileOutputStream + import java.text.SimpleDateFormat + import java.util.* + + class FormRendererAdapter( + private val fields: MutableList, + private val isViewOnly: Boolean = false, + private val minVisitDate: Date? = null, + private val maxVisitDate: Date? = null, + private val isSNCU: Boolean = false, + private val onValueChanged: (FormField, Any?) -> Unit, + private val onShowAlert: ((String, String) -> Unit)? = null, + private val formId: String? =null , + + + + ) : RecyclerView.Adapter() { + + fun getUpdatedFields(): List = fields + + fun getCurrentFields(): List = fields + fun updateFields(newFields: List) { + if (fields.size != newFields.size) { + fields.clear() + fields.addAll(newFields) + notifyDataSetChanged() + return + } + + newFields.forEachIndexed { index, newField -> + val oldField = fields.getOrNull(index) + + val shouldUpdate = oldField == null || + oldField.value != newField.value || + oldField.errorMessage != newField.errorMessage || + oldField.visible != newField.visible + + if (shouldUpdate) { + fields[index] = newField + notifyItemChanged(index) + } + } + } + + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FormViewHolder { + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.item_form_field, parent, false) + return FormViewHolder(view) + } + + override fun onBindViewHolder(holder: FormViewHolder, position: Int) { + val field = fields[position] + holder.itemView.visibility = View.VISIBLE + try { + holder.bind(field) + } catch (e: Exception) { + Timber.tag("FormRendererAdapter").e(e, "Error binding field: " + field.fieldId) + } + } + + override fun onViewRecycled(holder: FormViewHolder) { + holder.clear() + super.onViewRecycled(holder) + } + + override fun onViewDetachedFromWindow(holder: FormViewHolder) { + holder.clear() + super.onViewDetachedFromWindow(holder) + } + + override fun getItemCount(): Int = fields.size + + inner class FormViewHolder(view: View) : RecyclerView.ViewHolder(view) { + private val label: TextView = view.findViewById(R.id.tvLabel) + private val inputContainer: ViewGroup = view.findViewById(R.id.inputContainer) + private val viewHolderScope = MainScope() + private var muacDebounceJob: Job? = null + + private fun loadImageFromPath(context: android.content.Context, filePath: String, imageView: ImageView) { + if (filePath.isBlank()) { + imageView.setImageResource(R.drawable.ic_doc_upload) + return + } + + try { + when { + filePath.endsWith(".pdf", ignoreCase = true) || + (filePath.startsWith("content://") && + context.contentResolver.getType(Uri.parse(filePath))?.contains("pdf") == true) -> { + imageView.setImageResource(R.drawable.ic_doc_upload) + imageView.setOnClickListener { + val uri = Uri.parse(filePath) + val intent = Intent(Intent.ACTION_VIEW).apply { + setDataAndType(uri, "application/pdf") + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + } + try { + context.startActivity(intent) + } catch (e: ActivityNotFoundException) { + Toast.makeText(context, "No app found to open PDF", Toast.LENGTH_SHORT).show() + } + } + } + + filePath.startsWith("JVBERi0") || filePath.startsWith("%PDF") -> { + imageView.setImageResource(R.drawable.ic_doc_upload) + imageView.setOnClickListener { + try { + val decodedBytes = Base64.decode(filePath, Base64.DEFAULT) + val tempPdf = File.createTempFile("decoded_pdf_", ".pdf", context.cacheDir) + FileOutputStream(tempPdf).use { it.write(decodedBytes) } + + val uri = FileProvider.getUriForFile( + context, + "${context.packageName}.provider", + tempPdf + ) + + val intent = Intent(Intent.ACTION_VIEW).apply { + setDataAndType(uri, "application/pdf") + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + } + context.startActivity(intent) + } catch (e: Exception) { + Toast.makeText(context, "Failed to open PDF", Toast.LENGTH_SHORT).show() + Timber.e(e, "Failed to open Base64 PDF") + } + } + } + + filePath.startsWith("data:image", ignoreCase = true) || + filePath.startsWith("/9j/") || + filePath.startsWith("iVBOR") || + (filePath.length > 100 && + !filePath.startsWith("content://") && + !filePath.startsWith("file://")) -> { + try { + val base64String = filePath.substringAfter(",", filePath) + val decodedBytes = Base64.decode(base64String, Base64.DEFAULT) + val bitmap = BitmapFactory.decodeByteArray(decodedBytes, 0, decodedBytes.size) + imageView.setImageBitmap(bitmap) + } catch (e: Exception) { + Timber.e(e, "Failed to decode base64 image") + imageView.setImageResource(R.drawable.ic_person) + } + } + + else -> { + val uri = Uri.parse(filePath) + imageView.setImageURI(uri) + } + } + } catch (e: Exception) { + Timber.tag("FormRendererAdapter").e(e, "Failed to load file: $filePath") + imageView.setImageResource(R.drawable.ic_doc_upload) + } + } + + fun clear() { + muacDebounceJob?.cancel() + muacDebounceJob = null + viewHolderScope.coroutineContext.cancelChildren() + } + + fun bind(field: FormField) { + + itemView.visibility = View.VISIBLE + if (!field.visible) { + itemView.visibility = View.GONE + return + } else { + itemView.visibility = View.VISIBLE + } + if (field.isRequired) { + val labelText = "${field.label} *" + val spannable = SpannableString(labelText) + spannable.setSpan( + ForegroundColorSpan(Color.RED), + labelText.length - 1, + labelText.length, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + label.text = spannable + } else { + label.text = field.label + } + + + inputContainer.removeAllViews() + + fun addWithError(inputView: View, field: FormField) { + val wrapper = LinearLayout(itemView.context).apply { + orientation = LinearLayout.VERTICAL + layoutParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ) + } + + wrapper.addView(inputView) + + val errorTextView = TextView(itemView.context).apply { + tag = "field_error_tv" + setTextColor(android.graphics.Color.RED) + textSize = 12f + text = field.errorMessage ?: "" + visibility = if (field.errorMessage.isNullOrBlank()) View.GONE else View.VISIBLE + } + + wrapper.addView(errorTextView) + + inputContainer.removeAllViews() + inputContainer.addView(wrapper) + + } + + + + + when (field.type) { + "label" -> { + val context = itemView.context + val value = field.defaultValue.toString() + + if (!value.isNullOrEmpty()) { + val textView = TextView(context).apply { + layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ).apply { + setMargins(0, 16, 0, 8) + } + + text = value + setTypeface(typeface, Typeface.BOLD) + setTextColor(ContextCompat.getColor(context, android.R.color.black)) + setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_TitleMedium) + } + + addWithError(textView, field) + } + + } + + + "multicheckbox" -> { + val context = itemView.context + + val container = LinearLayout(context).apply { + orientation = LinearLayout.VERTICAL + layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ).apply { setMargins(0, 8, 0, 8) } + } + + val selectedValues: MutableSet = when (val v = field.value) { + is Set<*> -> v.filterIsInstance().toMutableSet() + is List<*> -> v.filterIsInstance().toMutableSet() + is String -> v.split(",").map { it.trim() }.toMutableSet() + else -> mutableSetOf() + } + field.options?.forEach { option -> + val checkBox = CheckBox(context).apply { + text = option.label + isChecked = selectedValues.contains(option.value) + isEnabled = !isViewOnly && field.isEditable + layoutParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ).apply { setMargins(0, 4, 0, 4) } + } + + if (!isViewOnly && field.isEditable) { + checkBox.setOnCheckedChangeListener { _, isChecked -> + if (isChecked) { + selectedValues.add(option.value) + } else { + selectedValues.remove(option.value) + } + field.value = selectedValues + onValueChanged(field, selectedValues) + } + } + + container.addView(checkBox) + } + + // Error TextView + val errorTextView = TextView(context).apply { + tag = "field_error_tv" + setTextColor(Color.RED) + textSize = 12f + text = field.errorMessage ?: "" + visibility = if (field.errorMessage.isNullOrBlank()) View.GONE else View.VISIBLE + } + + container.addView(errorTextView) + + inputContainer.removeAllViews() + inputContainer.addView(container) + } + + + "text" -> { + val context = itemView.context + + val textInputLayout = TextInputLayout( + context, + null, + com.google.android.material.R.style.Widget_Material3_TextInputLayout_OutlinedBox + ).apply { + layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ).apply { + setMargins(0, 16, 0, 8) + } + + hint = field.placeholder ?: "Enter ${field.label}" + boxBackgroundMode = TextInputLayout.BOX_BACKGROUND_OUTLINE + boxStrokeColor = ContextCompat.getColor(context, R.color.md_theme_light_primary) + boxStrokeWidthFocused = 2 + setBoxCornerRadii(12f, 12f, 12f, 12f) + } + + val editText = TextInputEditText(context).apply { + layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + setPadding(32, 24, 32, 24) + + background = null + setText(field.value as? String ?: "") + inputType = InputType.TYPE_CLASS_TEXT + isEnabled = !isViewOnly + setTextColor(ContextCompat.getColor(context, android.R.color.black)) + setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_BodyLarge) + } + + if (!isViewOnly) { + editText.addTextChangedListener(object : TextWatcher { + override fun afterTextChanged(s: Editable?) { + val normalized = org.piramalswasthya.sakhi.utils.StringMappingUtil.convertDigits(s.toString()) + val value = normalized.toFloatOrNull() + field.value = value + onValueChanged(field, s.toString()) + + } + + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} + }) + } + + textInputLayout.addView(editText) + addWithError(textInputLayout, field) + } + + + + /* "number" -> { + val context = itemView.context + + val textInputLayout = TextInputLayout( + context, + null, + com.google.android.material.R.style.Widget_Material3_TextInputLayout_OutlinedBox + ).apply { + layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ).apply { + setMargins(0, 16, 0, 8) + } + + hint = field.placeholder ?: "Enter ${field.label}" + boxBackgroundMode = TextInputLayout.BOX_BACKGROUND_OUTLINE + boxStrokeColor = ContextCompat.getColor(context, R.color.md_theme_light_primary) + boxStrokeWidthFocused = 2 + setBoxCornerRadii(12f, 12f, 12f, 12f) + } + + val editText = TextInputEditText(context).apply { + layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + setPadding(32, 24, 32, 24) + + background = null + setText(field.value?.let { if (it is Number) it.toString() else it.toString() } ?: "") + inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL + isEnabled = !isViewOnly + setTextColor(ContextCompat.getColor(context, android.R.color.black)) + setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_BodyLarge) + } + + if (!isViewOnly) { + + editText.addTextChangedListener(object : TextWatcher { + override fun afterTextChanged(s: Editable?) { + val normalized = org.piramalswasthya.sakhi.utils.StringMappingUtil.convertDigits(s.toString()) + val value = normalized.toFloatOrNull() + field.value = value + if (field.fieldId.contains("muac", ignoreCase = true)) { + muacDebounceJob?.cancel() + muacDebounceJob = CoroutineScope(Dispatchers.Main).launch { + delay(1500) + onValueChanged(field, value) + } + } else { + onValueChanged(field, value) + } + } + + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} + }) + } + + textInputLayout.addView(editText) + addWithError(textInputLayout, field) + }*/ + "number" -> { + val context = itemView.context + + val textInputLayout = TextInputLayout( + context, + null, + com.google.android.material.R.style.Widget_Material3_TextInputLayout_OutlinedBox + ).apply { + layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ).apply { + setMargins(0, 16, 0, 8) + } + + hint = field.placeholder ?: "Enter ${field.label}" + boxBackgroundMode = TextInputLayout.BOX_BACKGROUND_OUTLINE + boxStrokeColor = ContextCompat.getColor(context, R.color.md_theme_light_primary) + boxStrokeWidthFocused = 2 + setBoxCornerRadii(12f, 12f, 12f, 12f) + } + + val editText = TextInputEditText(context).apply { + layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + setPadding(32, 24, 32, 24) + + background = null + setText(field.value?.let { if (it is Number) it.toString() else it.toString() } ?: "") + inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL + + val isIFABottleCount = field.fieldId == "ifa_bottle_count" + val isEditable = !isViewOnly && !isIFABottleCount + + isEnabled = isEditable + + if (isIFABottleCount && !isEditable) { + setTextColor(ContextCompat.getColor(context, R.color.md_theme_light_onSurfaceVariant)) + } else { + setTextColor(ContextCompat.getColor(context, android.R.color.black)) + } + + setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_BodyLarge) + } + + if (editText.isEnabled) { + editText.addTextChangedListener(object : TextWatcher { + override fun afterTextChanged(s: Editable?) { + val normalized = org.piramalswasthya.sakhi.utils.StringMappingUtil.convertDigits(s.toString()) + val value = normalized.toFloatOrNull() + field.value = value + if (field.fieldId.contains("muac", ignoreCase = true)) { + muacDebounceJob?.cancel() + muacDebounceJob = viewHolderScope.launch { + delay(1500) + onValueChanged(field, value) + } + } else { + onValueChanged(field, value) + } + } + + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} + }) + } + + textInputLayout.addView(editText) + addWithError(textInputLayout, field) + } + + "dropdown" -> { + val context = itemView.context + val isEditableField = field.fieldId != "visit_day" && field.isEditable && !isViewOnly + + val textInputLayout = TextInputLayout( + context, + null, + com.google.android.material.R.style.Widget_Material3_TextInputLayout_OutlinedBox + ).apply { + layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ).apply { + setMargins(0, 16, 0, 8) + } + hint = field.placeholder ?: context.getString(R.string.dynamic_form_select, field.label) + boxBackgroundMode = TextInputLayout.BOX_BACKGROUND_OUTLINE + boxStrokeColor = ContextCompat.getColor(context, R.color.md_theme_light_primary) + boxStrokeWidthFocused = 2 + setBoxCornerRadii(12f, 12f, 12f, 12f) + } + + val editText = TextInputEditText(context).apply { + layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + isFocusable = false + isClickable = isEditableField + isEnabled = isEditableField + val displayText = field.options?.find { it.value == field.value?.toString() }?.label + ?: field.value?.toString() ?: "" + setText(displayText) + background = null + setTextColor(ContextCompat.getColor(context, android.R.color.black)) + setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_BodyLarge) + setPadding(16, 24, 16, 24) + val dropdownIcon = AppCompatResources.getDrawable( + context, + R.drawable.ic_arrow_drop_down + ) + dropdownIcon?.setTint( + ContextCompat.getColor( + context, + if (isEnabled) R.color.md_theme_light_primary else android.R.color.darker_gray + ) + ) + setCompoundDrawablesWithIntrinsicBounds(null, null, dropdownIcon, null) + compoundDrawablePadding = 16 + } + + if (isEditableField) { + editText.setOnClickListener { + val options = field.options ?: emptyList() + val labels = options.map { it.label }.toTypedArray() + val builder = AlertDialog.Builder(context) + builder.setTitle(context.getString(R.string.dynamic_form_select, field.label)) + builder.setItems(labels) { _, which -> + val selected = options[which] + editText.setText(selected.label) + field.value = selected.value + onValueChanged(field, selected.value) + } + builder.show() + } + } + + textInputLayout.addView(editText) + addWithError(textInputLayout, field) + } + + + "date" -> { + val context = itemView.context + val sdf = SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH) + val today = Calendar.getInstance().time + val todayStr = sdf.format(today) + + if (field.fieldId == "visit_date" && + (field.value == null || (field.value as? String)?.isBlank() == true) + ) { + field.value = todayStr + } + + val isFieldDisabled = field.fieldId == "due_date" + val isFieldEditable = field.isEditable && !isViewOnly && !isFieldDisabled + + val textInputLayout = TextInputLayout( + context, null, + com.google.android.material.R.style.Widget_Material3_TextInputLayout_OutlinedBox + ).apply { + layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ).apply { setMargins(0, 16, 0, 8) } + + hint = field.placeholder ?: context.getString(R.string.dynamic_form_select, field.label) + boxBackgroundMode = TextInputLayout.BOX_BACKGROUND_OUTLINE + boxStrokeColor = ContextCompat.getColor(context, R.color.md_theme_light_primary) + boxStrokeWidthFocused = 2 + setBoxCornerRadii(12f, 12f, 12f, 12f) + } + + val editText = TextInputEditText(context).apply { + layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + setPadding(32, 24, 32, 24) + background = null + setText(field.value as? String ?: "") + isFocusable = false + isClickable = isFieldEditable + isEnabled = isFieldEditable + setCompoundDrawablesWithIntrinsicBounds( + null, null, + ContextCompat.getDrawable(context, R.drawable.ic_calendar), + null + ) + compoundDrawablePadding = 24 + setTextColor(ContextCompat.getColor(context, android.R.color.black)) + setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_BodyLarge) + } + + textInputLayout.addView(editText) + + fun getDate(fieldId: String): Date? { + val v = fields.find { it.fieldId == fieldId }?.value as? String + return try { sdf.parse(v ?: "") } catch (e: Exception) { null } + } + + fun setError(fieldId: String, msg: String) { + val f = fields.find { it.fieldId == fieldId } + f?.errorMessage = msg + val idx = fields.indexOf(f) + if (idx >= 0) notifyItemChanged(idx) + } + + fun clearError(fieldId: String) { + val f = fields.find { it.fieldId == fieldId } + if (f?.errorMessage != null) { + f.errorMessage = null + val idx = fields.indexOf(f) + if (idx >= 0) notifyItemChanged(idx) + } + } + + if (isFieldEditable) { + editText.setOnClickListener { + val activity = editText.context.findFragmentActivity() + ?: return@setOnClickListener + val originalLocale = Locale.getDefault() + HelperUtil.setEnLocaleForDatePicker(activity) + + val calendar = Calendar.getInstance() + + var minDate: Date? = null + var maxDate: Date? = null + + if (field.fieldId == "ifa_provision_date") { + minDate = minVisitDate + maxDate = maxVisitDate + + if (minDate == null) { + calendar.time = today + calendar.add(Calendar.MONTH, -2) + minDate = calendar.time + } + if (maxDate == null) { + maxDate = today + } + } + else { + + + if (formId == FormConstants.IFA_DISTRIBUTION_FORM_ID|| formId == FormConstants.ANC_FORM_ID || formId == FormConstants.MDA_DISTRIBUTION_FORM_ID) { + minDate = minVisitDate + maxDate = maxVisitDate + + if (minDate == null) { + calendar.time = today + calendar.add(Calendar.MONTH, -2) + minDate = calendar.time + } + if (maxDate == null) { + maxDate = today + } + } else if (formId == FormConstants.PULSE_POLIO_CAMPAIGN_FORM_ID || + formId == FormConstants.ORS_CAMPAIGN_FORM_ID || formId == FormConstants.LF_MDA_CAMPAIGN ) { + if (field.fieldId == "end_date") { + val startDateValue = getDate("start_date") + if (startDateValue != null) { + val minDateCalendar = Calendar.getInstance() + minDateCalendar.time = startDateValue + minDateCalendar.add(Calendar.DAY_OF_MONTH, 1) + minDate = minDateCalendar.time + } else { + minDate = minVisitDate + } + } else { + minDate = minVisitDate + } + + maxDate = maxVisitDate + + if (minDate == null) { + calendar.time = today + calendar.add(Calendar.MONTH, -2) + minDate = calendar.time + } + if (maxDate == null) { + maxDate = today + } + } + else{ + minDate = when (field.fieldId) { + "visit_date" -> { + if (formId == FormConstants.HBNC_FORM_ID || formId == FormConstants.HBYC_FORM_ID) { + val dueDate = getDate("due_date") + when { + dueDate != null && minVisitDate != null -> + if (dueDate.after(minVisitDate)) dueDate else minVisitDate + dueDate != null -> dueDate + minVisitDate != null -> minVisitDate + else -> null + } + } else { + null + } + } + "nrc_admission_date" -> getDate("visit_date") + "nrc_discharge_date" -> getDate("nrc_admission_date") + "follow_up_visit_date" -> getDate("nrc_discharge_date") + else -> null + } + maxDate = today + } + + } + + DatePickerDialog( + context, + { _, year, month, dayOfMonth -> + try { + val dateStr = String.format( + Locale.ENGLISH, + "%02d-%02d-%04d", + dayOfMonth, + month + 1, + year + ) + + editText.setText(dateStr) + field.value = dateStr + field.errorMessage = null + onValueChanged(field, dateStr) + + if (field.fieldId == "ifa_provision_date") { + val selectedDate = sdf.parse(dateStr) + if (minDate != null && selectedDate.before(minDate)) { + field.errorMessage = "Date cannot be before ${sdf.format(minDate)}" + } else if (maxDate != null && selectedDate.after(maxDate)) { + field.errorMessage = "Date cannot be after ${sdf.format(maxDate)}" + } + notifyItemChanged(adapterPosition) + } + + if (formId == FormConstants.PULSE_POLIO_CAMPAIGN_FORM_ID || + formId == FormConstants.ORS_CAMPAIGN_FORM_ID || formId == FormConstants.LF_MDA_CAMPAIGN) { + val selectedDate = sdf.parse(dateStr) + if (minDate != null && selectedDate.before(minDate)) { + field.errorMessage = "Date cannot be before ${sdf.format(minDate)}" + } else if (maxDate != null && selectedDate.after(maxDate)) { + field.errorMessage = "Date cannot be after ${sdf.format(maxDate)}" + } + + if (field.fieldId == "end_date") { + val startDateValue = getDate("start_date") + if (startDateValue != null) { + val minEndDate = Calendar.getInstance() + minEndDate.time = startDateValue + minEndDate.add(Calendar.DAY_OF_MONTH, 1) + minEndDate.set(Calendar.HOUR_OF_DAY, 0) + minEndDate.set(Calendar.MINUTE, 0) + minEndDate.set(Calendar.SECOND, 0) + minEndDate.set(Calendar.MILLISECOND, 0) + + val selectedDateCal = Calendar.getInstance() + selectedDateCal.time = selectedDate + selectedDateCal.set(Calendar.HOUR_OF_DAY, 0) + selectedDateCal.set(Calendar.MINUTE, 0) + selectedDateCal.set(Calendar.SECOND, 0) + selectedDateCal.set(Calendar.MILLISECOND, 0) + + val startDateCal = Calendar.getInstance() + startDateCal.time = startDateValue + startDateCal.set(Calendar.HOUR_OF_DAY, 0) + startDateCal.set(Calendar.MINUTE, 0) + startDateCal.set(Calendar.SECOND, 0) + startDateCal.set(Calendar.MILLISECOND, 0) + + if (selectedDateCal.before(minEndDate) || selectedDateCal.time == startDateCal.time) { + field.errorMessage = "End date must be at least 1 day after start date (${sdf.format(minEndDate.time)})" + } + } + } + + if (field.errorMessage != null) { + notifyItemChanged(adapterPosition) + } + } + + when (field.fieldId) { + "start_date" -> { + if (formId == FormConstants.PULSE_POLIO_CAMPAIGN_FORM_ID || + formId == FormConstants.ORS_CAMPAIGN_FORM_ID) { + val endDateField = fields.find { it.fieldId == "end_date" } + if (endDateField != null) { + val startDateParsed = sdf.parse(dateStr) + if (startDateParsed != null) { + val minEndDate = Calendar.getInstance() + minEndDate.time = startDateParsed + minEndDate.add(Calendar.DAY_OF_MONTH, 1) + endDateField.validation?.minDate = sdf.format(minEndDate.time) + val endIdx = fields.indexOf(endDateField) + if (endIdx >= 0) notifyItemChanged(endIdx) + + val existingEndDate = getDate("end_date") + if (existingEndDate != null) { + val existingEndDateCal = Calendar.getInstance() + existingEndDateCal.time = existingEndDate + existingEndDateCal.set(Calendar.HOUR_OF_DAY, 0) + existingEndDateCal.set(Calendar.MINUTE, 0) + existingEndDateCal.set(Calendar.SECOND, 0) + existingEndDateCal.set(Calendar.MILLISECOND, 0) + + val startDateCal = Calendar.getInstance() + startDateCal.time = startDateParsed + startDateCal.set(Calendar.HOUR_OF_DAY, 0) + startDateCal.set(Calendar.MINUTE, 0) + startDateCal.set(Calendar.SECOND, 0) + startDateCal.set(Calendar.MILLISECOND, 0) + + if (existingEndDateCal.before(minEndDate) || existingEndDateCal.time == startDateCal.time) { + setError("end_date", "End date must be at least 1 day after start date") + } else { + clearError("end_date") + } + } + } + } + } + } + + "visit_date" -> { + val admission = fields.find { it.fieldId == "nrc_admission_date" } + admission?.validation?.minDate = dateStr + val admIdx = fields.indexOf(admission) + if (admIdx >= 0) notifyItemChanged(admIdx) + clearError("nrc_admission_date") + } + + "nrc_admission_date" -> { + val discharge = fields.find { it.fieldId == "nrc_discharge_date" } + discharge?.validation?.minDate = dateStr + val disIdx = fields.indexOf(discharge) + if (disIdx >= 0) notifyItemChanged(disIdx) + + val dischargeDate = getDate("nrc_discharge_date") + val admissionDate = sdf.parse(dateStr) + + if (dischargeDate != null && dischargeDate.before(admissionDate)) { + setError("nrc_discharge_date", "Discharge cannot be before admission") + } else { + clearError("nrc_discharge_date") + } + } + + "nrc_discharge_date" -> { + val followUp = fields.find { it.fieldId == "follow_up_visit_date" } + followUp?.validation?.minDate = dateStr + val fuIdx = fields.indexOf(followUp) + if (fuIdx >= 0) notifyItemChanged(fuIdx) + + val followDate = getDate("follow_up_visit_date") + val dischargeDate = sdf.parse(dateStr) + + if (followDate != null && followDate.before(dischargeDate)) { + setError("follow_up_visit_date", "Follow-up cannot be before discharge") + } else { + clearError("follow_up_visit_date") + } + } + } + } catch (e: Exception) { + Timber.tag("FormRendererAdapter").e(e, "Error in DatePicker callback for field: ${field.fieldId}") + } + }, + calendar.get(Calendar.YEAR), + calendar.get(Calendar.MONTH), + calendar.get(Calendar.DAY_OF_MONTH) + ).apply { + try { + datePicker.minDate = 0 + maxDate?.let { datePicker.maxDate = it.time } + if (minDate != null && maxDate != null && minDate.after(maxDate)) { + datePicker.minDate = maxDate.time + } else { + minDate?.let { datePicker.minDate = it.time } + } + } catch (e: Exception) { + Timber.tag("FormRendererAdapter").e(e, "Error setting date constraints for field: ${field.fieldId}") + } + + setOnDismissListener { + HelperUtil.setOriginalLocaleForDatePicker(activity,originalLocale) + } + + }.show() + } + } + + addWithError(textInputLayout, field) + } + + "radio" -> { + val context = itemView.context + + val radioGroup = RadioGroup(context).apply { + orientation = RadioGroup.HORIZONTAL + layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ).apply { setMargins(0, 8, 0, 8) } + } + + val isFieldDisabled = field.fieldId == "discharged_from_sncu" && + fields.find { it.fieldId == "is_baby_alive" }?.value == "Yes" && + isSNCU + + if (isFieldDisabled && field.value != "Yes") { + field.value = "Yes" + onValueChanged(field, "Yes") + notifyItemChanged(adapterPosition) + } + + field.options?.forEachIndexed { index, option -> + val radioButton = RadioButton(context).apply { + id = View.generateViewId() + text = option.label + isEnabled = !isViewOnly && !isFieldDisabled + layoutParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ).apply { setMargins(0, 0, if (index != field.options!!.lastIndex) 24 else 0, 0) } + } + radioGroup.addView(radioButton) + } + + // Set checked state AFTER all buttons are added to the group + field.options?.forEachIndexed { index, option -> + val rb = radioGroup.getChildAt(index) as RadioButton + if (field.value?.toString() == option.value) { + radioGroup.check(rb.id) + } + } + + if (!isViewOnly && !isFieldDisabled) { + radioGroup.setOnCheckedChangeListener { group, checkedId -> + // Clear focus from any previously focused EditText to prevent auto-scroll + itemView.rootView.findFocus()?.clearFocus() + val imm = itemView.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(itemView.windowToken, 0) + val checkedIndex = (0 until group.childCount).firstOrNull { (group.getChildAt(it) as RadioButton).id == checkedId } ?: return@setOnCheckedChangeListener + val selectedOption = field.options?.getOrNull(checkedIndex) ?: return@setOnCheckedChangeListener + if (field.value?.toString() != selectedOption.value) { + field.value = selectedOption.value + onValueChanged(field, selectedOption.value) + } + } + } + + val wrapper = LinearLayout(itemView.context).apply { + orientation = LinearLayout.VERTICAL + layoutParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ) + } + wrapper.addView(radioGroup) + + val errorTextView = TextView(itemView.context).apply { + tag = "field_error_tv" + setTextColor(Color.RED) + textSize = 12f + text = field.errorMessage ?: "" + visibility = if (field.errorMessage.isNullOrBlank()) View.GONE else View.VISIBLE + } + wrapper.addView(errorTextView) + + inputContainer.removeAllViews() + inputContainer.addView(wrapper) + } + + + "image" -> { + val context = itemView.context + val isCampaignPhotos = field.fieldId == "campaign_photos" || field.fieldId == "campaignPhotos" || field.fieldId == "mda_photos" + + val container = LinearLayout(context).apply { + orientation = LinearLayout.VERTICAL + } + + if (isCampaignPhotos) { + val imageList: List = when (val value = field.value) { + is List<*> -> value.filterIsInstance() + is Array<*> -> value.filterIsInstance().toList() + is String -> { + try { + val jsonArray = org.json.JSONArray(value) + (0 until jsonArray.length()).mapNotNull { jsonArray.optString(it).takeIf { it.isNotEmpty() } } + } catch (e: Exception) { + if (value.isNotEmpty()) listOf(value) else emptyList() + } + } + else -> emptyList() + } + val imagesContainer = LinearLayout(context).apply { + orientation = LinearLayout.HORIZONTAL + layoutParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ).apply { setMargins(0, 8, 0, 8) } + } + + imageList.takeLast(2).forEach { imagePath -> + val imageView = ImageView(context).apply { + layoutParams = LinearLayout.LayoutParams(300, 300).apply { + setMargins(0, 0, 8, 0) + } + scaleType = ImageView.ScaleType.CENTER_CROP + loadImageFromPath(context, imagePath, this) + } + imagesContainer.addView(imageView) + } + + container.addView(imagesContainer) + + if (!isViewOnly && field.isEditable) { + val pickButton = Button(context).apply { + text = context.getString(R.string.pick_image) + layoutParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ).apply { setMargins(0, 8, 0, 0) } + setOnClickListener { + onValueChanged(field, "pick_image") + } + } + container.addView(pickButton) + } + } else { + val imageView = ImageView(context).apply { + layoutParams = LinearLayout.LayoutParams(300, 300) + scaleType = ImageView.ScaleType.CENTER_CROP + + val filePath = field.value?.toString() + if (!filePath.isNullOrBlank()) { + loadImageFromPath(context, filePath, this) + } else { + setImageResource(R.drawable.ic_doc_upload) + } + } + + val pickButton = Button(context).apply { + text = context.getString(R.string.pick_image) + isEnabled = !isViewOnly && field.isEditable + setOnClickListener { + if (!isViewOnly && field.isEditable) { + onValueChanged(field, "pick_image") + } + } + } + + container.addView(imageView) + if (!isViewOnly && field.isEditable) container.addView(pickButton) + } + + addWithError(container, field) + } + else -> { + inputContainer.addView(TextView(itemView.context).apply { + text = field.value?.toString() ?: "" + textSize = 16f + }) + } + } + } + } + + init { + setHasStableIds(false) + } + + + } diff --git a/app/src/main/java/org/piramalswasthya/sakhi/adapters/dynamicAdapter/VisitCardAdapter.kt b/app/src/main/java/org/piramalswasthya/sakhi/adapters/dynamicAdapter/VisitCardAdapter.kt new file mode 100644 index 000000000..77f7faeb7 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/adapters/dynamicAdapter/VisitCardAdapter.kt @@ -0,0 +1,148 @@ +package org.piramalswasthya.sakhi.adapters.dynamicAdapter + +import android.text.Spannable +import android.text.SpannableString +import android.text.style.ForegroundColorSpan +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.RecyclerView +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.model.dynamicModel.VisitCard + +class VisitCardAdapter( + private var visits: List, + private var isBenDead: Boolean, + private val onVisitClick: (VisitCard) -> Unit +) : RecyclerView.Adapter() { + + inner class VisitViewHolder(view: View) : RecyclerView.ViewHolder(view) { + val tvVisitDay: TextView = view.findViewById(R.id.tvVisitDay) + val tvVisitDate: TextView = view.findViewById(R.id.tvVisitDate) + val tvVisitOption: TextView = view.findViewById(R.id.tvOptionalLabel) + val btnView: View = view.findViewById(R.id.btnView) + val btnAddVisit: View = view.findViewById(R.id.btnAddVisit) + init { + btnView.setOnClickListener { + val position = bindingAdapterPosition + if (position != RecyclerView.NO_POSITION) { + onVisitClick(visits[position]) + } + } + btnAddVisit.setOnClickListener { + val position = bindingAdapterPosition + if (position != RecyclerView.NO_POSITION) { + onVisitClick(visits[position]) + } + } + } + + } + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VisitViewHolder { + val view = + LayoutInflater.from(parent.context).inflate(R.layout.item_visit_card, parent, false) + return VisitViewHolder(view) + } + + fun updateVisits(newVisits: List) { + visits = newVisits + notifyDataSetChanged() + } + + fun updateDeathStatus(dead: Boolean) { + isBenDead = dead + notifyDataSetChanged() + } + override fun onBindViewHolder(holder: VisitViewHolder, position: Int) { + val visit = visits[position] + val ctx = holder.itemView.context + holder.tvVisitDay.text = when { + visit.visitDay.endsWith("Day") -> { + val englishDays = listOf("1st Day", "3rd Day", "7th Day", "14th Day", "21st Day", "28th Day", "42nd Day") + val localizedDays = ctx.resources.getStringArray(R.array.hbnc_visit_days) + val idx = englishDays.indexOf(visit.visitDay) + if (idx >= 0) localizedDays[idx] else visit.visitDay + } + visit.visitDay.endsWith("Months") -> { + val month = visit.visitDay.substringBefore(" ").toIntOrNull() + val localizedMonths = ctx.resources.getStringArray(R.array.hbyc_month_array) + val idx = if (month != null) month - 3 else -1 + if (idx >= 0 && idx < localizedMonths.size) localizedMonths[idx] else visit.visitDay + } + else -> visit.visitDay + } + holder.tvVisitDate.text = visit.visitDate + holder.btnView.visibility = View.GONE + holder.btnAddVisit.visibility = View.GONE + + + +// holder.tvVisitOption.visibility = if (visit.visitDay in listOf("14th Day", "21st Day", "28th Day")) { +// View.VISIBLE +// } else { +// View.GONE +// } + + + holder.tvVisitOption.apply { + + when (visit.visitDay) { + "14th Day", "21st Day", "28th Day" -> { + visibility = View.VISIBLE + text = context.getString(R.string.optional_) + setTextColor(context.getColor(R.color.read_only)) + } + "1st Day", "3rd Day", "7th Day", "42nd Day" -> { + visibility = View.VISIBLE + val mandatoryText = context.getString(R.string.mandatory).replace("*", "") + val spannable = SpannableString("$mandatoryText*") + val redColor = ContextCompat.getColor(context, R.color.Quartenary) + spannable.setSpan( + ForegroundColorSpan(redColor), + spannable.length - 1, + spannable.length, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + text = spannable + } + + } + } + + when { + visit.isCompleted -> { + holder.btnView.visibility = View.VISIBLE + holder.btnView.setBackgroundResource(R.color.Quartenary) + holder.itemView.setBackgroundResource(R.color.md_theme_dark_inversePrimary) + holder.itemView.isEnabled = true + holder.btnView.isEnabled = true + } + + visit.isEditable -> { + val enabled = !isBenDead + holder.itemView.isEnabled = enabled + holder.btnAddVisit.isEnabled = enabled + holder.btnView.isEnabled = enabled + + holder.btnAddVisit.visibility = if (enabled) View.VISIBLE else View.GONE + holder.btnAddVisit.setBackgroundResource(if (enabled) R.color.Quartenary else R.color.read_only) + holder.itemView.setBackgroundResource( + if (enabled) R.color.md_theme_dark_inversePrimary else R.color.read_only + ) + } + + + else -> { + holder.itemView.setBackgroundResource(R.color.read_only) + holder.itemView.isEnabled = false + holder.btnView.isEnabled = false + holder.tvVisitOption.visibility= View.GONE + holder.btnAddVisit.isEnabled = false + } + } + } + + override fun getItemCount(): Int = visits.size +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/AESJEFormDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/AESJEFormDataset.kt new file mode 100644 index 000000000..cd3be74a9 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/AESJEFormDataset.kt @@ -0,0 +1,328 @@ +package org.piramalswasthya.sakhi.configuration + +import android.content.Context +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.helpers.Languages +import org.piramalswasthya.sakhi.model.AESScreeningCache +import org.piramalswasthya.sakhi.model.BenRegCache +import org.piramalswasthya.sakhi.model.FormElement +import org.piramalswasthya.sakhi.model.InputType + +class AESJEFormDataset( + context: Context, currentLanguage: Languages +) : Dataset(context, currentLanguage) { + + private val dateOfCase = FormElement( + id = 1, + inputType = InputType.DATE_PICKER, + title = resources.getString(R.string.visit_date), + arrayId = -1, + required = true, + min = System.currentTimeMillis() - (90L * 24 * 60 * 60 * 1000), + max = System.currentTimeMillis(), + hasDependants = false + + ) + private val beneficiaryStatus = FormElement( + id = 2, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.beneficiary_status), + arrayId = R.array.benificary_case_status_kalaazar, + entries = resources.getStringArray(R.array.benificary_case_status_kalaazar), + required = true, + hasDependants = false + + ) + private val dateOfDeath = FormElement( + id = 3, + inputType = InputType.DATE_PICKER, + title = resources.getString(R.string.death_date), + arrayId = -1, + required = true, + min = System.currentTimeMillis() - (90L * 24 * 60 * 60 * 1000), + max = System.currentTimeMillis(), + hasDependants = true + + ) + + private val placeOfDeath = FormElement( + id = 4, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.place_of_Death), + arrayId = R.array.death_place, + entries = resources.getStringArray(R.array.death_place), + required = true, + hasDependants = true + + ) + + private var otherPlaceOfDeath = FormElement( + id = 5, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.other_place), + required = true, + hasDependants = false + ) + + private val reasonOfDeath = FormElement( + id = 6, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.reason_of_Death), + arrayId = R.array.reason_death, + entries = resources.getStringArray(R.array.reason_death), + required = true, + hasDependants = true + + ) + private var otherReasonOfDeath = FormElement( + id = 7, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.other_reason), + required = true, + hasDependants = false + ) + + + private val caseStatus = FormElement( + id = 8, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.aes_case_status_date), + arrayId = R.array.dc_case_status, + entries = resources.getStringArray(R.array.dc_case_status), + required = false, + hasDependants = true + + ) + + private var followUpPoint = FormElement( + id = 9, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.follow_up), + arrayId = R.array.follow_up_array, + entries = resources.getStringArray(R.array.follow_up_array), + required = false, + hasDependants = true + ) + + private var referredTo = FormElement( + id = 10, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.refer_to), + arrayId = R.array.dc_refer, + entries = resources.getStringArray(R.array.dc_refer), + required = false, + hasDependants = true + ) + private var other = FormElement( + id = 11, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.other), + required = true, + hasDependants = false + ) + + + + + + + + suspend fun setUpPage(ben: BenRegCache?, saved: AESScreeningCache?) { + val list = mutableListOf( + dateOfCase, + beneficiaryStatus, + + + ) + if (saved == null) { + dateOfCase.value = getDateFromLong(System.currentTimeMillis()) + beneficiaryStatus.value = resources.getStringArray(R.array.benificary_case_status_kalaazar)[0] + caseStatus.value = resources.getStringArray(R.array.dc_case_status)[0] + } else { + + dateOfCase.value = getDateFromLong(saved.visitDate) + + + if (saved.aesJeCaseStatus != null) { + caseStatus.value = + getLocalValueInArray(R.array.dc_case_status, saved.aesJeCaseStatus) + } + referredTo.value = + getLocalValueInArray(referredTo.arrayId, saved.referToName) + if (referredTo.value == referredTo.entries!!.last()) { + list.add(list.indexOf(referredTo) + 1, other) + } + beneficiaryStatus.value = + getLocalValueInArray(beneficiaryStatus.arrayId, saved.beneficiaryStatus) + other.value = saved.otherReferredFacility + if (beneficiaryStatus.value == beneficiaryStatus.entries!![beneficiaryStatus.entries!!.size - 2]) { + list.add(list.indexOf(beneficiaryStatus) + 1, dateOfDeath) + list.add(list.indexOf(beneficiaryStatus) + 2, placeOfDeath) + list.add(list.indexOf(beneficiaryStatus) + 3, reasonOfDeath) + dateOfDeath.value = + getDateFromLong(saved.dateOfDeath) + placeOfDeath.value = + getLocalValueInArray(placeOfDeath.arrayId, saved.placeOfDeath) + if (placeOfDeath.value == placeOfDeath.entries!!.last()) { + list.add(list.indexOf(placeOfDeath) + 1, otherPlaceOfDeath) + otherPlaceOfDeath.value = saved.otherPlaceOfDeath + } + + reasonOfDeath.value = + getLocalValueInArray(reasonOfDeath.arrayId, saved.reasonForDeath) + if (reasonOfDeath.value == reasonOfDeath.entries!!.last()) { + list.add(list.indexOf(reasonOfDeath) + 1, otherReasonOfDeath) + otherReasonOfDeath.value = saved.otherReasonForDeath + } + } else { + list.add(list.indexOf(beneficiaryStatus) + 1, caseStatus) + list.add(list.indexOf(beneficiaryStatus) + 2, followUpPoint) + list.add(list.indexOf(beneficiaryStatus) + 3, referredTo) + + + + + } + referredTo.value = + getLocalValueInArray(referredTo.arrayId, saved.referToName) + if (referredTo.value == referredTo.entries!!.last()) { + list.add(list.indexOf(referredTo) + 1, other) + } + + other.value = saved.otherReferredFacility + } + + + setUpPage(list) + + } + + override suspend fun handleListOnValueChanged(formId: Int, index: Int): Int { + return when (formId) { + beneficiaryStatus.id -> { + if (beneficiaryStatus.value == beneficiaryStatus.entries!![beneficiaryStatus.entries!!.size - 2] + ) { + triggerDependants( + source = beneficiaryStatus, + addItems = listOf(dateOfDeath, placeOfDeath, reasonOfDeath), + removeItems = listOf( + caseStatus,followUpPoint, referredTo, + ) + ) + } else { + triggerDependants( + source = beneficiaryStatus, + addItems = listOf( + caseStatus, referredTo + ), + removeItems = listOf(dateOfDeath, placeOfDeath, reasonOfDeath,otherPlaceOfDeath,otherReasonOfDeath) + ) + } + 0 + } + + referredTo.id -> { + if (referredTo.value == referredTo.entries!!.last()) { + triggerDependants( + source = referredTo, + addItems = listOf(other), + removeItems = listOf() + ) + } else { + triggerDependants( + source = referredTo, + addItems = listOf(), + removeItems = listOf(other) + ) + } + 0 + } + + placeOfDeath.id -> { + if (placeOfDeath.value == placeOfDeath.entries!!.last()) { + triggerDependants( + source = placeOfDeath, + addItems = listOf(otherPlaceOfDeath), + removeItems = listOf() + ) + } else { + triggerDependants( + source = placeOfDeath, + addItems = listOf(), + removeItems = listOf(otherPlaceOfDeath) + ) + } + 0 + } + otherPlaceOfDeath.id -> { + validateEmptyOnEditText(otherPlaceOfDeath) + } + other.id -> { + validateEmptyOnEditText(other) + } + + + + reasonOfDeath.id -> { + if (reasonOfDeath.value == reasonOfDeath.entries!!.last()) { + triggerDependants( + source = reasonOfDeath, + addItems = listOf(otherReasonOfDeath), + removeItems = listOf() + ) + } else { + triggerDependants( + source = reasonOfDeath, + addItems = listOf(), + removeItems = listOf(otherReasonOfDeath) + ) + } + 0 + } + + otherReasonOfDeath.id -> { + validateEmptyOnEditText(otherReasonOfDeath) + } + else -> { + -1 + } + } + } + + + override fun mapValues(cacheModel: FormDataModel, pageNumber: Int) { + (cacheModel as AESScreeningCache).let { form -> + form.visitDate = getLongFromDate(dateOfCase.value) + form.referToName = getEnglishValueInArray(referredTo.arrayId, referredTo.value) ?: referredTo.value + form.referredTo = referredTo.getPosition() + form.beneficiaryStatus = getEnglishValueInArray(beneficiaryStatus.arrayId, beneficiaryStatus.value) + form.beneficiaryStatusId = beneficiaryStatus.getPosition() + form.reasonForDeath = getEnglishValueInArray(reasonOfDeath.arrayId, reasonOfDeath.value) + form.aesJeCaseStatus = getEnglishValueInArray(R.array.dc_case_status, caseStatus.value) + form.otherPlaceOfDeath = otherPlaceOfDeath.value + form.otherReasonForDeath = otherReasonOfDeath.value + form.dateOfDeath = getLongFromDate(dateOfDeath.value) + form.placeOfDeath = getEnglishValueInArray(placeOfDeath.arrayId, placeOfDeath.value) + form.otherReferredFacility = other.value + form.diseaseTypeID = 3 + form.createdDate = getLongFromDate(dateOfCase.value) + form.followUpPoint = followUpPoint.value?.toIntOrNull() ?: 0 + + } + } + + + fun updateBen(benRegCache: BenRegCache) { + benRegCache.genDetails?.let { + it.reproductiveStatus = + englishResources.getStringArray(R.array.nbr_reproductive_status_array2)[1] + it.reproductiveStatusId = 2 + } + if (benRegCache.processed != "N") benRegCache.processed = "U" + } + + + fun getIndexOfDate(): Int { + return getIndexById(dateOfCase.id) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/AHDDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/AHDDataset.kt new file mode 100644 index 000000000..a85f004d4 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/AHDDataset.kt @@ -0,0 +1,174 @@ +package org.piramalswasthya.sakhi.configuration + +import android.content.Context +import android.net.Uri +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.helpers.Languages +import org.piramalswasthya.sakhi.model.AHDCache +import org.piramalswasthya.sakhi.model.FormElement +import org.piramalswasthya.sakhi.model.InputType +import org.piramalswasthya.sakhi.model.InputType.DATE_PICKER +import org.piramalswasthya.sakhi.model.InputType.DROPDOWN +import org.piramalswasthya.sakhi.model.InputType.IMAGE_VIEW +import org.piramalswasthya.sakhi.model.InputType.RADIO + +class AHDDataset( + var context: Context, + currentLanguage: Languages +) : Dataset(context, currentLanguage) { + private val formElementList = mutableListOf() + + private val mobilizedForAHD = FormElement( + id = 5, + inputType = RADIO, + title = resources.getString(R.string.mobilized_for_ahd), + entries = resources.getStringArray(R.array.yes_no_options), + required = true, + hasDependants = true + ) + + private val ahdPlace = FormElement( + id = 4, + inputType = DROPDOWN, + title = resources.getString(R.string.ahd_place), + entries = resources.getStringArray(R.array.ahd_place_options), + required = true, + isEnabled = true, + + ) + + private val ahdDate = FormElement( + id = 3, + inputType = DATE_PICKER, + title = resources.getString(R.string.ahd_date), + arrayId = -1, + required = true, + min = System.currentTimeMillis(), + max = System.currentTimeMillis(), + isEnabled = true + ) + + private val pic1 = FormElement( + id = 1, + inputType = InputType.FILE_UPLOAD, + title = resources.getString(R.string.nbr_image), + subtitle = resources.getString(R.string.nbr_image_sub), + arrayId = -1, + required = false + ) + + private val pic2 = FormElement( + id = 2, + inputType = InputType.FILE_UPLOAD, + title = resources.getString(R.string.nbr_image), + subtitle = resources.getString(R.string.nbr_image_sub), + arrayId = -1, + required = false + ) + + suspend fun setUpPage(ahd: AHDCache?) { + + if (pic1.value.isNullOrBlank()) { + pic1.value = "default" + } + + if (pic2.value.isNullOrBlank()) { + pic2.value = "default" + } + formElementList.clear() + val list = mutableListOf( + mobilizedForAHD, + pic1, + pic2 + ) + + ahd?.let { loadCachedData(it,list) } + formElementList.addAll(list) + setUpPage(list) + + } + fun getFormElementList(): List = formElementList + + private fun loadCachedData(ahd: AHDCache, list: MutableList) { + mobilizedForAHD.value = getLocalValueInArray(R.array.yes_no_options, ahd.mobilizedForAHD) + ahdPlace.value = getLocalValueInArray(R.array.ahd_place_options, ahd.ahdPlace) + ahdDate.value = ahd.ahdDate + pic1.value = ahd.image1 + pic2.value = ahd.image2 + if(mobilizedForAHD.value == mobilizedForAHD.entries!!.first()) { + list.add(mobilizedForAHD.getPosition() ,ahdPlace) + list.add(mobilizedForAHD.getPosition() + 1,ahdDate) + } + } + + override suspend fun handleListOnValueChanged(formId: Int, index: Int): Int { + + return when (formId) { + + mobilizedForAHD.id -> { + if (mobilizedForAHD.value == mobilizedForAHD.entries!!.first()) { + if (!formElementList.contains(ahdPlace)) { + formElementList.add(ahdPlace) + } + if (!formElementList.contains(ahdDate)) { + formElementList.add(ahdDate) + } + } + else{ + formElementList.remove(ahdPlace) + formElementList.remove(ahdDate) + } + triggerDependants( + source = mobilizedForAHD, + passedIndex = index, + triggerIndex = 0, + target = listOf(ahdPlace,ahdDate), + ) + + + } + + ahdPlace.id -> validateEmptyOnSpinner(ahdPlace) + else -> -1 + } + + } + + override fun mapValues(cacheModel: FormDataModel, pageNumber: Int) { + (cacheModel as AHDCache).let { form -> + + form.mobilizedForAHD = getEnglishValueInArray(R.array.yes_no_options, mobilizedForAHD.value) ?: mobilizedForAHD.value!! + form.ahdPlace = getEnglishValueInArray(R.array.ahd_place_options, ahdPlace.value) + form.ahdDate = ahdDate.value + form.image1 = pic1.value + form.image2 = pic2.value + + } + } + + fun setImageUriToFormElement(lastImageFormId: Int, dpUri: Uri) { + + when (lastImageFormId) { + pic1.id -> { + pic1.value = dpUri.toString() + pic1.errorText = null + } + pic2.id -> { + pic2.value = dpUri.toString() + pic2.errorText = null + } + } + + + } + + private fun validateEmptyOnSpinner(formElement: FormElement): Int { + if (formElement.value.isNullOrEmpty()) { + formElement.errorText = resources.getString(R.string.please_select_value) + return formElement.id + } + formElement.errorText = null + return -1 + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/AdolescentHealthFormDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/AdolescentHealthFormDataset.kt new file mode 100644 index 000000000..782a90ec7 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/AdolescentHealthFormDataset.kt @@ -0,0 +1,314 @@ +package org.piramalswasthya.sakhi.configuration + +import android.content.Context +import android.net.Uri +import android.text.InputType +import androidx.room.Entity +import androidx.room.PrimaryKey +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.helpers.Languages +import org.piramalswasthya.sakhi.model.AdolescentHealthCache +import org.piramalswasthya.sakhi.model.BenRegCache +import org.piramalswasthya.sakhi.model.FormElement +import org.piramalswasthya.sakhi.model.InputType.DATE_PICKER +import org.piramalswasthya.sakhi.model.InputType.DROPDOWN +import org.piramalswasthya.sakhi.model.InputType.EDIT_TEXT +import org.piramalswasthya.sakhi.model.InputType.RADIO +import org.piramalswasthya.sakhi.model.TBScreeningCache +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Locale + +class AdolescentHealthFormDataset(context: Context, language: Languages) : Dataset(context, language) { + + companion object { + private fun getCurrentDateString(): String { + val calendar = Calendar.getInstance() + val mdFormat = SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH) + return mdFormat.format(calendar.time) + } + + private fun getCurrentDateMillis(): Long { + return Calendar.getInstance().timeInMillis + } + } + + + private val visitDate = FormElement( + id = 3, + inputType = DATE_PICKER, + title = resources.getString(R.string.visit_date), + arrayId = -1, + required = true, + max = getCurrentDateMillis() + ) + + private val healthStatus = FormElement( + id = 4, + inputType = DROPDOWN, + title = resources.getString(R.string.ahd_health_status), + arrayId = R.array.ahd_health_status_array, + entries = resources.getStringArray(R.array.ahd_health_status_array), + required = true, + hasDependants = true + ) + + private val ifaTabletDistribution = FormElement( + id = 5, + inputType = RADIO, + title = resources.getString(R.string.ahd_ifa_tablet_distribution), + arrayId = R.array.yes_no, + entries = resources.getStringArray(R.array.yes_no), + required = true, + hasDependants = true + ) + + private val ifaTabletQuantity = FormElement( + id = 6, + inputType = EDIT_TEXT, + title = resources.getString(R.string.ahd_ifa_tablet_quantity), + arrayId = -1, + required = true, + etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, + min = 1L, + max = 100L + ) + + private val menstrualHygieneAwareness = FormElement( + id = 7, + inputType = RADIO, + title = resources.getString(R.string.ahd_menstrual_hygiene_awareness), + arrayId = R.array.yes_no, + entries = resources.getStringArray(R.array.yes_no), + required = false + ) + + private val sanitaryNapkinDistributed = FormElement( + id = 8, + inputType = RADIO, + title = resources.getString(R.string.ahd_sanitary_napkin_distributed), + arrayId = R.array.yes_no, + entries = resources.getStringArray(R.array.yes_no), + required = true, + hasDependants = true + ) + + private val noOfPacketsDistributed = FormElement( + id = 9, + inputType = EDIT_TEXT, + title = resources.getString(R.string.ahd_no_of_packets_distributed), + arrayId = -1, + required = true, + min = 1L, + etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, + max = 20L + ) + + private val place = FormElement( + id = 10, + inputType = DROPDOWN, + title = resources.getString(R.string.place), + arrayId = R.array.ahd_place_array, + entries = resources.getStringArray(R.array.ahd_place_array), + required = true + ) + + + private val referredToHealthFacility = FormElement( + id = 12, + inputType = EDIT_TEXT, + title = resources.getString(R.string.ahd_referred_to_health_facility), + arrayId = -1, + required = false, + ) + + private val counselingProvided = FormElement( + id = 13, + inputType = RADIO, + title = resources.getString(R.string.ahd_counseling_provided), + arrayId = R.array.yes_no, + entries = resources.getStringArray(R.array.yes_no), + required = true + ) + + private val counselingType = FormElement( + id = 14, + inputType = DROPDOWN, + title = resources.getString(R.string.ahd_counseling_type), + arrayId = R.array.ahd_counseling_type_array, + entries = resources.getStringArray(R.array.ahd_counseling_type_array), + required = false + ) + + private val followUpDate = FormElement( + id = 15, + inputType = DATE_PICKER, + title = resources.getString(R.string.ahd_follow_up_date), + arrayId = -1, + required = false, + max = getCurrentDateMillis() + ) + + private val referralStatus = FormElement( + id = 16, + inputType = DROPDOWN, + title = resources.getString(R.string.ahd_referral_status), + arrayId = R.array.ahd_referral_status_array, + entries = resources.getStringArray(R.array.ahd_referral_status_array), + required = false + ) + + private val sDate = FormElement( + id = 15, + inputType = DATE_PICKER, + title = resources.getString(R.string.ahd_distribution_date), + arrayId = -1, + required = false, + max = getCurrentDateMillis() + ) + + private val firstPage: List by lazy { + listOf( + visitDate, + healthStatus, + ifaTabletDistribution, + menstrualHygieneAwareness, + sanitaryNapkinDistributed, + counselingProvided, + counselingType, + followUpDate, + referralStatus + ) + } + + suspend fun setFirstPage(ben: BenRegCache?, saved: AdolescentHealthCache?) { + val list = firstPage.toMutableList() + if (visitDate.value == null) { + visitDate.value = getCurrentDateString() + } + saved?.let { saved -> + visitDate.value = saved.visitDate?.let { getDateFromLong(it) } + healthStatus.value = getLocalValueInArray(R.array.ahd_health_status_array, saved.healthStatus) + ifaTabletDistribution.value = if (saved.ifaTabletDistributed == true) ifaTabletDistribution.entries!![0] else ifaTabletDistribution.entries!![1] + ifaTabletQuantity.value = saved.quantityOfIfaTablets?.toString() + menstrualHygieneAwareness.value = if (saved.menstrualHygieneAwarenessGiven == true) menstrualHygieneAwareness.entries!![0] else menstrualHygieneAwareness.entries!![1] + sanitaryNapkinDistributed.value = if (saved.sanitaryNapkinDistributed == true) sanitaryNapkinDistributed.entries!![0] else sanitaryNapkinDistributed.entries!![1] + noOfPacketsDistributed.value = saved.noOfPacketsDistributed?.toString() + place.value = getLocalValueInArray(R.array.ahd_place_array, saved.place) + sDate.value = saved.distributionDate?.let { getDateFromLong(it) } + referredToHealthFacility.value = saved.referredToHealthFacility + counselingProvided.value = if (saved.counselingProvided == true) counselingProvided.entries!![0] else counselingProvided.entries!![1] + counselingType.value = getLocalValueInArray(R.array.ahd_counseling_type_array, saved.counselingType) + followUpDate.value = saved.followUpDate?.let { getDateFromLong(it) } + referralStatus.value = getLocalValueInArray(R.array.ahd_referral_status_array, saved.referralStatus) + } + + if (ifaTabletDistribution.value == ifaTabletDistribution.entries!![0]) { + list.add(list.indexOf(ifaTabletDistribution) + 1, ifaTabletQuantity) + + } + + if (sanitaryNapkinDistributed.value == sanitaryNapkinDistributed.entries!![0]) { + list.add(list.indexOf(sanitaryNapkinDistributed) + 1, noOfPacketsDistributed) + list.add(list.indexOf(noOfPacketsDistributed) + 1, place) + list.add(list.indexOf(place) + 1, sDate) + } + + if (healthStatus.value == healthStatus.entries!![1] || healthStatus.value == healthStatus.entries!![2]) { + referredToHealthFacility.required = true + list.add(list.indexOf(healthStatus) + 1, referredToHealthFacility) + } + + setUpPage(list) + } + + override suspend fun handleListOnValueChanged(formId: Int, index: Int): Int { + return when (formId) { + + visitDate.id -> { + validateEmptyOnEditText(visitDate) + -1 + } + healthStatus.id -> { + referredToHealthFacility.required = (index == 1 || index == 2) + triggerDependants( + source = healthStatus, + addItems = if (index == 1 || index == 2) listOf(referredToHealthFacility) else emptyList(), + removeItems = listOf(referredToHealthFacility) + ) + } + ifaTabletDistribution.id -> { + triggerDependants( + source = ifaTabletDistribution, + passedIndex = index, + triggerIndex = 0, + target = ifaTabletQuantity + ) + } + ifaTabletQuantity.id -> { + validateEmptyOnEditText(ifaTabletQuantity) + validateIntMinMax(ifaTabletQuantity) + } + + sanitaryNapkinDistributed.id -> { + triggerDependants( + source = sanitaryNapkinDistributed, + triggerIndex = 0, + passedIndex = index, + target = listOf(noOfPacketsDistributed,place,sDate) + + ) + } + menstrualHygieneAwareness.id -> -1 + + noOfPacketsDistributed.id -> { + validateEmptyOnEditText(noOfPacketsDistributed) + validateIntMinMax(noOfPacketsDistributed) + } + + referredToHealthFacility.id -> { + validateEmptyOnEditText(referredToHealthFacility) + } + + + + counselingProvided.id ->-1 + place.id -> { + validateEmptyOnEditText(place) + -1 + } + counselingType.id -> -1 + followUpDate.id -> { + validateEmptyOnEditText(visitDate) + -1 + } + + referralStatus.id -> -1 + else -> -1 + } + } + + override fun mapValues(cacheModel: FormDataModel, pageNumber: Int) { + (cacheModel as AdolescentHealthCache).let { ben -> + //ben.benId = ben.benId + ben.visitDate = getLongFromDate(visitDate.value) + ben.healthStatus = healthStatus.getEnglishStringFromPosition(healthStatus.getPosition()) + ben.ifaTabletDistributed = ifaTabletDistribution.value == resources.getStringArray( + R.array.yes_no)[0] + ben.quantityOfIfaTablets = ifaTabletQuantity.value?.toInt() + ben.menstrualHygieneAwarenessGiven = menstrualHygieneAwareness.value == menstrualHygieneAwareness.entries!![0] + ben.sanitaryNapkinDistributed = sanitaryNapkinDistributed.value == sanitaryNapkinDistributed.entries!![0] + ben.noOfPacketsDistributed = noOfPacketsDistributed.value?.toInt() + ben.place = place.getEnglishStringFromPosition(place.getPosition()) + ben.distributionDate = getLongFromDate(sDate.value) + ben.referredToHealthFacility = referredToHealthFacility.value + ben.counselingProvided = counselingProvided.value == counselingProvided.entries!![0] + ben.counselingType = counselingType.getEnglishStringFromPosition(counselingType.getPosition()) + ben.followUpDate = getLongFromDate(followUpDate.value) + ben.referralStatus = referralStatus.getEnglishStringFromPosition(referralStatus.getPosition()) + } + } + + fun setImageUriToFormElement(lastImageFormId: Int, dpUri: Uri) { + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/AshaProfileDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/AshaProfileDataset.kt new file mode 100644 index 000000000..f09de618a --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/AshaProfileDataset.kt @@ -0,0 +1,498 @@ +package org.piramalswasthya.sakhi.configuration + +import android.content.Context +import android.net.Uri +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.helpers.ImageUtils +import org.piramalswasthya.sakhi.helpers.Konstants +import org.piramalswasthya.sakhi.helpers.Languages +import org.piramalswasthya.sakhi.model.BenBasicCache +import org.piramalswasthya.sakhi.model.FormElement +import org.piramalswasthya.sakhi.model.InputType +import org.piramalswasthya.sakhi.model.ProfileActivityCache +import org.piramalswasthya.sakhi.model.User +import org.piramalswasthya.sakhi.repositories.AshaProfileRepo +import org.piramalswasthya.sakhi.utils.StringMappingUtil + +class AshaProfileDataset( + context: Context, currentLanguage: Languages,var ashaProfileRepo: AshaProfileRepo +) : Dataset(context, currentLanguage) { + + private val pic = FormElement( + id = 1, + inputType = InputType.IMAGE_VIEW, + title = resources.getString(R.string.nbr_image), + subtitle = resources.getString(R.string.nbr_image_sub), + arrayId = -1, + required = false + ) + + private var ashaName = FormElement( + id = 1, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.ashaname), + required = true, + allCaps = true, + etInputType = android.text.InputType.TYPE_CLASS_TEXT or android.text.InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS + + ) + + private val village = FormElement( + id = 2, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.village), + required = false, + ) + + private val loginuserName = FormElement( + id = 2, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.loginusername), + required = false, + ) + + private val userId = FormElement( + id = 3, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.asha_empid), + required = false, + etMaxLength = 12 + + ) + + private val dob = FormElement( + id = 4, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.date_of_birth), + required = false, + max = BenGenRegFormDataset.getMaxDobMillis(), + min = BenGenRegFormDataset.getMinDobMillis(), + ) + + private val ages = FormElement( + id = 5, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.age), + required = false, + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + etMaxLength = 2, + max = Konstants.maxAgeForGenBen.toLong(), + min = Konstants.minAgeForGenBen.toLong(), + ) + + private val mobileNumber = FormElement( + id = 5, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.mobile_number), + required = true, + isMobileNumber = true, + etMaxLength = 10, + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + + + ) + + private val alternameMobileNumber = FormElement( + id = 6, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.alternate_mobile_no), + required = false, + etMaxLength = 10, + isMobileNumber = true, + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + + ) + + private val fatherOrspouse = FormElement( + id = 7, + inputType = InputType.RADIO, + title = resources.getString(R.string.father_or_spouse), + required = false, + arrayId = R.array.fathers_spouse, + value = resources.getStringArray(R.array.fathers_spouse)[0], + entries = resources.getStringArray(R.array.fathers_spouse), + hasDependants = true, + ) + + private val spouseOrFatherNameEdt = FormElement( + id = 8, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.father_or_spouse), + required = false, + arrayId = R.array.fathers_spouse, + hasDependants = true, + allCaps = true, + etInputType = android.text.InputType.TYPE_CLASS_TEXT or android.text.InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS + ) + + private val dateOfJoining = FormElement( + id = 9, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.date_of_joining), + required = false, + + ) + + + private val bankAccount = FormElement( + id = 10, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.bank_acc), + arrayId = -1, + required = false, + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + isMobileNumber = true, + etMaxLength = 18, + max = 999999999999999999L, + min = 100000000L, + showDrawable = false + ) + private val Ifsc = FormElement( + id = 11, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.ifsc_code), + required = false, + etMaxLength = 11 + ) + private val populationCovered = FormElement( + id = 12, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.population_covered_under_asha), + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + required = false, + etMaxLength = 4, + ) + private val ashaSupervisorName = FormElement( + id = 13, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.asha_supervisor_name), + required = false, + allCaps = true, + etInputType = android.text.InputType.TYPE_CLASS_TEXT or android.text.InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS + + ) + private val ashaSupervisorContactNumber = FormElement( + id = 14, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.asha_supervisor_contact_no), + required = false, + etMaxLength = 10, + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + + ) + private val ChoName = FormElement( + id = 15, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.cho_name), + required = false, + allCaps = true, + + etInputType = android.text.InputType.TYPE_CLASS_TEXT or android.text.InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS + ) + private val ChoMobileNo = FormElement( + id = 16, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.mobile_no_of_cho), + required = false, + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + etMaxLength = 10, + ) + private val nameOfAWW = FormElement( + id = 17, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.name_of_aww), + required = false, + allCaps = true, + etInputType = android.text.InputType.TYPE_CLASS_TEXT or android.text.InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS + ) + private val mobieNoOfAWW = FormElement( + id = 18, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.mobile_no_aww), + required = false, + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + etMaxLength = 10, + ) + private val nameOfANM1 = FormElement( + id = 19, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.name_of_anm1), + required = false, + allCaps = true, + etInputType = android.text.InputType.TYPE_CLASS_TEXT or android.text.InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS + ) + private val mobileNoOfANM1 = FormElement( + id = 20, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.mobile_number_of_anm1), + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + required = false, + etMaxLength = 10, + ) + private val nameOfANM2 = FormElement( + id = 21, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.name_of_anm2), + required = false, + allCaps = true, + + etInputType = android.text.InputType.TYPE_CLASS_TEXT or android.text.InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS + ) + private val mobileNoOfANM2 = FormElement( + id = 22, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.mobile_number_of_anm2), + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + required = false, + etMaxLength = 10, + ) + private val abhaNumber = FormElement( + id = 23, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.abha_number), + required = false, + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + etMaxLength = 14, + min = 10000000000000L, + max = 99999999999999L + + + ) +// private val ashaHouseholdRegistrationNo = FormElement( +// id = 24, +// inputType = InputType.TEXT_VIEW, +// title = "Asha Household registration", +// required = false, +// ) +// private val ashaFamilymember = FormElement( +// id = 25, +// inputType = InputType.EDIT_TEXT, +// title = "Asha Family member", +// required = false, +// ) + + + suspend fun setUpPage( + currentUser: User, + ashaProfile: ProfileActivityCache?, + ) { + val list = mutableListOf( + pic, + ashaName, + village, + loginuserName, + userId, + dob, + ages, + mobileNumber, + alternameMobileNumber, + fatherOrspouse, + spouseOrFatherNameEdt, + dateOfJoining, + bankAccount, + Ifsc, + populationCovered, + ashaSupervisorName, + ashaSupervisorContactNumber, + ChoName, + ChoMobileNo, + nameOfAWW, + mobieNoOfAWW, + nameOfANM1, + mobileNoOfANM1, + nameOfANM2, + mobileNoOfANM2, + abhaNumber +// ashaHouseholdRegistrationNo, +// ashaFamilymember + + + ) + + ashaName.value = ashaProfile?.name + pic.value = ashaProfile?.profileImage + village.value = currentUser.villages[0].name + loginuserName.value = currentUser.userName + userId.value = ashaProfile?.employeeId.toString() + mobileNumber.value = ashaProfile?.mobileNumber.toString() + alternameMobileNumber.value = ashaProfile?.alternateMobileNumber.toString() + dateOfJoining.value = ashaProfile?.dateOfJoining.toString() + bankAccount.value = ashaProfile?.bankAccount.toString() + Ifsc.value = ashaProfile?.ifsc.toString() + dob.value =dateFormate(ashaProfile?.dob.toString()) + ages.value = ashaProfile?.dob?.let { BenBasicCache.getAgeFromDob(getLongFromDate(dateFormate(ashaProfile?.dob.toString()))).toString() } + populationCovered.value = ashaProfile?.populationCovered.toString() + spouseOrFatherNameEdt.value = ashaProfile?.fatherOrSpouseName.toString() + fatherOrspouse.value = fatherOrspouse.getStringSpauseFromPosition(if(ashaProfile?.isFatherOrSpouse==true) 1 else 0) + ashaSupervisorName.value = ashaProfile?.supervisorName.toString() + ashaSupervisorContactNumber.value = ashaProfile?.supervisorMobile.toString() + ChoName.value = ashaProfile?.choName.toString() + ChoMobileNo.value = ashaProfile?.choMobile.toString() + nameOfAWW.value = ashaProfile?.awwName.toString() + mobieNoOfAWW.value = ashaProfile?.awwMobile.toString() + nameOfANM1.value = ashaProfile?.anm1Name.toString() + mobileNoOfANM1.value = ashaProfile?.anm1Mobile.toString() + nameOfANM2.value = ashaProfile?.anm2Name.toString() + mobileNoOfANM2.value = ashaProfile?.anm2Mobile.toString() + abhaNumber.value = ashaProfile?.abhaNumber.toString() +// ashaHouseholdRegistrationNo.value = ashaProfile?.ashaHouseholdRegistration.toString() +// ashaFamilymember.value = ashaProfile?.ashaFamilyMember.toString() + setUpPage(list) + + } + + + + override suspend fun handleListOnValueChanged(formId: Int, index: Int): Int { + return when (formId) { + ashaName.id -> { + validateEmptyOnEditText(ashaName) + validateAllCapsOrSpaceOnEditText(ashaName) + } + + spouseOrFatherNameEdt.id -> { + validateEmptyOnEditText(spouseOrFatherNameEdt) + validateAllCapsOrSpaceOnEditText(spouseOrFatherNameEdt) + } + ashaSupervisorName.id -> { + validateEmptyOnEditText(ashaSupervisorName) + validateAllCapsOrSpaceOnEditText(ashaSupervisorName) + } + + nameOfAWW.id -> { + validateEmptyOnEditText(nameOfAWW) + validateAllCapsOrSpaceOnEditText(nameOfAWW) + } + + nameOfANM1.id -> { + validateEmptyOnEditText(nameOfANM1) + validateAllCapsOrSpaceOnEditText(nameOfANM1) + } + + nameOfANM2.id -> { + validateEmptyOnEditText(nameOfANM2) + validateAllCapsOrSpaceOnEditText(nameOfANM2) + } + + ChoName.id -> { + validateEmptyOnEditText(ChoName) + validateAllCapsOrSpaceOnEditText(ChoName) + } + + mobileNumber.id -> { + validateEmptyOnEditText(mobileNumber) + validateMobileNumberOnEditText(mobileNumber) + } + alternameMobileNumber.id -> { + validateEmptyOnEditText(alternameMobileNumber) + validateMobileNumberOnEditText(alternameMobileNumber) + } + bankAccount.id -> { + validateIntMinMax(bankAccount) + } + ashaSupervisorContactNumber.id -> { + validateEmptyOnEditText(ashaSupervisorContactNumber) + validateMobileNumberOnEditText(ashaSupervisorContactNumber) + } + mobileNoOfANM1.id -> { + validateEmptyOnEditText(mobileNoOfANM1) + validateMobileNumberOnEditText(mobileNoOfANM1) + } + mobileNoOfANM2.id -> { + validateEmptyOnEditText(mobileNoOfANM2) + validateMobileNumberOnEditText(mobileNoOfANM2) + } + mobieNoOfAWW.id -> { + validateEmptyOnEditText(mobieNoOfAWW) + validateMobileNumberOnEditText(mobieNoOfAWW) + } + ChoMobileNo.id -> { + validateEmptyOnEditText(ChoMobileNo) + validateMobileNumberOnEditText(ChoMobileNo) + } + Ifsc.id -> { + validateIFSCEditText(Ifsc) + } + + abhaNumber.id -> { + validateABHANumberEditText(abhaNumber) + + } + dob.id -> { + assignValuesToAgeFromDob(getLongFromDate(dob.value), ages) + ages.value?.takeIf { it.isNotEmpty() }?.toLong()?.let { } + -1 + } + + else -> 1 + } + } + + suspend fun mapProfileValues(cacheModel: ProfileActivityCache,context: Context){ + (cacheModel).let { dataModel -> + dataModel.name = ashaName.value + val imageUri = pic.value?.toString() ?: "" + val alreadyPersisted = imageUri.isNotEmpty() && + imageUri.contains(context.filesDir.absolutePath) + if (imageUri.isNotEmpty() && !alreadyPersisted) { + val persistedUri = ImageUtils.saveBenImageFromCameraToStorage( + context = context, + uriString = imageUri, + benId = dataModel.employeeId.toLong() + ) + dataModel.profileImage = persistedUri ?: imageUri + pic.value = dataModel.profileImage + } else { + dataModel.profileImage = imageUri + } + dataModel.village = village.value.toString() + dataModel.dob = dateReverseFormat(dob.value.toString()).toString() + dataModel.age = ages.value!!.toInt() + dataModel.mobileNumber = mobileNumber.value.toString() + dataModel.alternateMobileNumber = StringMappingUtil.convertDigits(alternameMobileNumber.value.toString()) + dataModel.fatherOrSpouseName = spouseOrFatherNameEdt.value.toString() + dataModel.supervisorName = ashaSupervisorName.value.toString() + dataModel.supervisorMobile = StringMappingUtil.convertDigits(ashaSupervisorContactNumber.value.toString()) + dataModel.dateOfJoining = dateOfJoining.value.toString() + dataModel.bankAccount = StringMappingUtil.convertDigits(bankAccount.value.toString()) + dataModel.ifsc = StringMappingUtil.convertDigits(Ifsc.value.toString()) + dataModel.populationCovered = StringMappingUtil.convertDigits(populationCovered.value?.takeIf { it.isNotEmpty() })?.toIntOrNull() ?: 0 + dataModel.choName = ChoName.value.toString() + dataModel.choMobile = StringMappingUtil.convertDigits(ChoMobileNo.value.toString()) + dataModel.awwName = nameOfAWW.value.toString() + dataModel.awwMobile = StringMappingUtil.convertDigits(mobieNoOfAWW.value.toString()) + dataModel.anm1Name = nameOfANM1.value.toString() + dataModel.anm1Mobile = StringMappingUtil.convertDigits(mobileNoOfANM1.value.toString()) + dataModel.anm2Name = nameOfANM2.value.toString() + dataModel.anm2Mobile = StringMappingUtil.convertDigits(mobileNoOfANM2.value.toString()) + dataModel.abhaNumber = StringMappingUtil.convertDigits(abhaNumber.value.toString()) +// dataModel.ashaHouseholdRegistration = ashaHouseholdRegistrationNo.value.toString() +// dataModel.ashaFamilyMember = ashaFamilymember.value.toString() + dataModel.isFatherOrSpouse = when (fatherOrspouse.value) { + fatherOrspouse.entries!![0] -> true + fatherOrspouse.entries!![1] -> false + else -> false + } + + + } + ashaProfileRepo.postDataToAmritServer(cacheModel) + } + + + override fun mapValues(cacheModel: FormDataModel, pageNumber: Int) { + + } + + fun setImageUriToFormElement(lastImageFormId: Int, dpUri: Uri) { + when (lastImageFormId) { + pic.id -> { + pic.value = dpUri.toString() + pic.errorText = null + } + } + + } + + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/BenGenRegFormDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/BenGenRegFormDataset.kt index f8d4cc351..2379cbe4f 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/configuration/BenGenRegFormDataset.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/BenGenRegFormDataset.kt @@ -34,13 +34,13 @@ class BenGenRegFormDataset(context: Context, language: Languages) : Dataset(cont } - private fun getMinDobMillis(): Long { + fun getMinDobMillis(): Long { val cal = Calendar.getInstance() cal.add(Calendar.YEAR, -1 * maxAgeForGenBen) return cal.timeInMillis } - private fun getMaxDobMillis(): Long { + fun getMaxDobMillis(): Long { val cal = Calendar.getInstance() cal.add(Calendar.YEAR, -1 * minAgeForGenBen) return cal.timeInMillis @@ -140,7 +140,6 @@ class BenGenRegFormDataset(context: Context, language: Languages) : Dataset(cont required = true, allCaps = true, hasSpeechToText = true, - etInputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS ) private val wifeName = FormElement( @@ -191,7 +190,7 @@ class BenGenRegFormDataset(context: Context, language: Languages) : Dataset(cont inputType = EDIT_TEXT, title = resources.getString(R.string.father_s_name), arrayId = -1, - required = true, + required = false, allCaps = true, hasSpeechToText = true, @@ -202,7 +201,7 @@ class BenGenRegFormDataset(context: Context, language: Languages) : Dataset(cont inputType = EDIT_TEXT, title = resources.getString(R.string.mother_s_name), arrayId = -1, - required = true, + required = false, allCaps = true, hasSpeechToText = true, @@ -362,8 +361,8 @@ class BenGenRegFormDataset(context: Context, language: Languages) : Dataset(cont id = 28, inputType = DROPDOWN, title = resources.getString(R.string.reproductive_status), - arrayId = R.array.nbr_reproductive_status_array, - entries = resources.getStringArray(R.array.nbr_reproductive_status_array), + arrayId = R.array.nbr_reproductive_status_array2, + entries = resources.getStringArray(R.array.nbr_reproductive_status_array2), required = true, hasDependants = true ) @@ -517,11 +516,14 @@ class BenGenRegFormDataset(context: Context, language: Languages) : Dataset(cont return when (formId) { firstName.id -> { validateEmptyOnEditText(firstName) - validateAllCapsOrSpaceOnEditText(firstName) + validateAllCapsOrSpaceOnEditTextWithHindiEnabled(firstName) + // validateAllCapsOrSpaceOnEditText(firstName) + } lastName.id -> { - validateAllCapsOrSpaceOnEditText(lastName) + // validateAllCapsOrSpaceOnEditText(lastName) + validateAllCapsOrSpaceOnEditTextWithHindiEnabled(lastName) } dob.id -> { @@ -597,8 +599,8 @@ class BenGenRegFormDataset(context: Context, language: Languages) : Dataset(cont } else { when (maritalStatus.value) { maritalStatus.entries!![0] -> { - fatherName.required = true - motherName.required = true + fatherName.required = false + motherName.required = false return triggerDependants( source = maritalStatus, addItems = emptyList(),//listOf(fatherName, motherName), @@ -612,13 +614,8 @@ class BenGenRegFormDataset(context: Context, language: Languages) : Dataset(cont } maritalStatus.entries!![1] -> { - if (gender.value == gender.entries!![1]) { - fatherName.required = false - motherName.required = false - } else { - fatherName.required = true - motherName.required = true - } + fatherName.required = false + motherName.required = false husbandName.required = true wifeName.required = true return triggerDependants( @@ -638,8 +635,8 @@ class BenGenRegFormDataset(context: Context, language: Languages) : Dataset(cont else -> { husbandName.required = maritalStatus.value != maritalStatus.entries!![2] wifeName.required = maritalStatus.value != maritalStatus.entries!![2] - fatherName.required = true - motherName.required = true + fatherName.required = false + motherName.required = false // ().let { //// wifeName.required = it //// husbandName.required = it @@ -667,6 +664,7 @@ class BenGenRegFormDataset(context: Context, language: Languages) : Dataset(cont ageAtMarriage.id -> age.value?.takeIf { it.isNotEmpty() && it.isDigitsOnly() && !ageAtMarriage.value.isNullOrEmpty() } ?.toInt()?.let { validateEmptyOnEditText(ageAtMarriage) + ageAtMarriage.max = it.toLong() validateIntMinMax(ageAtMarriage) if (it == ageAtMarriage.value?.toInt()) { val cal = Calendar.getInstance() @@ -685,27 +683,32 @@ class BenGenRegFormDataset(context: Context, language: Languages) : Dataset(cont fatherName.id -> { validateEmptyOnEditText(fatherName) - validateAllCapsOrSpaceOnEditText(fatherName) + validateAllCapsOrSpaceOnEditTextWithHindiEnabled(fatherName) + // validateAllCapsOrSpaceOnEditText(fatherName) } motherName.id -> { validateEmptyOnEditText(motherName) - validateAllCapsOrSpaceOnEditText(motherName) + validateAllCapsOrSpaceOnEditTextWithHindiEnabled(motherName) + // validateAllCapsOrSpaceOnEditText(motherName) } husbandName.id -> { validateEmptyOnEditText(husbandName) - validateAllCapsOrSpaceOnEditText(husbandName) + validateAllCapsOrSpaceOnEditTextWithHindiEnabled(husbandName) + // validateAllCapsOrSpaceOnEditText(husbandName) } wifeName.id -> { validateEmptyOnEditText(wifeName) - validateAllCapsOrSpaceOnEditText(wifeName) + validateAllCapsOrSpaceOnEditTextWithHindiEnabled(wifeName) + // validateAllCapsOrSpaceOnEditText(wifeName) } spouseName.id -> { validateEmptyOnEditText(spouseName) - validateAllCapsOrSpaceOnEditText(spouseName) + //validateAllCapsOrSpaceOnEditText(spouseName) + validateAllCapsOrSpaceOnEditTextWithHindiEnabled(spouseName) } contactNumber.id -> { diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/BenRegCHODataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/BenRegCHODataset.kt index 614f586b5..22af0f932 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/configuration/BenRegCHODataset.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/BenRegCHODataset.kt @@ -131,11 +131,13 @@ class BenRegCHODataset( } name.id -> { - validateAllCapsOrSpaceOnEditText(name) + // validateAllCapsOrSpaceOnEditText(name) + validateAllCapsOrSpaceOnEditTextWithHindiEnabled(name) } husbandName.id -> { - validateAllCapsOrSpaceOnEditText(husbandName) + // validateAllCapsOrSpaceOnEditText(husbandName) + validateAllCapsOrSpaceOnEditTextWithHindiEnabled(husbandName) } age.id -> { @@ -171,7 +173,7 @@ class BenRegCHODataset( if (isPregnant.value == resources.getStringArray(R.array.yes_no)[0]) { form.genDetails?.let { it.reproductiveStatus = - englishResources.getStringArray(R.array.nbr_reproductive_status_array)[1] + englishResources.getStringArray(R.array.nbr_reproductive_status_array2)[1] it.reproductiveStatusId = 2 } form.processed = "N" diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/BenRegFormDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/BenRegFormDataset.kt index 5b4a27c4d..57909bffc 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/configuration/BenRegFormDataset.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/BenRegFormDataset.kt @@ -3,15 +3,23 @@ package org.piramalswasthya.sakhi.configuration import android.content.Context import android.net.Uri import android.text.InputType +import android.util.Log import android.util.Range import android.widget.LinearLayout +import org.piramalswasthya.sakhi.BuildConfig +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.database.room.SyncState import org.piramalswasthya.sakhi.helpers.Konstants import org.piramalswasthya.sakhi.helpers.Languages import org.piramalswasthya.sakhi.helpers.setToStartOfTheDay import org.piramalswasthya.sakhi.model.AgeUnit import org.piramalswasthya.sakhi.model.BenBasicCache.Companion.getAgeFromDob +import org.piramalswasthya.sakhi.model.BenBasicCache.Companion.getYearsFromDate import org.piramalswasthya.sakhi.model.BenRegCache +import org.piramalswasthya.sakhi.model.BenStatus +import org.piramalswasthya.sakhi.model.EligibleCoupleRegCache import org.piramalswasthya.sakhi.model.FormElement import org.piramalswasthya.sakhi.model.Gender import org.piramalswasthya.sakhi.model.Gender.FEMALE @@ -22,10 +30,15 @@ import org.piramalswasthya.sakhi.model.InputType.CHECKBOXES import org.piramalswasthya.sakhi.model.InputType.DATE_PICKER import org.piramalswasthya.sakhi.model.InputType.DROPDOWN import org.piramalswasthya.sakhi.model.InputType.EDIT_TEXT +import org.piramalswasthya.sakhi.model.InputType.FILE_UPLOAD import org.piramalswasthya.sakhi.model.InputType.IMAGE_VIEW import org.piramalswasthya.sakhi.model.InputType.RADIO import org.piramalswasthya.sakhi.model.InputType.TEXT_VIEW +import org.piramalswasthya.sakhi.ui.home_activity.all_ben.new_ben_registration.ben_form.NewBenRegViewModel.Companion.isOtpVerified +import java.lang.IllegalStateException import java.text.SimpleDateFormat +import java.time.LocalDate +import java.time.ZoneId import java.util.Calendar import java.util.Locale import java.util.concurrent.TimeUnit @@ -34,6 +47,24 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context companion object { + fun calculateMaxSonAge( + parentYears: Int, + parentMonths: Int, + marriageYears: Int, + marriageMonths: Int + ): Pair { + val parentTotalMonths = parentYears * 12 + parentMonths + val bufferTotalMonths = marriageYears * 12 + marriageMonths + + val diffMonths = (parentTotalMonths - bufferTotalMonths).coerceAtLeast(0) + + val years = diffMonths / 12 + val months = diffMonths % 12 + + return Pair(years, months) + } + + private fun getCurrentDateString(): String { val calendar = Calendar.getInstance() val mdFormat = SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH) @@ -46,6 +77,7 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context return cal.timeInMillis } + private fun getHoFMinDobMillis(): Long { val cal = Calendar.getInstance() cal.add(Calendar.YEAR, -1 * Konstants.maxAgeForGenBen) @@ -58,13 +90,24 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context return cal.timeInMillis } + private fun getAgeStringFromDob(dob: Long): String { + val cal = Calendar.getInstance() + cal.timeInMillis = dob + val ageUnitDTO = org.piramalswasthya.sakhi.model.AgeUnitDTO(0, 0, 0) + org.piramalswasthya.sakhi.utils.HelperUtil.updateAgeDTO(ageUnitDTO, cal) + return org.piramalswasthya.sakhi.utils.HelperUtil.getAgeStrFromAgeUnit(ageUnitDTO) + } } private var familyHeadPhoneNo: String? = null private var timeStampDateOfMarriageFromSpouse: Long? = null private var isHoF: Boolean = false + private var isAddSppouse: Int = 0 + + private var isAddSpouse: Boolean = false private var hof: BenRegCache? = null + private var benIfDataExist: BenRegCache? = null private var minAgeYear: Int = 0 private var maxAgeYear: Int = Konstants.maxAgeForGenBen @@ -109,16 +152,6 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context hasSpeechToText = true, etInputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS, ) - - // val ageUnit = FormElement( -// id = 5, -// inputType = DROPDOWN, -// title = resources.getString(R.string.nbr_nb_age_unit), -// arrayId = -1, -// entries = resources.getStringArray(R.array.nbr_age_unit_array), -// required = true, -// hasDependants = true, -// ) private val agePopup = FormElement( id = 115, inputType = org.piramalswasthya.sakhi.model.InputType.AGE_PICKER, @@ -129,30 +162,15 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context allCaps = true, hasSpeechToText = true, hasDependants = true, - etInputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS + etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL + ) + private val dobReadOnly = FormElement( + id = 116, + inputType = TEXT_VIEW, + title = resources.getString(R.string.nbr_dob), + required = false, ) - // val age = FormElement( -// id = 7, -// inputType = EDIT_TEXT, -// title = resources.getString(R.string.nbr_age), -// arrayId = -1, -// required = true, -// hasDependants = true, -// etMaxLength = 2, -// etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, -// max = Konstants.maxAgeForGenBen.toLong(), -// min = Konstants.minAgeForGenBen.toLong(), -// ) -// val dob = FormElement( -// id = 8, -// inputType = DATE_PICKER, -// title = resources.getString(R.string.nbr_dob), -// arrayId = -1, -// required = true, -// showYearFirstInDatePicker = true, -// hasDependants = true, -// ) val gender = FormElement( id = 9, inputType = RADIO, @@ -208,10 +226,10 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context hasSpeechToText = true, etInputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS ) - private val ageAtMarriage = FormElement( + var ageAtMarriage = FormElement( id = 1012, inputType = EDIT_TEXT, - title = resources.getString(R.string.age_at_marriage), + title = resources.getString(R.string.age_at_marriagee), etMaxLength = 2, arrayId = -1, required = true, @@ -235,7 +253,7 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context inputType = EDIT_TEXT, title = resources.getString(R.string.nbr_father_name), arrayId = -1, - required = true, + required = false, allCaps = true, hasSpeechToText = true, etInputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS @@ -245,7 +263,7 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context inputType = EDIT_TEXT, title = resources.getString(R.string.nbr_mother_name), arrayId = -1, - required = true, + required = false, allCaps = true, hasSpeechToText = true, etInputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS @@ -364,6 +382,7 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context ) + val rchId = FormElement( id = 23, inputType = EDIT_TEXT, @@ -404,12 +423,13 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context private val reproductiveStatus = FormElement( id = 1028, - inputType = DROPDOWN, + inputType = TEXT_VIEW, title = resources.getString(R.string.reproductive_status), - arrayId = R.array.nbr_reproductive_status_array, - entries = resources.getStringArray(R.array.nbr_reproductive_status_array), + arrayId = R.array.nbr_reproductive_status_array2, + entries = resources.getStringArray(R.array.nbr_reproductive_status_array2), required = true, - hasDependants = false + hasDependants = false, +// isEnabled = false ) private val birthCertificateNumber = FormElement( id = 1029, @@ -418,6 +438,91 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context arrayId = -1, required = false, ) + + private val sendOtpBtn = FormElement( + id = 42, + inputType = org.piramalswasthya.sakhi.model.InputType.BUTTON, + title = resources.getString(R.string.send_otp), + required = false, + isEnabled = true, + etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, + isMobileNumber = false, + ) + + private val otpField = FormElement( + id = 43, + inputType = EDIT_TEXT, + title = resources.getString(R.string.enter_otp), + arrayId = -1, + required = false, + etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, + isMobileNumber = false, + etMaxLength = 6, + max = 9999999999, + min = 6000000000 + ) + private val tempraryContactNo = FormElement( + id = 44, + inputType = EDIT_TEXT, + title = resources.getString(R.string.contact_number), + arrayId = -1, + required = false, + etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, + isMobileNumber = true, + etMaxLength = 10, + max = 9999999999, + min = 6000000000 + ) + + private val tempraryContactNoBelongsto = FormElement( + id = 45, + inputType = DROPDOWN, + title = resources.getString(R.string.nbr_mobile_number_of), + arrayId = R.array.temp_mobile_no_relation_array, + entries = resources.getStringArray(R.array.temp_mobile_no_relation_array), + required = false, + hasDependants = true, + ) + + private val headLine = FormElement( + id = 45, + inputType = org.piramalswasthya.sakhi.model.InputType.HEADLINE, + title = resources.getString(R.string.cr_birth_cert_uploads), + headingLine = false, + required = false, + ) + private val fileUploadFront = FormElement( + id = 46, + inputType = FILE_UPLOAD, + title = resources.getString(R.string.front_side), + required = false, + ) + private val fileUploadBack = FormElement( + id = 47, + inputType = FILE_UPLOAD, + title = resources.getString(R.string.back_side), + required = false, + ) + + private val haveChildren = FormElement( + id = 48, + inputType = RADIO, + title = resources.getString(R.string.childrens), + arrayId = -1, + entries = resources.getStringArray(R.array.yes_no), + required = true, + hasDependants = true, + ) + fun getIndexOfBirthCertificateFrontPath() = getIndexById(fileUploadFront.id) + fun getIndexOfBirthCertificateBackPath() = getIndexById(fileUploadBack.id) + + private val _isAddingChildren = MutableStateFlow(false) + val isAddingChildren = _isAddingChildren.asStateFlow() + + fun setIsAddingChildren(value: Boolean) { + _isAddingChildren.value = value + } + val firstPage by lazy { listOf( pic, @@ -425,9 +530,6 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context firstName, lastName, agePopup, -// dob, -// age, -// ageUnit, gender, fatherName, motherName, @@ -446,9 +548,6 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context firstName, lastName, agePopup, -// dob, -// age, -// ageUnit, gender, maritalStatus, fatherName, @@ -459,30 +558,36 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context community, religion, rchId, -// hasAadharNo, ) + this.familyHeadPhoneNo = familyHeadPhoneNo?.toString() + ben?.takeIf { !it.isDraft }?.let { saved -> + + if (ben.isDeath) { + initializeDeathFields(list, saved, list.indexOf(lastName)) + } + pic.value = saved.userImage dateOfReg.value = getDateFromLong(saved.regDate) firstName.value = saved.firstName lastName.value = saved.lastName agePopup.value = getDateFromLong(saved.dob) -// dob.value = getDateFromLong(saved.dob) -// age.value = getAgeFromDob(saved.dob).toString() -// ageUnit.value = ageUnit.getStringFromPosition(saved.ageUnitId) + fileUploadFront.value = saved?.kidDetails?.birthCertificateFileFrontView + fileUploadBack.value = saved?.kidDetails?.birthCertificateFileBackView gender.value = gender.getStringFromPosition(saved.genderId) - gender.inputType = TEXT_VIEW +// gender.inputType = TEXT_VIEW + fatherName.value = saved.fatherName - saved.fatherName?.let { - if (it.isNotEmpty()) fatherName.inputType = TEXT_VIEW - } + saved.fatherName?.takeIf { it.isNotEmpty() }?.let { fatherName.inputType = TEXT_VIEW } + motherName.value = saved.motherName - saved.motherName?.let { - if (it.isNotEmpty()) motherName.inputType = TEXT_VIEW + saved.motherName?.takeIf { it.isNotEmpty() }?.let { motherName.inputType = TEXT_VIEW } + + if (saved.isSpouseAdded || saved.isChildrenAdded || saved.doYouHavechildren) { + maritalStatus.inputType = TEXT_VIEW } saved.genDetails?.spouseName?.let { - maritalStatus.inputType = TEXT_VIEW when (saved.genderId) { 1 -> { wifeName.value = it @@ -500,6 +605,7 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context } } } + maritalStatus.entries = when (saved.gender) { MALE -> maritalStatusMale FEMALE -> maritalStatusFemale @@ -511,125 +617,130 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context else -> R.array.nbr_marital_status_male_array } maritalStatus.value = - maritalStatus.getStringFromPosition(saved.genDetails?.maritalStatusId ?: 0) - ageAtMarriage.value = saved.genDetails?.ageAtMarriage.toString() - dateOfMarriage.value = getDateFromLong( - saved.genDetails?.marriageDate ?: 0 - ) + saved.genDetails?.maritalStatusId?.let { maritalStatus.getStringFromPosition(it) } + + ageAtMarriage.value = calculateAgeAtMarriage(saved.dob, saved.genDetails?.marriageDate)?.toString() + dateOfMarriage.value = getDateFromLong(saved.genDetails?.marriageDate ?: 0) + mobileNoOfRelation.value = mobileNoOfRelation.getStringFromPosition(saved.mobileNoOfRelationId) otherMobileNoOfRelation.value = saved.mobileOthers contactNumber.value = saved.contactNumber.toString() - relationToHead.value = relationToHeadListDefault[saved.familyHeadRelationPosition - 1] + + val relationIndex = (saved.familyHeadRelationPosition - 1).coerceIn( + 0, + relationToHeadListDefault.lastIndex + ) + relationToHead.value = relationToHeadListDefault.getOrNull(relationIndex) + otherRelationToHead.value = saved.familyHeadRelationOther community.value = community.getStringFromPosition(saved.communityId) religion.value = religion.getStringFromPosition(saved.religionId) otherReligion.value = saved.religionOthers + childRegisteredAtAwc.value = childRegisteredAtAwc.getStringFromPosition( saved.kidDetails?.childRegisteredSchoolId ?: 0 ) -// saved.kidDetails?.childRegisteredAWCId?.takeIf { it > 0 } -// ?.let { childRegisteredAtAwc.entries?.get(it - 1) } childRegisteredAtSchool.value = childRegisteredAtSchool.getStringFromPosition( saved.kidDetails?.childRegisteredSchoolId ?: 0 ) -// saved.kidDetails?.childRegisteredSchoolId?.takeIf { it > 0 } -// ?.let { childRegisteredAtSchool.entries?.get(it - 1) } typeOfSchool.value = typeOfSchool.getStringFromPosition(saved.kidDetails?.typeOfSchoolId ?: 0) -// saved.kidDetails?.typeOfSchoolId?.takeIf { it > 0 } -// ?.let { typeOfSchool.entries?.get(it - 1) } rchId.value = saved.rchId - -// relationToHead.entries = when (saved.gender) { -// MALE -> relationToHeadListMale -// FEMALE -> relationToHeadListFemale -// TRANSGENDER -> relationToHeadListDefault -// null -> null -// } -// relationToHead.arrayId = when (saved.gender) { -// MALE -> R.array.nbr_relationship_to_head_male -// FEMALE -> R.array.nbr_relationship_to_head_female -// TRANSGENDER -> R.array.nbr_relationship_to_head -// null -> -1 + reproductiveStatus.value = saved.genDetails?.reproductiveStatus +// reproductiveStatus.value = saved.genDetails?.reproductiveStatusId?.let { +// reproductiveStatus.getStringFromPosition(it) // } -// hasAadharNo.value = hasAadharNo.getStringFromPosition(saved.hasAadharId) -// aadharNo.value = saved.aadharNum - rchId.value = saved.rchId - reproductiveStatus.value = saved.genDetails?.reproductiveStatusId?.let { - reproductiveStatus.getStringFromPosition(it) + + // Restore haveChildren value for married females + val maritalStatusIdForChildren = saved.genDetails?.maritalStatusId + if (saved.genderId == 2 && maritalStatusIdForChildren != null && maritalStatusIdForChildren >= 2) { + haveChildren.value = if (saved.doYouHavechildren) haveChildren.entries?.get(0) else haveChildren.entries?.get(1) + _isAddingChildren.value = saved.doYouHavechildren } } - if (maritalStatus.value != null && maritalStatus.value != maritalStatus.entries!![0] && gender.value != null) { -// if(maritalStatus.value ==maritalStatus.entries!![1]) -// list.removeAll(listOf(fatherName, motherName)) - list.add( - list.indexOf(maritalStatus) + 3, when (gender.value) { - gender.entries!![0] -> wifeName - gender.entries!![1] -> husbandName - gender.entries!![2] -> spouseName - else -> throw java.lang.IllegalStateException("Gender unspecified with non empty marital status value!") + + val maritalIndex = list.indexOf(maritalStatus) + if (!maritalStatus.value.isNullOrEmpty() && maritalStatus.entries != null && gender.value != null && maritalIndex >= 0 && maritalStatus.value == maritalStatus.getStringFromPosition(2)) { + val genderField = when (gender.value) { + gender.entries!![0] -> wifeName + gender.entries!![1] -> husbandName + gender.entries!![2] -> spouseName + else -> null + } + genderField?.let { + list.add(maritalIndex + 3, it) + list.add(maritalIndex + 4, ageAtMarriage) + // Add haveChildren for females (gender.entries!![1]) + if (gender.value == gender.entries!![1]) { + list.add(maritalIndex + 5, haveChildren) } - ) - list.add( - list.indexOf(maritalStatus) + 4, - ageAtMarriage - ) + } + } else { + listOf( + husbandName, + wifeName, + spouseName, + ageAtMarriage + ).forEach { list.remove(it) } } - if (maritalStatus.value == maritalStatus.entries!![1] && gender.value == gender.entries!![1]) { + + if (maritalStatus.entries != null && maritalStatus.value == maritalStatus.entries!![1] && gender.value == gender.entries!![1]) { fatherName.required = false motherName.required = false - } - if (maritalStatus.value == maritalStatus.entries!![2]) { + + if (maritalStatus.entries != null && maritalStatus.value == maritalStatus.entries!![2]) { husbandName.required = false wifeName.required = false spouseName.required = false - } + ageAtMarriage.value?.takeIf { - it.isNotEmpty() && it == (getAgeFromDob( - getLongFromDate(agePopup.value) - )).toString() + it.isNotEmpty() && it == getAgeFromDob(getLongFromDate(agePopup.value)).toString() }?.let { - if (!list.contains(dateOfMarriage)) - list.add(list.indexOf(ageAtMarriage) + 1, dateOfMarriage) + if (!list.contains(dateOfMarriage)) { + val ageIndex = list.indexOf(ageAtMarriage) + if (ageIndex >= 0) list.add(ageIndex + 1, dateOfMarriage) + } } - if (mobileNoOfRelation.value == mobileNoOfRelation.entries!!.last()) { - list.add(list.indexOf(mobileNoOfRelation) + 1, otherMobileNoOfRelation) + + val mobileIndex = list.indexOf(mobileNoOfRelation) + if (mobileNoOfRelation.entries != null && mobileNoOfRelation.value == mobileNoOfRelation.entries!!.last() && mobileIndex >= 0) { + list.add(mobileIndex + 1, otherMobileNoOfRelation) } - if (relationToHead.value == relationToHead.entries!!.last()) { - list.add(list.indexOf(relationToHead) + 1, otherRelationToHead) + + val relationHeadIndex = list.indexOf(relationToHead) + if (relationToHead.entries != null && relationToHead.value == relationToHead.entries!!.last() && relationHeadIndex >= 0) { + list.add(relationHeadIndex + 1, otherRelationToHead) } - if (religion.value == religion.entries!![7]) { - list.add(list.indexOf(religion) + 1, otherReligion) + + val religionIndex = list.indexOf(religion) + if (religion.entries != null && religion.value == religion.entries!![7] && religionIndex >= 0) { + list.add(religionIndex + 1, otherReligion) } -// if (ageUnit.value == ageUnit.entries?.last() && (age.value?.toInt() ?: 0) in 3..5) { -// list.add((list.indexOf(rchId)), childRegisteredAtAwc) -// } - if ((getAgeFromDob(getLongFromDate(agePopup.value))) in 4..14) { - list.add((list.indexOf(rchId)), childRegisteredAtSchool) + + val dob = agePopup.value?.takeIf { it.isNotEmpty() }?.let { getLongFromDate(it) } + val rchIndex = list.indexOf(rchId) + if (dob != null && getAgeFromDob(dob) in 4..14 && rchIndex >= 0) { + list.add(rchIndex, childRegisteredAtSchool) } - if (childRegisteredAtSchool.value == childRegisteredAtSchool.entries?.first()) list.add( - list.indexOf( - childRegisteredAtSchool - ) + 1, typeOfSchool - ) + + val schoolIndex = list.indexOf(childRegisteredAtSchool) + if (childRegisteredAtSchool.entries != null && childRegisteredAtSchool.value == childRegisteredAtSchool.entries?.first() && schoolIndex >= 0) { + list.add(schoolIndex + 1, typeOfSchool) + } + + birthCertificateNumber.value = ben?.kidDetails?.birthCertificateNumber placeOfBirth.value = ben?.kidDetails?.birthPlaceId?.let { placeOfBirth.getStringFromPosition(it) } -// if (hasAadharNo.value == hasAadharNo.entries!!.first()) { -// aadharNo.inputType = TEXT_VIEW -// list.add( -// list.indexOf(hasAadharNo) + 1, aadharNo -// ) -// } - if (hasThirdPage()) - list.add(reproductiveStatus) + + if (hasThirdPage()) list.add(reproductiveStatus) + if (isKid()) { - list.removeAll( - listOf( + listOf( maritalStatus, husbandName, wifeName, @@ -637,32 +748,61 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context ageAtMarriage, dateOfMarriage, reproductiveStatus - ) - ) + ).forEach { list.remove(it) } list.addAll( listOf( birthCertificateNumber, - placeOfBirth + placeOfBirth, + headLine, + fileUploadFront, + fileUploadBack ) ) } - if (!isKid() and !hasThirdPage()) { + + if (!isKid() && !hasThirdPage()) { list.remove(rchId) } setUpPage(list) } + private fun calculateMarriageDate(marriageAge: Int, dob: Long): Long { + val calendar = Calendar.getInstance() + calendar.timeInMillis = dob + calendar.add(Calendar.YEAR, marriageAge) + return calendar.timeInMillis + } + + private fun calculateAgeAtMarriage(dob: Long, marriageDate: Long?): Int? { + if (marriageDate == null || marriageDate <= 0L) return null + val doB = Calendar.getInstance() + doB.timeInMillis = dob + val marriageDatE = Calendar.getInstance() + marriageDatE.timeInMillis = marriageDate + var ageAtMarriage = marriageDatE.get(Calendar.YEAR) - doB.get(Calendar.YEAR) + if (marriageDatE.get(Calendar.DAY_OF_YEAR) < doB.get(Calendar.DAY_OF_YEAR)) { + ageAtMarriage-- + } + Log.d("marriage age", ageAtMarriage.toString()) + return ageAtMarriage.takeIf { it >= Konstants.minAgeForMarriage } + } + + + private val isMitaninVariant = BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true) + suspend fun setPageForHof(ben: BenRegCache?, household: HouseholdCache) { val list = mutableListOf( pic, dateOfReg, firstName, lastName, + ) + if (!isMitaninVariant) { + list.addAll(listOf(tempraryContactNo, tempraryContactNoBelongsto, sendOtpBtn)) + } + list.addAll(listOf( agePopup, -// dob, -// age, -// ageUnit, gender, maritalStatus, fatherName, @@ -671,55 +811,87 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context community, religion, rchId, -// hasAadharNo, - ) + )) this.familyHeadPhoneNo = household.family?.familyHeadPhoneNo?.toString() + if (!isMitaninVariant) { + tempraryContactNoBelongsto.value = + tempraryContactNoBelongsto.getStringFromPosition(1) + tempraryContactNo.value = familyHeadPhoneNo + } this.isHoF = true if (dateOfReg.value == null) dateOfReg.value = getCurrentDateString() contactNumber.value = familyHeadPhoneNo +// household.family?.let { +// firstName.value = it.familyHeadName?.also { +// firstName.inputType = TEXT_VIEW +// } +// +// lastName.value = it.familyName?.also { +// lastName.inputType = TEXT_VIEW +// } +// +// contactNumber.value = it.familyHeadPhoneNo?.toString()?.also { +// contactNumber.inputType = TEXT_VIEW +// } +// } household.family?.let { - firstName.value = it.familyHeadName?.also { - firstName.inputType = TEXT_VIEW - } - lastName.value = it.familyName/*?.also { - lastName.inputType = TEXT_VIEW - }*/ - contactNumber.value = it.familyHeadPhoneNo?.toString()?.also { - contactNumber.inputType = TEXT_VIEW - } + firstName.value = it.familyHeadName + + lastName.value = it.familyName + + contactNumber.value = it.familyHeadPhoneNo?.toString() } agePopup.min = getHoFMinDobMillis() agePopup.max = getHofMaxDobMillis() + ben?.takeIf { !it.isDraft }?.let { saved -> + list.add(list.indexOf(lastName) + 1, beneficiaryStatus) + if (saved.isDeath) { + list.add(list.indexOf(beneficiaryStatus) + 1, dateOfDeath) + list.add(list.indexOf(dateOfDeath) + 1, timeOfDeath) + list.add(list.indexOf(timeOfDeath) + 1, reasonOfDeath) + list.add(list.indexOf(reasonOfDeath) + 1, placeOfDeath) + placeOfDeath.entries?.indexOf(saved.placeOfDeath)?.takeIf { it >= 0 } + ?.let { index -> + if (index == 8) { + list.add(list.indexOf(placeOfDeath) + 1, otherPlaceOfDeath) + } + } -// ageUnit.apply { -// inputType = TEXT_VIEW -// value = ageUnit.entries!!.last() -// } -// dob.min = getHoFMinDobMillis() -// dob.max = getHofMaxDobMillis() -// age.min = Konstants.minAgeForGenBen.toLong().also { -// minAgeYear = it.toInt() -// } -// age.max = Konstants.maxAgeForGenBen.toLong().also { -// maxAgeYear = it.toInt() -// } + } + + beneficiaryStatus.value = when (saved.isDeath) { + true -> BenStatus.Death.name + false -> BenStatus.Alive.name + null -> null + } + dateOfDeath.value = saved.dateOfDeath + timeOfDeath.value = saved.timeOfDeath + reasonOfDeath.value = saved.reasonOfDeath + placeOfDeath.value = saved.placeOfDeath + otherPlaceOfDeath.value = saved.otherPlaceOfDeath + - ben?.takeIf { !it.isDraft }?.let { saved -> pic.value = saved.userImage dateOfReg.value = getDateFromLong(saved.regDate) firstName.value = saved.firstName lastName.value = saved.lastName agePopup.value = getDateFromLong(saved.dob) -// dob.value = getDateFromLong(saved.dob) -// age.value = getAgeFromDob(saved.dob).toString() -// ageUnit.value = ageUnit.getStringFromPosition(saved.ageUnitId) gender.value = gender.getStringFromPosition(saved.genderId) - gender.inputType = TEXT_VIEW + if (ben.isSpouseAdded || ben.isChildrenAdded || ben.doYouHavechildren || !ben.genDetails?.spouseName.isNullOrEmpty()) { + gender.inputType = TEXT_VIEW + agePopup.inputType = TEXT_VIEW + agePopup.value = getAgeStringFromDob(saved.dob) + dobReadOnly.value = getDateFromLong(saved.dob) + list.add(list.indexOf(agePopup) + 1, dobReadOnly) + maritalStatus.inputType = TEXT_VIEW + } fatherName.value = saved.fatherName + fileUploadFront.value = saved.kidDetails?.birthCertificateFileFrontView + fileUploadBack.value = saved.kidDetails?.birthCertificateFileBackView saved.fatherName?.let { if (it.isNotEmpty()) fatherName.inputType = TEXT_VIEW } @@ -746,12 +918,23 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context } maritalStatus.value = maritalStatus.getStringFromPosition(saved.genDetails?.maritalStatusId ?: 0) - ageAtMarriage.value = saved.genDetails?.ageAtMarriage.toString() + ageAtMarriage.value = calculateAgeAtMarriage(saved.dob, saved.genDetails?.marriageDate)?.toString() + ageAtMarriage.max = getAgeFromDob(saved.dob).toLong() dateOfMarriage.value = getDateFromLong( saved.genDetails?.marriageDate ?: 0 ) + val maritalIndex = list.indexOf(maritalStatus) + if (maritalStatus.value == maritalStatus.entries!![1]) { + list.add(maritalIndex + 1, ageAtMarriage) + } mobileNoOfRelation.value = mobileNoOfRelation.getStringFromPosition(saved.mobileNoOfRelationId) + if (!isMitaninVariant) { + tempraryContactNoBelongsto.value = + tempraryContactNoBelongsto.getStringFromPosition(saved.tempMobileNoOfRelationId) + tempraryContactNoBelongsto.isEnabled = false + tempraryContactNo.value = saved.contactNumber.toString() + } otherMobileNoOfRelation.value = saved.mobileOthers contactNumber.value = saved.contactNumber.toString() relationToHead.value = relationToHeadListDefault[saved.familyHeadRelationPosition - 1] @@ -762,45 +945,20 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context childRegisteredAtAwc.value = childRegisteredAtAwc.getStringFromPosition( saved.kidDetails?.childRegisteredSchoolId ?: 0 ) -// saved.kidDetails?.childRegisteredAWCId?.takeIf { it > 0 } -// ?.let { childRegisteredAtAwc.entries?.get(it - 1) } childRegisteredAtSchool.value = childRegisteredAtSchool.getStringFromPosition( saved.kidDetails?.childRegisteredSchoolId ?: 0 ) -// saved.kidDetails?.childRegisteredSchoolId?.takeIf { it > 0 } -// ?.let { childRegisteredAtSchool.entries?.get(it - 1) } typeOfSchool.value = typeOfSchool.getStringFromPosition(saved.kidDetails?.typeOfSchoolId ?: 0) -// saved.kidDetails?.typeOfSchoolId?.takeIf { it > 0 } -// ?.let { typeOfSchool.entries?.get(it - 1) } rchId.value = saved.rchId - -// relationToHead.entries = when (saved.gender) { -// MALE -> relationToHeadListMale -// FEMALE -> relationToHeadListFemale -// TRANSGENDER -> relationToHeadListDefault -// null -> null -// } -// relationToHead.arrayId = when (saved.gender) { -// MALE -> R.array.nbr_relationship_to_head_male -// FEMALE -> R.array.nbr_relationship_to_head_female -// TRANSGENDER -> R.array.nbr_relationship_to_head -// null -> -1 -// } } if (mobileNoOfRelation.value == mobileNoOfRelation.entries!!.last()) { list.add(list.indexOf(mobileNoOfRelation) + 1, otherMobileNoOfRelation) } -// if (relationToHead.value == relationToHead.entries!!.last()) { -// list.add(list.indexOf(relationToHead) + 1, otherRelationToHead) -// } if (religion.value == religion.entries!![7]) { list.add(list.indexOf(religion) + 1, otherReligion) } -// if (ageUnit.value == ageUnit.entries?.last() && (age.value?.toInt() ?: 0) in 3..5) { -// list.add((list.indexOf(rchId)), childRegisteredAtAwc) -// } if ((getAgeFromDob(getLongFromDate(agePopup.value))) in 4..14) { list.add((list.indexOf(rchId)), childRegisteredAtSchool) } @@ -810,6 +968,7 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context ) + 1, typeOfSchool ) + if (!isKid() and !hasThirdPage()) { list.remove(rchId) } @@ -822,17 +981,21 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context hoF: BenRegCache?, benGender: Gender, relationToHeadId: Int, - hoFSpouse: List = emptyList() + hoFSpouse: List = emptyList(), + selectedben: BenRegCache?, + isAddspouse: Int, ) { val list = mutableListOf( pic, dateOfReg, firstName, lastName, + ) + if (!isMitaninVariant) { + list.addAll(listOf(tempraryContactNo, tempraryContactNoBelongsto, sendOtpBtn)) + } + list.addAll(listOf( agePopup, -// dob, -// age, -// ageUnit, gender, maritalStatus, fatherName, @@ -842,16 +1005,24 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context contactNumber, community, religion, - rchId, -// hasAadharNo, - ) + rchId + )) this.familyHeadPhoneNo = household.family?.familyHeadPhoneNo?.toString() + this.isAddSppouse = isAddspouse + if (!isMitaninVariant) { + tempraryContactNoBelongsto.value = + tempraryContactNoBelongsto.getStringFromPosition(1) + } this.hof = hoF + this.benIfDataExist = ben if (ben == null) { dateOfReg.value = getCurrentDateString() // ageUnit.value = ageUnit.entries!!.last() mobileNoOfRelation.value = mobileNoOfRelation.entries!![4] contactNumber.value = familyHeadPhoneNo + if (!isMitaninVariant) { + tempraryContactNo.value = familyHeadPhoneNo + } community.value = hoF?.communityId?.let { community.getStringFromPosition(it) } religion.value = hoF?.religionId?.let { religion.getStringFromPosition(it) } gender.value = when (benGender) { @@ -869,18 +1040,34 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context FEMALE -> R.array.nbr_marital_status_female_array TRANSGENDER -> R.array.nbr_marital_status_male_array } - gender.inputType = TEXT_VIEW +// gender.inputType = TEXT_VIEW relationToHead.value = relationToHead.getStringFromPosition(relationToHeadId + 1) if (relationToHeadId == relationToHead.entries!!.lastIndex) { list.add(list.indexOf(relationToHead) + 1, otherRelationToHead) } + list.remove(reproductiveStatus) } agePopup.min = getMinDobMillis() agePopup.max = System.currentTimeMillis() -// dob.min = getMinDobMillis() -// dob.max = System.currentTimeMillis() + if (isAddspouse == 1) { + isAddSpouse = true + gender.inputType = TEXT_VIEW + maritalStatus.inputType = TEXT_VIEW + reproductiveStatus.inputType = DROPDOWN + } if (relationToHeadId == 4 || relationToHeadId == 5) hoF?.let { - setUpForSpouse(it, hoFSpouse) + isAddSpouse = true +// agePopup.inputType = TEXT_VIEW + gender.inputType = TEXT_VIEW + maritalStatus.inputType = TEXT_VIEW + reproductiveStatus.inputType = DROPDOWN + setUpForSpouse(it, hoFSpouse,list) + } + if (relationToHeadId == 9 || relationToHeadId == 19 || relationToHeadId == 13 || + relationToHeadId == 11 || relationToHeadId == 17 || relationToHeadId == 2 || + relationToHeadId == 18 || relationToHeadId == 14 || + relationToHeadId == 10 || relationToHeadId == 12 || relationToHeadId == 16) hoF?.let { + setUpPageforOthers(it,hoFSpouse,selectedben,list) } if (relationToHeadId == 8 || relationToHeadId == 9) hoF?.let { setUpForChild(it, hoFSpouse.firstOrNull()) @@ -893,18 +1080,57 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context ben?.takeIf { !it.isDraft }?.let { saved -> + list.add(list.indexOf(lastName) + 1, beneficiaryStatus) + + if (saved.isDeath) { + list.add(list.indexOf(beneficiaryStatus) + 1, dateOfDeath) + list.add(list.indexOf(dateOfDeath) + 1, timeOfDeath) + list.add(list.indexOf(timeOfDeath) + 1, reasonOfDeath) + list.add(list.indexOf(reasonOfDeath) + 1, placeOfDeath) + placeOfDeath.entries?.indexOf(saved.placeOfDeath)?.takeIf { it >= 0 } + ?.let { index -> + if (index == 8) { + list.add(list.indexOf(placeOfDeath) + 1, otherPlaceOfDeath) + } + } + + + } + + beneficiaryStatus.value = when (saved.isDeath) { + true -> BenStatus.Death.name + false -> BenStatus.Alive.name + null -> null + } + dateOfDeath.value = saved.dateOfDeath + timeOfDeath.value = saved.timeOfDeath + reasonOfDeath.value = saved.reasonOfDeath + placeOfDeath.value = saved.placeOfDeath + otherPlaceOfDeath.value = saved.otherPlaceOfDeath + + try { + handleForAgeDob(formId = agePopup.id) + } catch(e: Exception) { + e.printStackTrace() + } pic.value = saved.userImage dateOfReg.value = getDateFromLong(saved.regDate) firstName.value = saved.firstName lastName.value = saved.lastName agePopup.value = getDateFromLong(saved.dob) ageAtMarriage.max = getAgeFromDob(saved.dob).toLong() -// dob.value = getDateFromLong(saved.dob) -// age.value = getAgeFromDob(saved.dob).toString() -// ageUnit.value = ageUnit.getStringFromPosition(saved.ageUnitId) gender.value = gender.getStringFromPosition(saved.genderId) - gender.inputType = TEXT_VIEW + if (ben.isSpouseAdded || ben.isChildrenAdded || ben.doYouHavechildren || !ben.genDetails?.spouseName.isNullOrEmpty()) { + gender.inputType = TEXT_VIEW + agePopup.inputType = TEXT_VIEW + agePopup.value = getAgeStringFromDob(saved.dob) + dobReadOnly.value = getDateFromLong(saved.dob) + list.add(list.indexOf(agePopup) + 1, dobReadOnly) + maritalStatus.inputType = TEXT_VIEW + } fatherName.value = saved.fatherName + fileUploadFront.value = saved.kidDetails?.birthCertificateFileFrontView + fileUploadBack.value = saved.kidDetails?.birthCertificateFileBackView saved.fatherName?.let { if (it.isNotEmpty()) fatherName.inputType = TEXT_VIEW } @@ -921,12 +1147,22 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context } maritalStatus.value = maritalStatus.getStringFromPosition(saved.genDetails?.maritalStatusId ?: 0) - ageAtMarriage.value = saved.genDetails?.ageAtMarriage.toString() + ageAtMarriage.value = calculateAgeAtMarriage(saved.dob, saved.genDetails?.marriageDate)?.toString() dateOfMarriage.value = getDateFromLong( saved.genDetails?.marriageDate ?: 0 ) + val maritalIndex = list.indexOf(maritalStatus) + if (maritalStatus.value == maritalStatus.entries!![1]) { + list.add(maritalIndex + 1, ageAtMarriage) + } mobileNoOfRelation.value = mobileNoOfRelation.getStringFromPosition(saved.mobileNoOfRelationId) + if (!isMitaninVariant) { + tempraryContactNoBelongsto.value = + tempraryContactNoBelongsto.getStringFromPosition(saved.tempMobileNoOfRelationId) + tempraryContactNoBelongsto.isEnabled = false + tempraryContactNo.value = saved.contactNumber.toString() + } otherMobileNoOfRelation.value = saved.mobileOthers contactNumber.value = saved.contactNumber.toString() // relationToHead.entries = relationToHeadListDefault @@ -941,19 +1177,19 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context childRegisteredAtAwc.value = childRegisteredAtAwc.getStringFromPosition( saved.kidDetails?.childRegisteredSchoolId ?: 0 ) -// saved.kidDetails?.childRegisteredAWCId?.takeIf { it > 0 } -// ?.let { childRegisteredAtAwc.entries?.get(it - 1) } childRegisteredAtSchool.value = childRegisteredAtSchool.getStringFromPosition( saved.kidDetails?.childRegisteredSchoolId ?: 0 ) -// saved.kidDetails?.childRegisteredSchoolId?.takeIf { it > 0 } -// ?.let { childRegisteredAtSchool.entries?.get(it - 1) } typeOfSchool.value = typeOfSchool.getStringFromPosition(saved.kidDetails?.typeOfSchoolId ?: 0) -// saved.kidDetails?.typeOfSchoolId?.takeIf { it > 0 } -// ?.let { typeOfSchool.entries?.get(it - 1) } rchId.value = saved.rchId + // Restore haveChildren value for married females + val maritalStatusId = saved.genDetails?.maritalStatusId + if (saved.genderId == 2 && maritalStatusId != null && maritalStatusId >= 2) { + haveChildren.value = if (saved.doYouHavechildren) haveChildren.entries?.get(0) else haveChildren.entries?.get(1) + _isAddingChildren.value = saved.doYouHavechildren + } relationToHead.entries = when (saved.gender) { MALE -> relationToHeadListMale @@ -970,21 +1206,22 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context } if (maritalStatus.value != null && maritalStatus.value != maritalStatus.entries!![0] && gender.value != null) { -// if(maritalStatus.value ==maritalStatus.entries!![1]) -// list.removeAll(listOf(fatherName, motherName)) list.add( list.indexOf(maritalStatus) + 3, when (gender.value) { gender.entries!![0] -> wifeName gender.entries!![1] -> husbandName gender.entries!![2] -> spouseName - else -> throw java.lang.IllegalStateException("Gender unspecified with non empty marital status value!") + else -> throw IllegalStateException("Gender unspecified with non empty marital status value!") } ) + // Add haveChildren for females if not already in list + if (gender.value == gender.entries!![1] && !list.contains(haveChildren)) { + val ageAtMarriageIndex = list.indexOf(ageAtMarriage) + if (ageAtMarriageIndex >= 0) { + list.add(ageAtMarriageIndex + 1, haveChildren) + } + } -// list.add( -// list.indexOf(maritalStatus) + 4, -// ageAtMarriage -// ) } if (maritalStatus.value == maritalStatus.entries!![1] && gender.value == gender.entries!![1]) { @@ -1009,15 +1246,9 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context if (mobileNoOfRelation.value == mobileNoOfRelation.entries!!.last()) { list.add(list.indexOf(mobileNoOfRelation) + 1, otherMobileNoOfRelation) } -// if (relationToHead.value == relationToHead.entries!!.last()) { -// list.add(list.indexOf(relationToHead) + 1, otherRelationToHead) -// } if (religion.value == religion.entries!![7]) { list.add(list.indexOf(religion) + 1, otherReligion) } -// if (ageUnit.value == ageUnit.entries?.last() && (age.value?.toInt() ?: 0) in 3..5) { -// list.add((list.indexOf(rchId)), childRegisteredAtAwc) -// } if ((getAgeFromDob(getLongFromDate(agePopup.value))) in 4..14) { list.add((list.indexOf(rchId)), childRegisteredAtSchool) } @@ -1033,24 +1264,47 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context if (relationToHeadId == 4 || relationToHeadId == 5) hoF?.let { if (it.genDetails?.maritalStatusId == 2) { - agePopup.min = getHoFMinDobMillis() - agePopup.max = getHofMaxDobMillis() + if (benIfDataExist != null) { + try { + handleForAgeDob(formId = reproductiveStatus.id) + } catch(e: Exception) { + e.printStackTrace() + } +// handleForAgeDob(formId = reproductiveStatus.id) + } else { + agePopup.min = getHoFMinDobMillis() + agePopup.max = getHofMaxDobMillis() + } + } } if (relationToHeadId == 8 || relationToHeadId == 9) hoF?.let { - val hoFAge = getAgeFromDob(it.dob) + + val hoFAge = getAgeFromDob(hoF.dob) val hoFSpouseAge = hoFSpouse.firstOrNull()?.dob?.let { h -> getAgeFromDob(h) } - val maxAge = (if (hoFSpouseAge == null) hoFAge else minOf( - hoFAge, - hoFSpouseAge - )) - Konstants.minAgeForGenBen + val minParentAge = hoFSpouseAge?.let { minOf(hoFAge, it) } ?: hoFAge + + val hofAgeAtMarriage = hoF.genDetails?.ageAtMarriage ?: 0 + var hoFSpouseAgeAtMarriage = hoFSpouse.firstOrNull()?.genDetails?.ageAtMarriage + val minAgeAtMarriage = + hoFSpouseAgeAtMarriage?.let { minOf(hofAgeAtMarriage, it) } ?: hofAgeAtMarriage + + val (maxSonYears, maxSonMonths) = calculateMaxSonAge( + parentYears = minParentAge, + parentMonths = 0, + marriageYears = minAgeAtMarriage, + marriageMonths = 7 + ) - agePopup.min = Calendar.getInstance().setToStartOfTheDay().let { cal -> - cal.add(Calendar.YEAR, -1 * maxAge) - cal.timeInMillis - } - maxAgeYear = maxAge + val totalMonthsToSubtract = maxSonYears * 12 + maxSonMonths + val maxAllowedDobMillis = Calendar.getInstance().setToStartOfTheDay().apply { + add(Calendar.MONTH, -totalMonthsToSubtract) + }.timeInMillis + + agePopup.min = maxAllowedDobMillis + + maxAgeYear = maxSonYears } if (relationToHeadId == 0 || relationToHeadId == 1) hoF?.let { @@ -1064,8 +1318,7 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context ageAtMarriage.max = getAgeFromDob(it.dob).toLong() } if (isKid()) { - list.removeAll( - listOf( + listOf( maritalStatus, husbandName, wifeName, @@ -1073,34 +1326,42 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context ageAtMarriage, dateOfMarriage, reproductiveStatus - ) - ) + ).forEach { list.remove(it) } list.addAll( listOf( birthCertificateNumber, - placeOfBirth + placeOfBirth, + headLine, + fileUploadFront, + fileUploadBack ) ) } if (!isKid() and !hasThirdPage()) { list.remove(rchId) } + if (hasThirdPage()) list.add(reproductiveStatus) setUpPage(list) } - private fun setUpForChild(hoF: BenRegCache, hoFSpouse: BenRegCache?) { + private fun setUpForChild( + hoF: BenRegCache, + hoFSpouse: BenRegCache?, + ) { if (hoF.gender == MALE) { fatherName.value = "${hoF.firstName} ${hoF.lastName ?: ""}" motherName.value = hoFSpouse?.let { "${it.firstName} ${it.lastName ?: ""}" } ?: hoF.genDetails?.spouseName + fatherName.value?.let { if (it.isNotEmpty()) fatherName.inputType = TEXT_VIEW } motherName.value?.let { if (it.isNotEmpty()) motherName.inputType = TEXT_VIEW } + } else { motherName.value = "${hoF.firstName} ${hoF.lastName ?: ""}" fatherName.value = hoFSpouse?.let { @@ -1113,23 +1374,33 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context if (it.isNotEmpty()) motherName.inputType = TEXT_VIEW } } + fileUploadFront.value = hof?.kidDetails?.birthCertificateFileFrontView + fileUploadBack.value = hof?.kidDetails?.birthCertificateFileBackView val hoFAge = getAgeFromDob(hoF.dob) - val hoFSpouseAge = hoFSpouse?.dob?.let { getAgeFromDob(it) } - val maxAge = (if (hoFSpouseAge == null) hoFAge else minOf( - hoFAge, - hoFSpouseAge - )) - Konstants.minAgeForGenBen -// age.max = maxAge.toLong() -// dob.min = Calendar.getInstance().setToStartOfTheDay().let { -// it.add(Calendar.YEAR, -1 * maxAge) -// it.timeInMillis -// } + val hoFSpouseAge = hoFSpouse?.dob?.let { h -> getAgeFromDob(h) } + val minParentAge = hoFSpouseAge?.let { minOf(hoFAge, it) } ?: hoFAge + + val hofAgeAtMarriage = hoF.genDetails?.ageAtMarriage ?: 0 + var hoFSpouseAgeAtMarriage = hoFSpouse?.genDetails?.ageAtMarriage + val minAgeAtMarriage = + hoFSpouseAgeAtMarriage?.let { minOf(hofAgeAtMarriage, it) } ?: hofAgeAtMarriage + + + val (maxSonYears, maxSonMonths) = calculateMaxSonAge( + parentYears = minParentAge, + parentMonths = 0, + marriageYears = minAgeAtMarriage, + marriageMonths = 7 + ) + + val totalMonthsToSubtract = maxSonYears * 12 + maxSonMonths + val maxAllowedDobMillis = Calendar.getInstance().setToStartOfTheDay().apply { + add(Calendar.MONTH, -totalMonthsToSubtract) + }.timeInMillis + agePopup.min = maxAllowedDobMillis + + maxAgeYear = maxSonYears - agePopup.min = Calendar.getInstance().setToStartOfTheDay().let { - it.add(Calendar.YEAR, -1 * maxAge) - it.timeInMillis - } - maxAgeYear = maxAge lastName.value = hoF.lastName } @@ -1144,57 +1415,107 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context minAgeYear = minAge firstName.value = when (benGender) { - MALE -> hoF.fatherName?.also { firstName.inputType = TEXT_VIEW } - FEMALE -> hoF.motherName?.also { firstName.inputType = TEXT_VIEW } + MALE -> hoF.fatherName?.takeIf { it.isNotEmpty() }?.also { firstName.inputType = TEXT_VIEW } + FEMALE -> hoF.motherName?.takeIf { it.isNotEmpty() }?.also { firstName.inputType = TEXT_VIEW } else -> null } if (benGender == MALE) wifeName.value = hof?.motherName if (benGender == FEMALE) husbandName.value = hof?.fatherName - lastName.value = hoF.lastName?.also { firstName.inputType = TEXT_VIEW } + maritalStatus.value = maritalStatus.getStringFromPosition(2) + + if (hoF.isSpouseAdded || hoF.isChildrenAdded || hoF.doYouHavechildren) { + maritalStatus.inputType = TEXT_VIEW + agePopup.inputType = TEXT_VIEW + } + + lastName.value = hoF.lastName?.also { lastName.inputType = TEXT_VIEW } ageAtMarriage.max = getAgeFromDob(hoF.dob).toLong() } - private fun setUpForSpouse(hoFSpouse: BenRegCache, hoFSpouse1: List) { + private fun setUpForSpouse( + hoFSpouse: BenRegCache, + hoFSpouse1: List, + list1: MutableList + ) { if (hoFSpouse.genDetails?.maritalStatusId == 2) { if (hoFSpouse1.isEmpty()) { firstName.value = hoFSpouse.genDetails?.spouseName firstName.inputType = TEXT_VIEW lastName.value = hoFSpouse.lastName - lastName.inputType = TEXT_VIEW + lastName.inputType = EDIT_TEXT + } else { + lastName.value = hoFSpouse.lastName + lastName.inputType = EDIT_TEXT } if (hoFSpouse.gender == FEMALE) { wifeName.value = "${hoFSpouse.firstName} ${hoFSpouse.lastName ?: ""}" wifeName.inputType = TEXT_VIEW + } else { husbandName.value = "${hoFSpouse.firstName} ${hoFSpouse.lastName ?: ""}" husbandName.inputType = TEXT_VIEW } -// ageUnit.value = ageUnit.entries!!.last() -// ageUnit.inputType = TEXT_VIEW + maritalStatus.value = maritalStatus.getStringFromPosition(2) - maritalStatus.inputType = TEXT_VIEW -// ageAtMarriage.inputType = TEXT_VIEW + if (hoFSpouse.isSpouseAdded || hoFSpouse.isChildrenAdded || hoFSpouse.doYouHavechildren) { + maritalStatus.inputType = TEXT_VIEW + } +// maritalStatus.inputType = TEXT_VIEW + if (hoFSpouse.gender == MALE) { + list1.add(list1.indexOf(maritalStatus) + 1 ,haveChildren) + } + + timeStampDateOfMarriageFromSpouse = hoFSpouse.genDetails?.marriageDate -// dob.min = getHoFMinDobMillis() -// dob.max = getHofMaxDobMillis() -// age.min = Konstants.minAgeForGenBen.toLong().also { -// minAgeYear = it.toInt() -// } -// age.max = Konstants.maxAgeForGenBen.toLong().also { -// maxAgeYear = it.toInt() -// } + agePopup.min = getHoFMinDobMillis() + agePopup.max = getHofMaxDobMillis() +// reproductiveStatus.isEnabled = true + } + } + private fun setUpPageforOthers( + hoFSpouse: BenRegCache, hoFSpouse1: List, + selectedben: BenRegCache?, + list1: MutableList + ) { + if (selectedben?.genDetails?.maritalStatusId == 2) { + + firstName.value = selectedben?.genDetails?.spouseName + if (firstName.value?.isNotEmpty()!!) { + firstName.inputType = TEXT_VIEW + + } + lastName.value = selectedben?.lastName + lastName.inputType = EDIT_TEXT + + if (selectedben?.gender == FEMALE) { + wifeName.value = "${selectedben?.firstName} ${selectedben?.lastName ?: ""}" + wifeName.inputType = TEXT_VIEW + } else { + husbandName.value = "${selectedben?.firstName} ${selectedben?.lastName ?: ""}" + husbandName.inputType = TEXT_VIEW + } + maritalStatus.value = maritalStatus.getStringFromPosition(2) + if (selectedben.isSpouseAdded || selectedben.isChildrenAdded || selectedben.doYouHavechildren) { + maritalStatus.inputType = TEXT_VIEW + } +// maritalStatus.inputType = TEXT_VIEW + if (selectedben?.gender == MALE) { + list1.add(list1.indexOf(maritalStatus) + 1 ,haveChildren) + + } + timeStampDateOfMarriageFromSpouse = selectedben?.genDetails?.marriageDate agePopup.min = getHoFMinDobMillis() agePopup.max = getHofMaxDobMillis() } } private fun hasThirdPage(): Boolean { - return ((getAgeFromDob(getLongFromDate(agePopup.value))) >= Konstants.minAgeForGenBen && + val result = ((getAgeFromDob(getLongFromDate(agePopup.value))) >= Konstants.minAgeForGenBen && (gender.value == gender.entries!![1])) - + return result } - private fun isKid(): Boolean { + fun isKid(): Boolean { return ((getAgeFromDob(getLongFromDate(agePopup.value))) < 15) } @@ -1346,7 +1667,6 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context ) private val motherOfChild = FormElement( id = 41, inputType = DROPDOWN, title = resources.getString(R.string.mother_of_the_child), -// entries = pncMotherList?.toTypedArray(), arrayId = -1, required = true ) @@ -1372,6 +1692,59 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL, etMaxLength = 4 ) + private val beneficiaryStatus = FormElement( + id = 50, + inputType = RADIO, + title = context.getString(R.string.beneficiary_status), + arrayId = R.array.beneficiary_status, + entries = resources.getStringArray(R.array.beneficiary_status), + required = false, + hasDependants = true, + ) + + private val dateOfDeath = FormElement( + id = 51, + arrayId = -1, + inputType = DATE_PICKER, + title = context.getString(R.string.date_of_death), + max = System.currentTimeMillis(), + required = true, + ) + + private val timeOfDeath = FormElement( + id = 52, + inputType = org.piramalswasthya.sakhi.model.InputType.TIME_PICKER, + title = context.getString(R.string.time_of_death), + required = false, + ) + + private val reasonOfDeath = FormElement( + id = 53, + inputType = DROPDOWN, + title = context.getString(R.string.reason_for_death), + arrayId = R.array.reason_of_death_array, + entries = resources.getStringArray(R.array.reason_of_death_array), + required = true + ) + + + private val placeOfDeath = FormElement( + id = 54, + inputType = DROPDOWN, + title = context.getString(R.string.place_of_death), + arrayId = R.array.death_place_array, + entries = resources.getStringArray(R.array.death_place_array), + required = true, + ) + + private val otherPlaceOfDeath = FormElement( + id = 55, + inputType = EDIT_TEXT, + title = context.getString(R.string.other_place_of_death), + required = true, + hasDependants = true, + ) + private val deathRemoveList by lazy { listOf( @@ -1462,68 +1835,217 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context if (whoConductedDelivery.value == whoConductedDelivery.entries!!.last()) list.add( list.indexOf(whoConductedDelivery) + 1, otherWhoConductedDelivery ) - if (complicationsDuringDelivery.value == complicationsDuringDelivery.entries!![4]) list.removeAll( - deathRemoveList - ) + if (complicationsDuringDelivery.value == complicationsDuringDelivery.entries!![4]) deathRemoveList.forEach { list.remove(it) } setUpPage(list) } + + override suspend fun handleListOnValueChanged(formId: Int, index: Int): Int { return when (formId) { + + firstName.id -> { validateEmptyOnEditText(firstName) - validateAllCapsOrSpaceOnEditText(firstName) + validateAllCapsOrSpaceOnEditTextWithHindiEnabled(firstName) } - lastName.id -> { - validateAllCapsOrSpaceOnEditText(lastName) + beneficiaryStatus.id -> { + val isDeath = beneficiaryStatus.value == BenStatus.Death.name + + if (isDeath) { + dateOfDeath.min = getMinDateFromRegistration(dateOfReg.value!!) + val gender = gender.value + val age = agePopup.value + val showMaternal = shouldShowMaternalDeath(gender, age) + reasonOfDeath.entries = if (showMaternal) { + resources.getStringArray(R.array.reason_of_death_array_with_maternal) + } else { + resources.getStringArray(R.array.reason_of_death_array) + } + } + + return triggerDependants( + source = beneficiaryStatus, + passedIndex = if (isDeath) 1 else 0, + triggerIndex = 1, + target = if (isDeath) { + listOf(dateOfDeath, timeOfDeath, reasonOfDeath, placeOfDeath) + } else { + listOf( + dateOfDeath, + timeOfDeath, + reasonOfDeath, + placeOfDeath, + otherPlaceOfDeath + ) + } + ) } - agePopup.id -> { - assignValuesToAgeAndAgeUnitFromDob( - getLongFromDate(agePopup.value), -// age, -// ageUnit, - ageAtMarriage, - timeStampDateOfMarriageFromSpouse + + placeOfDeath.id -> { + val index = placeOfDeath.entries?.indexOf(placeOfDeath.value).takeIf { it!! >= 0 } + ?: return -1 + val triggerIndex = 8 + return triggerDependants( + source = placeOfDeath, + passedIndex = index, + triggerIndex = triggerIndex, + target = otherPlaceOfDeath ) - handleForAgeDob(formId = agePopup.id) + } + lastName.id -> { + // validateAllCapsOrSpaceOnEditText(lastName) + validateEmptyOnEditText(lastName) + validateAllCapsOrSpaceOnEditTextWithHindiEnabled(lastName) } -// ageUnit.id, age.id -> { -// if (age.value.isNullOrEmpty() || ageUnit.value == null) { -// validateEmptyOnEditText(age) -// validateEmptyOnEditText(ageUnit) -// return -1 -// } -// handleForAgeDob(formId = age.id) -// } + agePopup.id -> { +// assignValuesToAgeAndAgeUnitFromDob( +// getLongFromDate(agePopup.value), +// ageAtMarriage, +// timeStampDateOfMarriageFromSpouse +// ) + + if (benIfDataExist != null || !isAddSpouse) { + if (gender.value != null) { + // Gender already set (editing OR add member with pre-selected relation) + // Preserve gender, maritalStatus, relationToHead + // reproductiveStatus will be handled in updateReproductiveOptionsBasedOnAgeGender + + val genderIndex = when (gender.value) { + gender.entries?.get(0) -> 0 // Male + gender.entries?.get(1) -> 1 // Female + else -> 2 // Transgender/Other + } + + maritalStatus.entries = when (genderIndex) { + 1 -> maritalStatusFemale + else -> maritalStatusMale + } + maritalStatus.arrayId = when (genderIndex) { + 1 -> R.array.nbr_marital_status_female_array + else -> R.array.nbr_marital_status_male_array + } + relationToHead.entries = when (genderIndex) { + 0 -> relationToHeadListMale + 1 -> relationToHeadListFemale + else -> relationToHeadListDefault + } + relationToHead.arrayId = when (genderIndex) { + 0 -> R.array.nbr_relationship_to_head_male + 1 -> R.array.nbr_relationship_to_head_female + else -> R.array.nbr_relationship_to_head + } + } else { + // Gender not set yet: reset all values + gender.value = null + relationToHead.value = null + maritalStatus.value = null + reproductiveStatus.value = null + + maritalStatus.inputType = DROPDOWN + reproductiveStatus.inputType = DROPDOWN + relationToHead.inputType = DROPDOWN + + maritalStatus.entries = when (index) { + 1 -> maritalStatusFemale + else -> maritalStatusMale + } + maritalStatus.arrayId = when (index) { + 1 -> R.array.nbr_marital_status_female_array + else -> R.array.nbr_marital_status_male_array + } + relationToHead.entries = when (index) { + 0 -> relationToHeadListMale + 1 -> relationToHeadListFemale + else -> relationToHeadListDefault + } + relationToHead.arrayId = when (index) { + 0 -> R.array.nbr_relationship_to_head_male + 1 -> R.array.nbr_relationship_to_head_female + else -> R.array.nbr_relationship_to_head + } + } + } + + if (isAddSpouse) { + ageAtMarriage.max = getAgeFromDob(getLongFromDate(agePopup.value)).toLong() + ageAtMarriage.value = calculateAgeAtMarriage(getLongFromDate(agePopup.value), timeStampDateOfMarriageFromSpouse)?.toString() + ?: Konstants.minAgeForMarriage.toString() + dateOfMarriage.value = getDateFromLong( + timeStampDateOfMarriageFromSpouse ?: 0 + ) + triggerDependants( + source = agePopup, + addItems = listOf(ageAtMarriage), + removeItems = emptyList(), + position = 13 + ) + + } + + try { + return updateReproductiveOptionsBasedOnAgeGender(formId = agePopup.id) + } catch (e: Exception) { + e.printStackTrace() + return 1 + } + + } ageAtMarriage.id -> { - (getAgeFromDob(getLongFromDate(agePopup.value))).takeIf { it > 0 && !ageAtMarriage.value.isNullOrEmpty() } - ?.toInt()?.let { + val dobMillis = getLongFromDate(agePopup.value) + val currentAge = getAgeFromDob(dobMillis) + + currentAge.takeIf { it > 0 && !ageAtMarriage.value.isNullOrEmpty() } + ?.let { + validateEmptyOnEditText(ageAtMarriage) + ageAtMarriage.max = currentAge.toLong() validateIntMinMax(ageAtMarriage) - if (it == ageAtMarriage.value?.toInt()) { - val cal = Calendar.getInstance() - dateOfMarriage.max = cal.timeInMillis - cal.add(Calendar.YEAR, -1) - dateOfMarriage.min = cal.timeInMillis + val enteredAgeAtMarriage = ageAtMarriage.value!!.toIntOrNull() ?: return@let + + val dobCal = Calendar.getInstance() + dobCal.timeInMillis = dobMillis + + val birthYear = dobCal.get(Calendar.YEAR) + + val marriageYear = birthYear + enteredAgeAtMarriage + + + val marriageCal = Calendar.getInstance().apply { + timeInMillis = dobMillis + set(Calendar.YEAR, marriageYear) } + + + dateOfMarriage.value = getDateFromLong(marriageCal.timeInMillis) + + dateOfMarriage.max = Calendar.getInstance().timeInMillis + + dateOfMarriage.min = marriageCal.timeInMillis + triggerDependants( source = ageAtMarriage, - passedIndex = ageAtMarriage.value!!.toInt(), + passedIndex = it, triggerIndex = it, target = dateOfMarriage ) } ?: -1 + + return 0 } childRegisteredAtSchool.id -> { + if ((getAgeFromDob(getLongFromDate(agePopup.value))) in 3..6) { + typeOfSchool.required = true + } triggerDependants( source = childRegisteredAtSchool, passedIndex = index, @@ -1535,6 +2057,12 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context gender.id -> { relationToHead.value = null maritalStatus.value = null + reproductiveStatus.value = null + + maritalStatus.inputType = DROPDOWN + reproductiveStatus.inputType = DROPDOWN + relationToHead.inputType = DROPDOWN + maritalStatus.entries = when (index) { 1 -> maritalStatusFemale else -> maritalStatusMale @@ -1543,6 +2071,7 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context maritalStatus.arrayId = when (index) { 1 -> R.array.nbr_marital_status_female_array else -> R.array.nbr_marital_status_male_array + } relationToHead.entries = when (index) { @@ -1550,12 +2079,26 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context 1 -> relationToHeadListFemale else -> relationToHeadListDefault } - val listChanged = if (hasThirdPage()) triggerDependants( - source = rchId, - addItems = listOf(reproductiveStatus), - removeItems = emptyList(), - position = -2 - ) else { + + relationToHead.arrayId = when (index) { + 0 -> R.array.nbr_relationship_to_head_male + 1 -> R.array.nbr_relationship_to_head_female + else -> R.array.nbr_relationship_to_head + } + val listChanged = if (hasThirdPage()) { + + Log.e("ThirdPage","isThird") + updateReproductiveOptionsBasedOnAgeGender(formId = gender.id) + triggerDependants( + source = rchId, + addItems = listOf(reproductiveStatus), + removeItems = emptyList(), + position = -2 + ) + } else { + fatherName.required = false + motherName.required = false + triggerDependants( source = rchId, removeItems = listOf(reproductiveStatus), @@ -1577,43 +2120,48 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context if (index == 0 && isBenParentOfHoF()) { maritalStatus.errorText = "Parents cannot be unmarried!" } + + + when (maritalStatus.value) { maritalStatus.entries!![0] -> { - fatherName.required = true - motherName.required = true + fatherName.required = false + motherName.required = false + updateReproductiveOptionsBasedOnAgeGender(formId = maritalStatus.id) return triggerDependants( source = maritalStatus, - addItems = emptyList(),//listOf(fatherName, motherName), + addItems = emptyList(), removeItems = listOf( spouseName, husbandName, wifeName, ageAtMarriage, + haveChildren, ) ) } maritalStatus.entries!![1] -> { - if (gender.value == gender.entries!![1]) { - fatherName.required = false - motherName.required = false - } else { - fatherName.required = true - motherName.required = true - } + fatherName.required = false + motherName.required = false husbandName.required = true wifeName.required = true + wifeName.allCaps = true + husbandName.inputType = EDIT_TEXT + wifeName.inputType = EDIT_TEXT + updateReproductiveOptionsBasedOnAgeGender(formId = maritalStatus.id) return triggerDependants( - source = maritalStatus, addItems = when (gender.value) { + source = motherName, addItems = when (gender.value) { gender.entries!![0] -> listOf(wifeName, ageAtMarriage) - gender.entries!![1] -> listOf(husbandName, ageAtMarriage) + gender.entries!![1] -> listOf(husbandName, ageAtMarriage, haveChildren) else -> listOf(spouseName, ageAtMarriage) }, removeItems = listOf( wifeName, husbandName, spouseName, - ageAtMarriage + ageAtMarriage, + haveChildren ) ).also { if (relationToHead.value == relationToHead.entries!![0]) { @@ -1628,29 +2176,35 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context else -> { husbandName.required = maritalStatus.value != maritalStatus.entries!![2] wifeName.required = maritalStatus.value != maritalStatus.entries!![2] - fatherName.required = true - motherName.required = true -// ().let { -//// wifeName.required = it -//// husbandName.required = it -// spouseName.required = it -// } + wifeName.allCaps = true + fatherName.required = false + motherName.required = false + updateReproductiveOptionsBasedOnAgeGender(formId = maritalStatus.id) return triggerDependants( - source = maritalStatus, addItems = when (gender.value) { + source = motherName, addItems = when (gender.value) { gender.entries!![0] -> listOf(wifeName, ageAtMarriage) - gender.entries!![1] -> listOf(husbandName, ageAtMarriage) + gender.entries!![1] -> listOf(husbandName, ageAtMarriage, haveChildren) else -> listOf(spouseName, ageAtMarriage) }, removeItems = listOf( wifeName, husbandName, spouseName, - ageAtMarriage + ageAtMarriage, + haveChildren ) ) } } } + haveChildren.id -> { + if (haveChildren.entries != null) { + val yesIndex = 0 + val current = haveChildren.getPosition() + setIsAddingChildren(current == yesIndex) + } + 0 + } otherRelationToHead.id -> { validateEmptyOnEditText(otherRelationToHead) } @@ -1661,27 +2215,57 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context fatherName.id -> { validateEmptyOnEditText(fatherName) - validateAllCapsOrSpaceOnEditText(fatherName) + validateAllCapsOrSpaceOnEditTextWithHindiEnabled(fatherName) } motherName.id -> { validateEmptyOnEditText(motherName) - validateAllCapsOrSpaceOnEditText(motherName) + validateAllCapsOrSpaceOnEditTextWithHindiEnabled(motherName) } husbandName.id -> { validateEmptyOnEditText(husbandName) - validateAllCapsOrSpaceOnEditText(husbandName) + validateAllCapsOrSpaceOnEditTextWithHindiEnabled(husbandName) } wifeName.id -> { validateEmptyOnEditText(wifeName) - validateAllCapsOrSpaceOnEditText(wifeName) + validateAllCapsOrSpaceOnEditTextWithHindiEnabled(wifeName) } spouseName.id -> { validateEmptyOnEditText(spouseName) - validateAllCapsOrSpaceOnEditText(spouseName) + validateAllCapsOrSpaceOnEditTextWithHindiEnabled(spouseName) + } + + tempraryContactNo.id -> { + if (!isMitaninVariant) { + validateEmptyOnEditText(tempraryContactNo) + validateMobileNumberOnEditText(tempraryContactNo) + if (tempraryContactNo.value!!.isEmpty()) { + triggerforHide( + source = tempraryContactNoBelongsto, + passedIndex = index, + triggerIndex = index, + target = sendOtpBtn + ) + } else if (tempraryContactNo.value!!.length >= 10) { + triggerDependants( + source = tempraryContactNoBelongsto, + passedIndex = index, + triggerIndex = index, + target = sendOtpBtn + ) + } else { + triggerforHide( + source = tempraryContactNoBelongsto, + passedIndex = index, + triggerIndex = index, + target = sendOtpBtn + ) + } + } + -1 } contactNumber.id -> { @@ -1689,6 +2273,18 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context validateMobileNumberOnEditText(contactNumber) } + sendOtpBtn.id -> { + if (!isMitaninVariant && sendOtpBtn.isEnabled) { + triggerDependants( + source = sendOtpBtn, + passedIndex = index, + triggerIndex = index, + target = otpField + ) + } + return 0 + } + mobileNoOfRelation.id -> { contactNumber.value = null when (index) { @@ -1709,7 +2305,7 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context triggerDependants( source = mobileNoOfRelation, removeItems = listOf(otherMobileNoOfRelation), - addItems = emptyList(),//listOf(contactNumber) + addItems = emptyList(), ) } @@ -1717,7 +2313,7 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context val ret = triggerDependants( source = mobileNoOfRelation, - addItems = emptyList(),// listOf(contactNumber), + addItems = emptyList(), removeItems = listOf(otherMobileNoOfRelation) ) contactNumber.errorText = null @@ -1730,7 +2326,7 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context contactNumber.errorText = null triggerDependants( source = mobileNoOfRelation, - removeItems = emptyList(),//listOf(contactNumber), + removeItems = emptyList(), addItems = listOf(otherMobileNoOfRelation) ) } @@ -1755,12 +2351,6 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context otherReligion.id -> validateEmptyOnEditText(otherReligion) rchId.id -> validateRchIdOnEditText(rchId) - -// hasAadharNo.id -> triggerDependants( -// source = hasAadharNo, passedIndex = index, triggerIndex = 0, target = aadharNo -// ) -// -// aadharNo.id -> validateAadharNoOnEditText(aadharNo) birthCertificateNumber.id -> validateNoAlphabetSpaceOnEditText(birthCertificateNumber) else -> -1 @@ -1769,120 +2359,287 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context private fun handleForAgeDob(formId: Int): Int { if (formId == agePopup.id) { -// when (ageUnit.value) { -// ageUnit.entries?.get(0) -> { -// age.min = 0 -// age.max = 31 -// } -// -// ageUnit.entries?.get(1) -> { -// age.min = 1 -// age.max = 11 -// } -// -// ageUnit.entries?.get(2) -> { -// age.min = minAgeYear.toLong() -// age.max = maxAgeYear.toLong() -// } -// -// else -> return -1 -// } - -// validateIntMinMax(age) if (agePopup.errorText == null) { ageAtMarriage.value = null val ageAtMarriageMax = if (isBenParentOfHoF()) - getAgeFromDob(getLongFromDate(agePopup.value)) - getAgeFromDob( - hof!!.dob - ) else getAgeFromDob(getLongFromDate(agePopup.value)) + getAgeFromDob(getLongFromDate(agePopup.value)) - (hof?.dob?.let { + getAgeFromDob( + it + ) + } ?: 0) + else + getAgeFromDob(getLongFromDate(agePopup.value)) ageAtMarriage.max = ageAtMarriageMax.toLong() - } -// when (ageUnit.value) { -// ageUnit.entries?.get(2) -> { -// ageAtMarriage.value = null -// ageAtMarriage.max = -// if (isBenParentOfHoF()) -// age.value!!.toLong() - getAgeFromDob( -// hof!!.dob -// ) else age.value!!.toLong() -// cal.add( -// Calendar.YEAR, -1 * age.value!!.toInt() -// ) + val age = getAgeFromDob(getLongFromDate(agePopup.value)) + val genderIsFemale = gender.value == gender.entries?.get(1) + + if (benIfDataExist == null) { + + reproductiveStatus.inputType = DROPDOWN + + when { + + !genderIsFemale -> { + reproductiveStatus.entries = emptyList().toTypedArray() + } + + age in 15..19 -> when (maritalStatus.value) { + maritalStatus.entries!![0] -> { + reproductiveStatus.entries = resources.getStringArray(R.array.nbr_reproductive_status_array1) + reproductiveStatus.arrayId = R.array.nbr_reproductive_status_array1 + } + maritalStatus.entries!![1] -> { + reproductiveStatus.entries = resources.getStringArray(R.array.nbr_reproductive_status_array2) + reproductiveStatus.arrayId = R.array.nbr_reproductive_status_array2 + } + else -> { + reproductiveStatus.entries = resources.getStringArray(R.array.nbr_reproductive_status_array2) + reproductiveStatus.arrayId = R.array.nbr_reproductive_status_array2 + } + } + + age in 20..49 -> when (maritalStatus.value) { + maritalStatus.entries!![0] -> { + reproductiveStatus.entries = resources.getStringArray(R.array.nbr_reproductive_status_array3) + reproductiveStatus.arrayId = R.array.nbr_reproductive_status_array3 + } + maritalStatus.entries!![1] -> { + reproductiveStatus.entries = resources.getStringArray(R.array.nbr_reproductive_status_array4) + reproductiveStatus.arrayId = R.array.nbr_reproductive_status_array4 + } + else -> { + reproductiveStatus.entries = resources.getStringArray(R.array.nbr_reproductive_status_array4) + reproductiveStatus.arrayId = R.array.nbr_reproductive_status_array4 + } + } + + age >= 50 -> { + reproductiveStatus.entries = resources.getStringArray(R.array.nbr_reproductive_status_array5) + reproductiveStatus.arrayId = R.array.nbr_reproductive_status_array5 + } + + else -> reproductiveStatus.entries = emptyList().toTypedArray() + + } + +// val updatedReproductiveOptions = when { +// !genderIsFemale -> emptyList() +// age in 15..19 -> when (maritalStatus.value) { +// maritalStatus.entries!![0] -> { +// listOf( +// "Adolescent Girl" +// ) +// } +// maritalStatus.entries!![1] -> { +// listOf( +// "Adolescent Girl", "Eligible Couple", "Pregnant Woman", +// "Postnatal Mother", "Permanently Sterilised" +// ) +// } +// else -> { +// listOf( +// "Adolescent Girl", "Eligible Couple", "Pregnant Woman", +// "Postnatal Mother", "Permanently Sterilised" +// ) +// } // } // -// ageUnit.entries?.get(1) -> { -// cal.add( -// Calendar.MONTH, -1 * age.value!!.toInt() -// ) +// age in 20..49 -> when (maritalStatus.value) { +// maritalStatus.entries!![0] -> { +// listOf( +// "Not Applicable" +// ) +// } +// maritalStatus.entries!![1] -> { +// listOf( +// "Eligible Couple", "Pregnant Woman", +// "Postnatal Mother", "Permanently Sterilised" +// ) +// } +// else -> { +// listOf( +// "Eligible Couple", "Pregnant Woman", +// "Postnatal Mother", "Permanently Sterilised" +// ) +// } // } // -// ageUnit.entries?.get(0) -> { -// cal.add( -// Calendar.DAY_OF_YEAR, -1 * age.value!!.toInt() -// ) - } +// age >= 50 -> listOf("Elderly Woman") +// else -> emptyList() // } -// val year = cal.get(Calendar.YEAR) -// val month = cal.get(Calendar.MONTH) + 1 -// val day = cal.get(Calendar.DAY_OF_MONTH) -// val newDob = -// "${if (day > 9) day else "0$day"}-${if (month > 9) month else "0$month"}-$year" -// if (dob.value != newDob) { -// dob.value = newDob -// dob.errorText = null +// +// reproductiveStatus.entries = updatedReproductiveOptions.toTypedArray() + + reproductiveStatus.value = null + if (reproductiveStatus.entries?.size == 1) { + reproductiveStatus.value = reproductiveStatus.entries?.get(0) + } + + } else { + + val saved = benIfDataExist + if (saved != null) { + val storedEnglishStatus = saved.genDetails?.reproductiveStatus + reproductiveStatus.value = if (storedEnglishStatus != null) { + listOf( + R.array.nbr_reproductive_status_array1, + R.array.nbr_reproductive_status_array2, + R.array.nbr_reproductive_status_array3, + R.array.nbr_reproductive_status_array4, + R.array.nbr_reproductive_status_array5 + ).firstNotNullOfOrNull { getLocalValueInArray(it, storedEnglishStatus) } ?: storedEnglishStatus + } else null + } + reproductiveStatus.inputType = DROPDOWN + +// when (reproductiveStatus.value) { +// "Adolescent Girl" -> { +// agePopup.max = yearsAgo(15) +// agePopup.min = yearsAgo(19) +// } +// +// "Eligible Couple" -> { +// agePopup.max = yearsAgo(15) +// agePopup.min = yearsAgo(49) +// } +// +// "Pregnant Woman", "Postnatal Mother", "Permanently Sterilised" -> { +// agePopup.max = yearsAgo(20) +// agePopup.min = yearsAgo(49) +// } +// +// "Elderly Woman" -> { +// agePopup.max = yearsAgo(50) +// agePopup.min = yearsAgo(100) +// } // } -// } -// } + + when { + + !genderIsFemale -> { + reproductiveStatus.entries = emptyList().toTypedArray() + } + + age in 15..19 -> when (maritalStatus.value) { + maritalStatus.entries!![0] -> { + reproductiveStatus.entries = resources.getStringArray(R.array.nbr_reproductive_status_array1) + reproductiveStatus.arrayId = R.array.nbr_reproductive_status_array1 + } + maritalStatus.entries!![1] -> { + reproductiveStatus.entries = resources.getStringArray(R.array.nbr_reproductive_status_array2) + reproductiveStatus.arrayId = R.array.nbr_reproductive_status_array2 + } + else -> { + reproductiveStatus.entries = resources.getStringArray(R.array.nbr_reproductive_status_array2) + reproductiveStatus.arrayId = R.array.nbr_reproductive_status_array2 + } + } + + age in 20..49 -> when (maritalStatus.value) { + maritalStatus.entries!![0] -> { + reproductiveStatus.entries = resources.getStringArray(R.array.nbr_reproductive_status_array3) + reproductiveStatus.arrayId = R.array.nbr_reproductive_status_array3 + } + maritalStatus.entries!![1] -> { + reproductiveStatus.entries = resources.getStringArray(R.array.nbr_reproductive_status_array4) + reproductiveStatus.arrayId = R.array.nbr_reproductive_status_array4 + } + else -> { + reproductiveStatus.entries = resources.getStringArray(R.array.nbr_reproductive_status_array4) + reproductiveStatus.arrayId = R.array.nbr_reproductive_status_array4 + } + } + + age >= 50 -> { + reproductiveStatus.entries = resources.getStringArray(R.array.nbr_reproductive_status_array5) + reproductiveStatus.arrayId = R.array.nbr_reproductive_status_array5 + } + + else -> reproductiveStatus.entries = emptyList().toTypedArray() + + } + +// reproductiveStatus.entries = updatedReproductiveOptions.toTypedArray() + + } + } + val listChanged = if (hasThirdPage()) triggerDependants( source = rchId, addItems = listOf(reproductiveStatus), removeItems = emptyList(), position = -2 ) else { + fatherName.required = false + motherName.required = false triggerDependants( source = rchId, removeItems = listOf(reproductiveStatus), addItems = emptyList() ) } != -1 + val listChanged2 = triggerDependants( age = getAgeFromDob(getLongFromDate(agePopup.value)), -// ageUnit = ageUnit, ageTriggerRange = Range(4, 14), -// ageUnitTriggerIndex = 2, target = childRegisteredAtSchool, placeAfter = religion, targetSideEffect = listOf(typeOfSchool) ) != -1 + val listChanged3 = if (maritalStatus.inputType == TEXT_VIEW) -1 else { - if (getAgeFromDob(getLongFromDate(agePopup.value)) <= Konstants.maxAgeForAdolescent) triggerDependants( - source = rchId, - addItems = listOf(birthCertificateNumber, placeOfBirth), - removeItems = listOf( - husbandName, - wifeName, - spouseName, - ageAtMarriage, - dateOfMarriage, - maritalStatus - ), - position = -2 - ) else { + + if (getYearsFromDate(agePopup.value.toString()) <= Konstants.maxAgeForAdolescent) { + fatherName.required = false + motherName.required = false + + triggerDependants( + source = rchId, + addItems = listOf(birthCertificateNumber, placeOfBirth), + removeItems = listOf( + husbandName, + wifeName, + spouseName, + ageAtMarriage, + dateOfMarriage, + maritalStatus, + reproductiveStatus, + haveChildren + ), + position = -2 + ) + + } else { maritalStatus.value = null triggerDependants( source = gender, removeItems = listOf(birthCertificateNumber, placeOfBirth), addItems = listOf(maritalStatus) ) + if (gender.value == gender.entries!![1]) { + triggerDependants( + source = rchId, + removeItems = listOf(), + addItems = listOf(reproductiveStatus), + position = -2 + ) + + } else { + triggerDependants( + source = rchId, + removeItems = listOf(reproductiveStatus), + addItems = listOf() + ) + } } } != -1 val listChanged4 = if (maritalStatus.inputType == TEXT_VIEW) -1 else { - if (getAgeFromDob(getLongFromDate(agePopup.value)) <= Konstants.maxAgeForAdolescent || gender.value == gender.entries!![1]) { + if (getYearsFromDate(agePopup.value.toString()) <= Konstants.maxAgeForAdolescent || gender.value == gender.entries!![1]) { triggerDependants( source = religion, removeItems = emptyList(), @@ -1896,23 +2653,81 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context ) } } != -1 - return if (listChanged || listChanged2 || listChanged3 || listChanged4) - 1 - else - -1 + + return if (listChanged || listChanged2 || listChanged3 || listChanged4) 1 else -1 } - private fun isBenParentOfHoF() = - relationToHead.value == relationToHead.entries!![0] || relationToHead.value == relationToHead.entries!![1] + private fun getReproductiveStatusEnglishValue(): String { + val localized = reproductiveStatus.value?.trim() ?: return "" + return listOf( + R.array.nbr_reproductive_status_array1, + R.array.nbr_reproductive_status_array2, + R.array.nbr_reproductive_status_array3, + R.array.nbr_reproductive_status_array4, + R.array.nbr_reproductive_status_array5 + ).firstNotNullOfOrNull { getEnglishValueInArray(it, localized) } ?: localized + } + + private fun validateReproductiveStatusField(genderIsFemale: Boolean, age: Int): Int { + val reproEnglish = getReproductiveStatusEnglishValue() + if ((genderIsFemale && + age in 15..19 && + maritalStatus.value == maritalStatus.entries!![1] && + reproEnglish == "Adolescent Girl") || + (genderIsFemale && + age in 20..49 && + maritalStatus.value == maritalStatus.entries!![1] && + reproEnglish == "Not Applicable")) { + reproductiveStatus.inputType = DROPDOWN + + } +// reproductiveStatus.isEnabled = (genderIsFemale && +// age in 15..19 && +// maritalStatus.value == maritalStatus.entries!![1] && +// reproductiveStatus.value == "Adolescent Girl") || +// (genderIsFemale && +// age in 20..49 && +// maritalStatus.value == maritalStatus.entries!![1] && +// reproductiveStatus.value == "Not Applicable") + + return 1 + } + + private fun isBenParentOfHoF(): Boolean { + val value = relationToHead.value ?: return false + val parentRelations = setOf( + relationToHeadListDefault[0], // Mother + relationToHeadListDefault[1], // Father + relationToHeadListDefault[10], // Grand Father + relationToHeadListDefault[11], // Grand Mother + relationToHeadListDefault[12], // Father in Law + relationToHeadListDefault[13], // Mother in Law + ) + return value in parentRelations + } fun getIndexOfAgeAtMarriage() = getIndexOfElement(ageAtMarriage) fun getIndexOfContactNumber() = getIndexOfElement(contactNumber) fun getIndexOfMaritalStatus() = getIndexOfElement(maritalStatus) + fun getTempMobileNoStatus() = getIndexOfElement(tempraryContactNo) override fun mapValues(cacheModel: FormDataModel, pageNumber: Int) { (cacheModel as BenRegCache).let { ben -> // Page 001 + ben.isDeathValue = beneficiaryStatus.value + ben.isDeath = beneficiaryStatus.entries?.indexOf(beneficiaryStatus.value ?: "") == 1 + ben.dateOfDeath = dateOfDeath.value + ben.timeOfDeath = timeOfDeath.value + ben.reasonOfDeath = reasonOfDeath.value + ben.reasonOfDeathId = + reasonOfDeath.entries?.indexOf(reasonOfDeath.value ?: "")?.takeIf { it != -1 } ?: -1 + ben.placeOfDeath = placeOfDeath.value + ben.placeOfDeathId = + placeOfDeath.entries?.indexOf(placeOfDeath.value ?: "")?.takeIf { it != -1 } ?: -1 + ben.otherPlaceOfDeath = otherPlaceOfDeath.value + + ben.userImage = pic.value ben.regDate = getLongFromDate(dateOfReg.value!!) ben.firstName = firstName.value @@ -1921,22 +2736,8 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context ben.age = (getAgeFromDob(getLongFromDate(agePopup.value))) ben.ageUnitId = 3 ben.ageUnit = AgeUnit.YEARS -// ben.ageUnitId = when (ageUnit.value) { -// ageUnit.entries!![2] -> 3 -// ageUnit.entries!![1] -> 2 -// ageUnit.entries!![0] -> 1 -// else -> 0 -// } -// ben.ageUnit = when (ben.ageUnitId) { -// 3 -> AgeUnit.YEARS -// 2 -> AgeUnit.MONTHS -// 1 -> AgeUnit.DAYS -// else -> null -// } ben.isAdult = ben.ageUnit == AgeUnit.YEARS && ben.age >= 15 ben.isKid = !ben.isAdult - -// ben.registrationType = getTypeFromAge(ben.age, ben.ageUnit) ben.genderId = when (gender.value) { gender.entries!![0] -> 1 gender.entries!![1] -> 2 @@ -1958,8 +2759,11 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context getEnglishValueInArray(R.array.nbr_relationship_to_head_src, relationToHead.value) ben.familyHeadRelationOther = otherRelationToHead.value ben.mobileNoOfRelationId = if (isHoF) 1 else mobileNoOfRelation.getPosition() + ben.tempMobileNoOfRelationId = if (isMitaninVariant) 0 else if (isHoF) 1 else tempraryContactNoBelongsto.getPosition() ben.mobileNoOfRelation = mobileNoOfRelation.getEnglishStringFromPosition(ben.mobileNoOfRelationId) + /* ben.mobileNoOfRelation = + tempraryContactNoBelongsto.getEnglishStringFromPosition(ben.mobileNoOfRelationId)*/ ben.mobileOthers = otherMobileNoOfRelation.value ben.contactNumber = if (ben.mobileNoOfRelationId == 5) familyHeadPhoneNo!!.toLong() else contactNumber.value!!.toLong() @@ -1976,18 +2780,7 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context ben.kidDetails?.typeOfSchool = typeOfSchool.getEnglishStringFromPosition(typeOfSchool.getPosition()) ben.rchId = rchId.value -// ben.hasAadhar = hasAadharNo.value?.let { -// it == hasAadharNo.entries!!.first() -// }?.also { -// if (it) -// ben.hasAadharId = 1 -// else -// ben.hasAadharId = 2 -// if (it) -// ben.aadharNum = aadharNo.value?.let { -// "*".repeat(8) + it.takeLast(4) -// } -// } + ben.genDetails?.maritalStatusId = maritalStatus.getPosition() ben.genDetails?.maritalStatus = maritalStatus.getEnglishStringFromPosition(ben.genDetails?.maritalStatusId ?: 0) @@ -1996,26 +2789,39 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context ?: spouseName.value.takeIf { !it.isNullOrEmpty() } ben.genDetails?.ageAtMarriage = ageAtMarriage.value?.toInt() ?: 0 - ben.genDetails?.marriageDate = - timeStampDateOfMarriageFromSpouse.takeIf { it != null }?.also { - ben.genDetails?.ageAtMarriage = - (TimeUnit.MILLISECONDS.toDays(it - ben.dob) / 365).toInt() - } ?: run { - dateOfMarriage.value?.let { getLongFromDate(it) } - ?: run { - ben.genDetails?.ageAtMarriage?.takeIf { it > 0 }?.let { - getDoMFromDoR( - if (ben.genDetails?.ageAtMarriage == null) 0 else (ben.age - ben.genDetails!!.ageAtMarriage), - ben.regDate - ) - } - } - } + ben.genDetails?.marriageDate = calculateMarriageDate(ben.genDetails?.ageAtMarriage!!, ben.dob) +// ben.genDetails?.marriageDate = +// timeStampDateOfMarriageFromSpouse.takeIf { it != null }?.also { +// ben.genDetails?.ageAtMarriage = +// (TimeUnit.MILLISECONDS.toDays(it - ben.dob) / 365).toInt() +// } ?: run { +// dateOfMarriage.value?.let { getLongFromDate(it) } +// ?: run { +// ben.genDetails?.ageAtMarriage?.takeIf { it > 0 }?.let { +// getDoMFromDoR( +// if (ben.genDetails?.ageAtMarriage == null) 0 else (ben.age - ben.genDetails!!.ageAtMarriage), +// ben.regDate +// ) +// } +// } +// } ben.genDetails?.let { gen -> - gen.reproductiveStatusId = reproductiveStatus.getPosition() - gen.reproductiveStatus = - reproductiveStatus.getEnglishStringFromPosition(gen.reproductiveStatusId) + val selectedValue = getReproductiveStatusEnglishValue() + val reproductiveMap = mapOf( + "Eligible Couple" to 1, + "Pregnant Woman" to 2, + "Postnatal Mother" to 3, + "Elderly Woman" to 4, + "Adolescent Girl" to 5, + "Permanently Sterilised" to 6, + "Not Applicable" to 7 + ) + + val selectedId = reproductiveMap[selectedValue] ?: 0 + + gen.reproductiveStatusId = selectedId + gen.reproductiveStatus = selectedValue } ben.kidDetails?.birthCertificateNumber = @@ -2094,7 +2900,23 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context babyWeight.value?.takeIf { it.isNotEmpty() }?.toDouble() ?: 0.0 + ben.kidDetails?.birthCertificateFileBackView = fileUploadBack.value + ben.kidDetails?.birthCertificateFileFrontView = fileUploadFront.value ben.isDraft = false + ben.isConsent = isOtpVerified + ben.isSpouseAdded = if (isAddSppouse == 1) { + true + } else { + when (ben.familyHeadRelationPosition) { + 5 -> true + 6 -> true + else -> false + } + } + ben.isChildrenAdded = false + ben.isMarried = (maritalStatus.getPosition() == 2) + ben.doYouHavechildren = (haveChildren.getPosition() == 1) + } } @@ -2105,14 +2927,418 @@ class BenRegFormDataset(context: Context, language: Languages) : Dataset(context } fun setImageUriToFormElement(lastImageFormId: Int, dpUri: Uri) { - when (lastImageFormId) { - pic.id -> { - pic.value = dpUri.toString() - pic.errorText = null + + if (lastImageFormId == 46) { + fileUploadFront.value = dpUri.toString() + fileUploadFront.errorText = null + + } else if (lastImageFormId == 47){ + fileUploadBack.value = dpUri.toString() + fileUploadBack.errorText = null + + } else { + + pic.value = dpUri.toString() + pic.errorText = null + + + } + + + } + + + fun yearsAgo(years: Int): Long { + return Calendar.getInstance().apply { + val today = Calendar.getInstance().setToStartOfTheDay() + timeInMillis = today.timeInMillis + add(Calendar.YEAR, -years) + }.timeInMillis + } + + private fun updateReproductiveOptionsBasedOnAgeGender(formId: Int): Int { + val age = getAgeFromDob(getLongFromDate(agePopup.value)) + val genderIsFemale = gender.value == gender.entries?.get(1) + + if (benIfDataExist == null) { + if (genderIsFemale) { + reproductiveStatus.inputType = DROPDOWN + } +// reproductiveStatus.isEnabled = genderIsFemale + + when { + + !genderIsFemale -> { + reproductiveStatus.entries = emptyList().toTypedArray() + } + + age in 15..19 -> when (maritalStatus.value) { + maritalStatus.entries!![0] -> { + reproductiveStatus.entries = resources.getStringArray(R.array.nbr_reproductive_status_array1) + reproductiveStatus.arrayId = R.array.nbr_reproductive_status_array1 + } + maritalStatus.entries!![1] -> { + reproductiveStatus.entries = resources.getStringArray(R.array.nbr_reproductive_status_array2) + reproductiveStatus.arrayId = R.array.nbr_reproductive_status_array2 + } + else -> { + reproductiveStatus.entries = resources.getStringArray(R.array.nbr_reproductive_status_array2) + reproductiveStatus.arrayId = R.array.nbr_reproductive_status_array2 + } + } + + age in 20..49 -> when (maritalStatus.value) { + maritalStatus.entries!![0] -> { + reproductiveStatus.entries = resources.getStringArray(R.array.nbr_reproductive_status_array3) + reproductiveStatus.arrayId = R.array.nbr_reproductive_status_array3 + } + maritalStatus.entries!![1] -> { + reproductiveStatus.entries = resources.getStringArray(R.array.nbr_reproductive_status_array4) + reproductiveStatus.arrayId = R.array.nbr_reproductive_status_array4 + } + else -> { + reproductiveStatus.entries = resources.getStringArray(R.array.nbr_reproductive_status_array4) + reproductiveStatus.arrayId = R.array.nbr_reproductive_status_array4 + } + } + + age >= 50 -> { + reproductiveStatus.entries = resources.getStringArray(R.array.nbr_reproductive_status_array5) + reproductiveStatus.arrayId = R.array.nbr_reproductive_status_array5 + } + + else -> reproductiveStatus.entries = emptyList().toTypedArray() + + } + + val oldReproductiveValue = reproductiveStatus.value + reproductiveStatus.value = null + if (reproductiveStatus.entries?.size == 1) { + reproductiveStatus.value = reproductiveStatus.entries?.get(0) + } else if (oldReproductiveValue != null && reproductiveStatus.entries?.contains(oldReproductiveValue) == true) { + reproductiveStatus.value = oldReproductiveValue } + if (maritalStatus.value != null) { + reproductiveStatus.inputType = DROPDOWN +// reproductiveStatus.isEnabled = true + } + } else { + +// val updatedReproductiveOptions = when { +// !genderIsFemale -> emptyList() +// age in 15..19 -> when (maritalStatus.value) { +// maritalStatus.entries!![0] -> { +// listOf( +// "Adolescent Girl" +// ) +// } +// maritalStatus.entries!![1] -> { +// listOf( +// "Adolescent Girl", "Eligible Couple", "Pregnant Woman", +// "Postnatal Mother", "Permanently Sterilised" +// ) +// } +// else -> { +// listOf( +// "Adolescent Girl", "Eligible Couple", "Pregnant Woman", +// "Postnatal Mother", "Permanently Sterilised" +// ) +// } +// } +// +// age in 20..49 -> when (maritalStatus.value) { +// maritalStatus.entries!![0] -> { +// listOf( +// "Not Applicable" +// ) +// } +// maritalStatus.entries!![1] -> { +// listOf( +// "Eligible Couple", "Pregnant Woman", +// "Postnatal Mother", "Permanently Sterilised" +// ) +// } +// else -> { +// listOf( +// "Eligible Couple", "Pregnant Woman", +// "Postnatal Mother", "Permanently Sterilised" +// ) +// } +// } +// +// age >= 50 -> listOf("Elderly Woman") +// else -> emptyList() +// } + + when { + + !genderIsFemale -> { + reproductiveStatus.entries = emptyList().toTypedArray() + } + + age in 15..19 -> when (maritalStatus.value) { + maritalStatus.entries!![0] -> { + reproductiveStatus.entries = resources.getStringArray(R.array.nbr_reproductive_status_array1) + reproductiveStatus.arrayId = R.array.nbr_reproductive_status_array1 + } + maritalStatus.entries!![1] -> { + reproductiveStatus.entries = resources.getStringArray(R.array.nbr_reproductive_status_array2) + reproductiveStatus.arrayId = R.array.nbr_reproductive_status_array2 + } + else -> { + reproductiveStatus.entries = resources.getStringArray(R.array.nbr_reproductive_status_array2) + reproductiveStatus.arrayId = R.array.nbr_reproductive_status_array2 + } + } + + age in 20..49 -> when (maritalStatus.value) { + maritalStatus.entries!![0] -> { + reproductiveStatus.entries = resources.getStringArray(R.array.nbr_reproductive_status_array3) + reproductiveStatus.arrayId = R.array.nbr_reproductive_status_array3 + } + maritalStatus.entries!![1] -> { + reproductiveStatus.entries = resources.getStringArray(R.array.nbr_reproductive_status_array4) + reproductiveStatus.arrayId = R.array.nbr_reproductive_status_array4 + } + else -> { + reproductiveStatus.entries = resources.getStringArray(R.array.nbr_reproductive_status_array4) + reproductiveStatus.arrayId = R.array.nbr_reproductive_status_array4 + } + } + + age >= 50 -> { + reproductiveStatus.entries = resources.getStringArray(R.array.nbr_reproductive_status_array5) + reproductiveStatus.arrayId = R.array.nbr_reproductive_status_array5 + } + + else -> reproductiveStatus.entries = emptyList().toTypedArray() + + } + + val oldReproductiveValue2 = reproductiveStatus.value + reproductiveStatus.value = null +// reproductiveStatus.entries = updatedReproductiveOptions.toTypedArray() + if (reproductiveStatus.entries?.size == 1) { + reproductiveStatus.value = reproductiveStatus.entries?.get(0) + } else if (oldReproductiveValue2 != null && reproductiveStatus.entries?.contains(oldReproductiveValue2) == true) { + reproductiveStatus.value = oldReproductiveValue2 + } + validateReproductiveStatusField(genderIsFemale, age) + + // Set DOB constraints based on existing reproductive status +// when (reproductiveStatus.value) { +// "Adolescent Girl" -> { +// agePopup.max = yearsAgo(15) +// agePopup.min = yearsAgo(19) +// } +// +// "Eligible Couple", "Pregnant Woman", "Postnatal Mother", "Permanently Sterilised" -> { +// agePopup.max = yearsAgo(20) +// agePopup.min = yearsAgo(49) +// } +// +// "Elderly Woman" -> { +// agePopup.max = yearsAgo(50) +// agePopup.min = yearsAgo(100) +// } +// } + } + + if (formId == agePopup.id) { + + val listChanged = if (hasThirdPage()) triggerDependants( + source = rchId, + addItems = listOf(reproductiveStatus), + removeItems = emptyList(), + position = -2 + ) else { + fatherName.required = false + motherName.required = false + triggerDependants( + source = rchId, + removeItems = listOf(reproductiveStatus), + addItems = emptyList() + ) + } != -1 + + val listChanged2 = triggerDependants( + age = getAgeFromDob(getLongFromDate(agePopup.value)), + ageTriggerRange = Range(4, 14), + target = childRegisteredAtSchool, + placeAfter = religion, + targetSideEffect = listOf(typeOfSchool) + ) != -1 + + val listChanged3 = + if (maritalStatus.inputType == TEXT_VIEW) -1 else { + + if (getYearsFromDate(agePopup.value.toString()) <= Konstants.maxAgeForAdolescent) { + fatherName.required = false + motherName.required = false + + triggerDependants( + source = rchId, + addItems = listOf(birthCertificateNumber, placeOfBirth), + removeItems = listOf( + husbandName, + wifeName, + spouseName, + ageAtMarriage, + dateOfMarriage, + maritalStatus, + reproductiveStatus, + haveChildren + ), + position = -2 + ) + + } else { + if (gender.value == null) { + maritalStatus.value = null + } + triggerDependants( + source = gender, + removeItems = listOf(birthCertificateNumber, placeOfBirth), + addItems = listOf(maritalStatus) + ) + if (gender.value == gender.entries!![1]) { + triggerDependants( + source = rchId, + removeItems = listOf(), + addItems = listOf(reproductiveStatus), + position = -2 + ) + + } else { + triggerDependants( + source = rchId, + removeItems = listOf(reproductiveStatus), + addItems = listOf() + ) + } + } + } != -1 + + val listChanged4 = + if (maritalStatus.inputType == TEXT_VIEW) -1 else { + if (getYearsFromDate(agePopup.value.toString()) <= Konstants.maxAgeForAdolescent || gender.value == gender.entries!![1]) { + triggerDependants( + source = religion, + removeItems = emptyList(), + addItems = listOf(rchId) + ) + } else { + triggerDependants( + source = religion, + removeItems = listOf(rchId), + addItems = emptyList() + ) + } + } != -1 + + return if (listChanged || listChanged2 || listChanged3 || listChanged4) 1 else -1 + + } else { + + val listChanged = if (hasThirdPage()) triggerDependants( + source = rchId, + addItems = listOf(reproductiveStatus), + removeItems = emptyList(), + position = -2 + ) else { + // fatherName.required = true + // motherName.required = true + triggerDependants( + source = rchId, + removeItems = listOf(reproductiveStatus), + addItems = emptyList() + ) + } + + return listChanged + } } + private fun shouldShowMaternalDeath(gender: String?, dob: String?): Boolean { + Log.v("valuesOfData", "dob:$dob") + + if (gender != "Female") return false + val age = dob?.let { dobString -> + val sdf = SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH) + try { + val date = sdf.parse(dobString) + val dobInMillis = date?.time ?: return false + getAgeFromDob(dobInMillis) + } catch (e: Exception) { + return false + } + } ?: return false + return age in 15..49 + } + + private fun getMinDateFromRegistration(registrationDate: String): Long { + return try { + + val sdf = SimpleDateFormat("dd-MM-yyyy", Locale.US) + val date = sdf.parse(registrationDate) + date?.time ?: 0L + } catch (e: Exception) { + 0L + } + } + + + private fun initializeDeathFields( + list: MutableList, + saved: BenRegCache?, + lastNameIndex: Int + ) { + list.add(lastNameIndex + 1, beneficiaryStatus) + if (saved?.isDeath == true) { + list.add(list.indexOf(beneficiaryStatus) + 1, dateOfDeath) + list.add(list.indexOf(dateOfDeath) + 1, timeOfDeath) + list.add(list.indexOf(timeOfDeath) + 1, reasonOfDeath) + list.add(list.indexOf(reasonOfDeath) + 1, placeOfDeath) + placeOfDeath.entries?.indexOf(saved.placeOfDeath)?.takeIf { it >= 0 }?.let { index -> + if (index == 8) { + list.add(list.indexOf(placeOfDeath) + 1, otherPlaceOfDeath) + } + } + } + + beneficiaryStatus.value = when (saved?.isDeath) { + true -> BenStatus.Death.name + false -> BenStatus.Alive.name + null -> null + } + dateOfDeath.value = saved?.dateOfDeath + timeOfDeath.value = saved?.timeOfDeath + reasonOfDeath.value = saved?.reasonOfDeath + placeOfDeath.value = saved?.placeOfDeath + otherPlaceOfDeath.value = saved?.otherPlaceOfDeath + } + + fun mapValueToBen(ben: BenRegCache?): Boolean { + var isUpdated = false + val rchIdFromBen = ben?.rchId?.takeIf { it.isNotEmpty() }?.toLong() + val aadharNoFromBen = ben?.aadharNum?.takeIf { it.isNotEmpty() } + rchId.value?.takeIf { + it.isNotEmpty() + }?.toLong()?.let { + if (it != rchIdFromBen) { + ben?.rchId = it.toString() + isUpdated = true + } + } + + if (isUpdated) { + if (ben?.processed != "N") + ben?.processed = "U" + ben?.syncState = SyncState.UNSYNCED + } + return isUpdated + } } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/CDRFormDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/CDRFormDataset.kt index ca26d5422..87e769d94 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/configuration/CDRFormDataset.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/CDRFormDataset.kt @@ -1,34 +1,41 @@ package org.piramalswasthya.sakhi.configuration import android.content.Context -import android.widget.LinearLayout +import android.net.Uri +import org.piramalswasthya.sakhi.BuildConfig +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao import org.piramalswasthya.sakhi.helpers.Languages import org.piramalswasthya.sakhi.model.BenBasicCache import org.piramalswasthya.sakhi.model.BenRegCache import org.piramalswasthya.sakhi.model.CDRCache import org.piramalswasthya.sakhi.model.FormElement import org.piramalswasthya.sakhi.model.InputType +import timber.log.Timber +import java.text.SimpleDateFormat +import java.util.Locale class CDRFormDataset( - context: Context, currentLanguage: Languages + context: Context, currentLanguage: Languages, + val preferences: PreferenceDao ) : Dataset(context, currentLanguage) { private val childName = FormElement( id = 1, inputType = InputType.TEXT_VIEW, - title = "Name of the Child", + title = resources.getString(R.string.name_of_the_child), required = false ) private val dateOfBirth = FormElement( id = 2, inputType = InputType.TEXT_VIEW, - title = "Date of Birth", + title = resources.getString(R.string.date_of_birth), required = false ) private val age = FormElement( id = 3, inputType = InputType.TEXT_VIEW, - title = "Age", + title = resources.getString(R.string.age), required = false ) private val visitDate = FormElement( @@ -36,55 +43,55 @@ class CDRFormDataset( inputType = InputType.DATE_PICKER, min = 0L, max = System.currentTimeMillis(), - title = "Visit Date", + title = resources.getString(R.string.visit_date), required = true ) private val gender = FormElement( id = 5, inputType = InputType.TEXT_VIEW, - title = "Gender", + title = resources.getString(R.string.gender), required = false ) private val motherName = FormElement( id = 6, inputType = InputType.TEXT_VIEW, - title = "Mother’s Name", + title = resources.getString(R.string.mother_s_name), required = false ) private val fatherName = FormElement( id = 7, inputType = InputType.TEXT_VIEW, - title = "Father’s Name", + title =resources.getString(R.string.father_s_name), required = false ) private val address = FormElement( id = 8, inputType = InputType.TEXT_VIEW, - title = "Address", + title = resources.getString(R.string.address), required = false ) private val houseNumber = FormElement( id = 9, - inputType = InputType.EDIT_TEXT, - title = "House number", + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.house_number), required = false ) private val mohalla = FormElement( id = 10, - inputType = InputType.EDIT_TEXT, - title = "Mohalla/Colony ", + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.mohalla_colony), required = false ) private val landmarks = FormElement( id = 11, - inputType = InputType.EDIT_TEXT, - title = "Landmarks, if any ", + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.landmarks_if_any), required = false ) private val pincode = FormElement( id = 12, - inputType = InputType.EDIT_TEXT, - title = "Pincode", + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.pincode), etMaxLength = 6, min = 100000, max = 999999, @@ -94,7 +101,7 @@ class CDRFormDataset( private val landline = FormElement( id = 13, inputType = InputType.EDIT_TEXT, - title = "Landline", + title = resources.getString(R.string.landline), etMaxLength = 12, etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, required = false @@ -102,13 +109,13 @@ class CDRFormDataset( private val mobileNumber = FormElement( id = 14, inputType = InputType.TEXT_VIEW, - title = "Mobile number", + title = resources.getString(R.string.mobile_number), required = false ) private val dateOfDeath = FormElement( id = 15, - inputType = InputType.DATE_PICKER, - title = "Date of death", + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.date_of_death), min = 0L, max = System.currentTimeMillis(), required = true @@ -116,38 +123,80 @@ class CDRFormDataset( private val timeOfDeath = FormElement( id = 16, inputType = InputType.TIME_PICKER, - title = "Time", + title = resources.getString(R.string.time), required = false ) private val placeOfDeath = FormElement( id = 17, - inputType = InputType.RADIO, - title = "Place of Death", + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.place_of_death), required = true, - orientation = LinearLayout.VERTICAL, - entries = arrayOf("Home", "Hospital", "In transit") ) private val firstInformant = FormElement( id = 18, inputType = InputType.TEXT_VIEW, - title = "Name of First Informant ", + title = resources.getString(R.string.name_of_first_informant), required = false ) private val ashaSign = FormElement( id = 19, inputType = InputType.EDIT_TEXT, - title = "Signature/Name of ASHA", + title = resources.getString(R.string.signature_name_of_asha), required = false ) private val dateOfNotification = FormElement( id = 20, inputType = InputType.DATE_PICKER, - title = "Date of Notification ", + title = resources.getString(R.string.date_of_notification), min = 0L, max = System.currentTimeMillis(), required = false ) + private val cdrFileUpload1 = FormElement( + id = 21, + inputType = InputType.FILE_UPLOAD, + title = resources.getString(R.string.cdr_form_from_anm_1), + required = false, + ) + private val cdrFileUpload2 = FormElement( + id = 22, + inputType = InputType.FILE_UPLOAD, + title = resources.getString(R.string.cdr_form_from_anm_2), + required = false, + ) + private val cdrDeathFileUpload = FormElement( + id = 23, + inputType = InputType.FILE_UPLOAD, + title =resources.getString(R.string.death_certificate), + required = false, + ) + + private val state = FormElement( + id = 24, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.state), + required = false + ) + private val district = FormElement( + id = 25, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.district_tehsil), + required = false + ) + private val block = FormElement( + id = 26, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.block), + required = false + ) + private val village = FormElement( + id = 27, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.village_town_city), + required = false + ) + suspend fun setUpPage( ben: BenRegCache, currentAddress: String?, @@ -155,7 +204,7 @@ class CDRFormDataset( currentMohalla: String?, saved: CDRCache? ) { - val list = listOf( + val list = mutableListOf( childName, dateOfBirth, age, @@ -164,19 +213,44 @@ class CDRFormDataset( motherName, fatherName, address, + state, + district, + block, + village, houseNumber, mohalla, - landmarks, - pincode, +// landmarks, +// pincode, landline, mobileNumber, dateOfDeath, - timeOfDeath, placeOfDeath, firstInformant, - ashaSign, - dateOfNotification +// ashaSign, + timeOfDeath, + + dateOfNotification, + cdrFileUpload1, cdrFileUpload2, cdrDeathFileUpload ) + + if (BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true)) { + list.remove(cdrFileUpload1) + list.remove(cdrFileUpload2) + list.remove(cdrDeathFileUpload) + } + + val dateFormat = SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH) + val deathDateMillis = ben.dateOfDeath?.let { + try { + dateFormat.parse(it)?.time + } catch (e: Exception) { + Timber.tag("CDRFormDataset").e(e, "Failed to parse death date: " + it) + null + } + } ?: System.currentTimeMillis() + dateOfNotification.min = deathDateMillis + dateOfNotification.max = System.currentTimeMillis() + childName.value = "${ben.firstName} ${ben.lastName}" dateOfBirth.value = getDateFromLong(ben.dob) age.value = @@ -188,16 +262,28 @@ class CDRFormDataset( address.value = currentAddress houseNumber.value = currentHouseNumber mohalla.value = currentMohalla + dateOfDeath.value = ben.dateOfDeath +// timeOfDeath.value =ben.timeOfDeath + placeOfDeath.value = ben.placeOfDeath mobileNumber.value = ben.contactNumber.toString() + val user = preferences.getLoggedInUser() + firstInformant.value = user?.userName ?: "" + state.value = user?.state?.name ?: "" + district.value = user?.district?.name ?: "" + block.value = user?.block?.name ?: "" + village.value = user!!.villages[0].name saved?.let { savedCdr -> visitDate.value = savedCdr.visitDate?.let { it1 -> getDateFromLong(it1) } landmarks.value = savedCdr.landmarks + cdrFileUpload2.value = savedCdr.cdr2File + cdrFileUpload1.value = savedCdr.cdr1File + cdrDeathFileUpload.value = savedCdr.cdrDeathCertFile pincode.value = savedCdr.pincode?.toString() landline.value = savedCdr.landline?.toString() mobileNumber.value = savedCdr.mobileNumber.toString() - dateOfDeath.value = getDateFromLong(savedCdr.dateOfDeath) +// dateOfDeath.value = getDateFromLong(savedCdr.dateOfDeath) timeOfDeath.value = savedCdr.timeOfDeath?.let { getDateFromLong(it) } - placeOfDeath.value = savedCdr.placeOfDeath +// placeOfDeath.value = savedCdr.placeOfDeath firstInformant.value = savedCdr.firstInformant ashaSign.value = savedCdr.ashaSign dateOfNotification.value = savedCdr.dateOfNotification?.let { getDateFromLong(it) } @@ -218,6 +304,9 @@ class CDRFormDataset( cdr.motherName = motherName.value cdr.fatherName = fatherName.value cdr.address = address.value + cdr.cdr1File = cdrFileUpload1.value + cdr.cdr2File = cdrFileUpload2.value + cdr.cdrDeathCertFile = cdrDeathFileUpload.value cdr.houseNumber = houseNumber.value cdr.mohalla = mohalla.value cdr.landmarks = landmarks.value @@ -225,11 +314,57 @@ class CDRFormDataset( cdr.landline = landline.value?.toLong() cdr.mobileNumber = mobileNumber.value!!.toLong() cdr.dateOfDeath = getLongFromDate(dateOfDeath.value) - cdr.timeOfDeath = timeOfDeath.value?.let { getLongFromDate(it) } + cdr.timeOfDeath = convertTimeToLong(timeOfDeath.value) cdr.placeOfDeath = placeOfDeath.value cdr.firstInformant = firstInformant.value cdr.ashaSign = ashaSign.value cdr.dateOfNotification = dateOfNotification.value?.let { getLongFromDate(it) } } } + + fun getIndexOfCDR1() = getIndexById(cdrFileUpload1.id) + fun getIndexOfCDR2() = getIndexById(cdrFileUpload2.id) + fun getIndexOfIsDeathCertificate() = getIndexById(cdrDeathFileUpload.id) + + + fun setImageUriToFormElement(lastImageFormId: Int, dpUri: Uri) { + + when (lastImageFormId) { + 21 -> { + cdrFileUpload1.value = dpUri.toString() + cdrFileUpload1.errorText = null + } + + 22 -> { + cdrFileUpload2.value = dpUri.toString() + cdrFileUpload2.errorText = null + } + + 23 -> { + cdrDeathFileUpload.value = dpUri.toString() + cdrDeathFileUpload.errorText = null + } + + } + } + + private fun convertTimeToLong(value: Any?): Long { + return when (value) { + is Long -> value + is String -> { + try { + val parts = value.split(":") + val hours = parts.getOrNull(0)?.toIntOrNull() ?: return 0L + val minutes = parts.getOrNull(1)?.toIntOrNull() ?: return 0L + return (hours * 60 * 60 * 1000 + minutes * 60 * 1000).toLong() + } catch (e: Exception) { + 0L + } + } + + else -> 0L + } + } + + } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/ChildFormData.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/ChildFormData.kt new file mode 100644 index 000000000..feaea18c7 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/ChildFormData.kt @@ -0,0 +1,8 @@ +package org.piramalswasthya.sakhi.configuration + +data class ChildFormData( + val firstName: String, + val dob: String, + val age: Int, + val gender: String +) diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/ChildRegistrationDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/ChildRegistrationDataset.kt index 14971a939..5dc0caa89 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/configuration/ChildRegistrationDataset.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/ChildRegistrationDataset.kt @@ -1,6 +1,7 @@ package org.piramalswasthya.sakhi.configuration import android.content.Context +import android.net.Uri import org.piramalswasthya.sakhi.R import org.piramalswasthya.sakhi.database.room.SyncState import org.piramalswasthya.sakhi.helpers.Languages @@ -22,7 +23,7 @@ class ChildRegistrationDataset( private val dateOfReg = FormElement( id = 1, inputType = InputType.DATE_PICKER, - title = context.getString(R.string.nbr_dor), + title = resources.getString(R.string.nbr_dor), arrayId = -1, required = true, min = getMinDateOfReg(), @@ -32,14 +33,14 @@ class ChildRegistrationDataset( private val childName = FormElement( id = 2, inputType = InputType.EDIT_TEXT, - title = "Name of Child", + title = resources.getString(R.string.str_name_of_child), required = false, hasDependants = false ) private val rchId = FormElement( id = 3, inputType = InputType.EDIT_TEXT, - title = "RCH ID No. of Child", + title = resources.getString(R.string.str_rch_id_of_child), arrayId = -1, required = false, etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, @@ -50,7 +51,7 @@ class ChildRegistrationDataset( private val dob = FormElement( id = 4, inputType = InputType.DATE_PICKER, - title = "Date of Birth", + title = resources.getString(R.string.nbr_dob), arrayId = -1, required = true, min = getMinDateOfReg(), @@ -61,8 +62,8 @@ class ChildRegistrationDataset( private val childGender = FormElement( id = 5, inputType = InputType.RADIO, - title = "Sex of Child", - entries = arrayOf("Male", "Female"), + title = resources.getString(R.string.str_sex_of_child), + entries = arrayOf(resources.getString(R.string.male), resources.getString(R.string.female)), isEnabled = false, required = false, hasDependants = false @@ -72,7 +73,7 @@ class ChildRegistrationDataset( private val motherName = FormElement( id = 6, inputType = InputType.EDIT_TEXT, - title = "Mother's Name", + title = resources.getString(R.string.mother_s_name), arrayId = -1, required = true, allCaps = true, @@ -84,7 +85,7 @@ class ChildRegistrationDataset( private val fatherName = FormElement( id = 7, inputType = InputType.EDIT_TEXT, - title = "Father's Name", + title = resources.getString(R.string.father_s_name), arrayId = -1, required = true, allCaps = true, @@ -97,12 +98,8 @@ class ChildRegistrationDataset( private val mobileNumberOf = FormElement( id = 8, inputType = InputType.RADIO, - title = "Whose Mobile Number", - entries = arrayOf( - "Mother", - "Father", - "Others" - ), + title = resources.getString(R.string.str_whose_mobile_number), + entries = resources.getStringArray(R.array.cr_mobile_no_of_array), isEnabled = false, required = false, @@ -112,7 +109,7 @@ class ChildRegistrationDataset( private val mobileNumber = FormElement( id = 9, inputType = InputType.EDIT_TEXT, - title = "Mobile No.", + title = resources.getString(R.string.str_mobile_no), arrayId = -1, required = true, etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, @@ -126,7 +123,7 @@ class ChildRegistrationDataset( private val rchIdMother = FormElement( id = 10, inputType = InputType.EDIT_TEXT, - title = "RCH ID No. of Mother", + title = resources.getString(R.string.str_rch_id_of_mother), arrayId = -1, required = false, isEnabled = false, @@ -140,43 +137,66 @@ class ChildRegistrationDataset( private val birthCertificateNo = FormElement( id = 11, inputType = InputType.EDIT_TEXT, - title = "Birth Certificate Number", + title = resources.getString(R.string.str_birth_cert_number), arrayId = -1, required = false, ) - private val weightAtBirth = FormElement( + + private var weightAtBirth = FormElement( + id = 11, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.str_weight_at_birth_gram), + required = false, + hasDependants = false, + etMaxLength = 4, + min = 500, + max = 6000, + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + ) + /* private val weightAtBirth = FormElement( id = 12, inputType = InputType.EDIT_TEXT, - title = "Weight at Birth (kg)", + title = resources.getString(R.string.str_weight_at_birth), arrayId = -1, etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_FLAG_DECIMAL, minDecimal = 0.5, maxDecimal = 7.0, etMaxLength = 3, required = false, - ) + )*/ private val placeOfBirth = FormElement( id = 13, inputType = InputType.DROPDOWN, - title = "Place of Birth", - entries = arrayOf( - "District Hospital", - "Community Health Centre", - "Primary Health Centre", - "Sub Centre", - "Other Public Facility", - "Accredited Private Hospital", - "Other Private Hospital ", - "Home", - "Sub District Hospital", - "Medical College Hospital", - "In Transit" - ), + title = resources.getString(R.string.str_place_of_birth), + arrayId = R.array.cr_place_of_birth_array, + entries = resources.getStringArray(R.array.cr_place_of_birth_array), required = false, hasDependants = false ) + private val headLine = FormElement( + id = 13, + inputType = InputType.HEADLINE, + title = resources.getString(R.string.cr_birth_cert_uploads), + headingLine = false, + required = false, + ) + private val fileUploadFront = FormElement( + id = 14, + inputType = InputType.FILE_UPLOAD, + title = resources.getString(R.string.front_side), + required = false, + ) + private val fileUploadBack = FormElement( + id = 15, + inputType = InputType.FILE_UPLOAD, + title = resources.getString(R.string.back_side), + required = false, + ) + + fun getIndexOfBirthCertificateFrontPath() = getIndexById(fileUploadFront.id) + fun getIndexOfBirthCertificateBackPath() = getIndexById(fileUploadBack.id) suspend fun setUpPage( motherBen: BenRegCache?, @@ -196,7 +216,11 @@ class ChildRegistrationDataset( rchIdMother, birthCertificateNo, weightAtBirth, - placeOfBirth + placeOfBirth, + headLine, + fileUploadFront, + fileUploadBack + ) dateOfReg.value = getDateFromLong(System.currentTimeMillis()) dateOfReg.min = deliveryOutcomeCache?.dateOfDelivery @@ -205,24 +229,36 @@ class ChildRegistrationDataset( fatherName.value = it } ?: run { fatherName.inputType = InputType.EDIT_TEXT + fatherName.isEnabled = true } motherName.value = "${it.firstName} ${it.lastName ?: ""}" mobileNumberOf.value = mobileNumberOf.entries?.first() mobileNumber.value = it.contactNumber.toString() rchIdMother.value = it.rchId + } ?: run { + motherName.isEnabled = true + fatherName.isEnabled = true + mobileNumber.isEnabled = true + mobileNumberOf.isEnabled = true } deliveryOutcomeCache?.dateOfDelivery?.let { dob.value = getDateFromLong(it) + } ?: run { + dob.isEnabled = true } deliveryOutcomeCache?.placeOfDelivery?.let { - placeOfBirth.value = it + placeOfBirth.value = getLocalValueInArray(R.array.cr_place_of_birth_array, it) ?: it } infantRegCache?.let { infant -> childName.value = infant.babyName - weightAtBirth.value = infant.weight?.toString() + weightAtBirth.value =formatWeightInGrams(infant.weight) infant.gender?.let { childGender.value = childGender.entries?.get(it.ordinal) + } ?: run { + childGender.isEnabled = true } + } ?: run { + childGender.isEnabled = true } @@ -231,6 +267,16 @@ class ChildRegistrationDataset( } + fun formatWeightInGrams(weight: Double?): String { + if (weight == null) return "" + val grams = if (weight < 50) weight * 1000 else weight + return if (grams % 1 == 0.0) { + "${grams.toInt()}" + } else { + "${grams}" + } + } + override suspend fun handleListOnValueChanged(formId: Int, index: Int): Int { return when (formId) { childName.id -> validateAllCapsOrSpaceOnEditText(childName) @@ -238,7 +284,8 @@ class ChildRegistrationDataset( rchIdMother.id -> validateRchIdOnEditText(rchIdMother) mobileNumber.id -> validateMobileNumberOnEditText(mobileNumber) fatherName.id -> validateAllCapsOrSpaceOnEditText(fatherName) - weightAtBirth.id -> validateDoubleMinMax(weightAtBirth) + weightAtBirth.id -> validateWeightOnEditText(weightAtBirth) + //validateDoubleMinMax(weightAtBirth) birthCertificateNo.id -> validateNoAlphabetSpaceOnEditText(birthCertificateNo) else -> -1 @@ -271,6 +318,17 @@ class ChildRegistrationDataset( return BenRegCache( ashaId = user.userId, beneficiaryId = 0, + isDeath = motherBen.isDeath, + isDeathValue=motherBen.isDeathValue, + dateOfDeath=motherBen.dateOfDeath, + timeOfDeath=motherBen.timeOfDeath, + reasonOfDeath=motherBen.reasonOfDeath, + reasonOfDeathId=motherBen.reasonOfDeathId, + placeOfDeath=motherBen.placeOfDeath, + placeOfDeathId=motherBen.placeOfDeathId, + otherPlaceOfDeath=motherBen.otherPlaceOfDeath, + + createdDate = System.currentTimeMillis(), updatedBy = user.userName, createdBy = user.userName, @@ -306,10 +364,13 @@ class ChildRegistrationDataset( processed = "N", kidDetails = BenRegKid( childName = childName.value, - birthPlace = placeOfBirth.value, + birthPlace = getEnglishValueInArray(R.array.cr_place_of_birth_array, placeOfBirth.value), birthPlaceId = placeOfBirth.getPosition(), - birthCertificateNumber = birthCertificateNo.value - ) + birthCertificateNumber = birthCertificateNo.value, + birthCertificateFileFrontView = fileUploadFront.value.toString(), + birthCertificateFileBackView = fileUploadBack.value.toString() + ), + isConsent = false ) } @@ -326,4 +387,18 @@ class ChildRegistrationDataset( else 10 } + + fun setImageUriToFormElement(lastImageFormId: Int, dpUri: Uri) { + if (lastImageFormId == 14) { + fileUploadFront.value = dpUri.toString() + fileUploadFront.errorText = null + + } else { + fileUploadBack.value = dpUri.toString() + fileUploadBack.errorText = null + + } + + + } } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/Dataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/Dataset.kt index 30f00b00e..8c453482f 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/configuration/Dataset.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/Dataset.kt @@ -2,11 +2,15 @@ package org.piramalswasthya.sakhi.configuration import android.content.Context import android.content.res.Resources +import android.util.Log import android.util.Range import androidx.annotation.StringRes import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.helpers.Konstants.english import org.piramalswasthya.sakhi.helpers.Languages import org.piramalswasthya.sakhi.helpers.setToStartOfTheDay import org.piramalswasthya.sakhi.model.FormElement @@ -18,13 +22,14 @@ import java.text.SimpleDateFormat import java.util.Calendar import java.util.Locale import java.util.concurrent.TimeUnit +import kotlin.math.abs /** * Base class to be extended to use as a sandwich between viewModel and repository objects. * @see org.piramalswasthya.sakhi.adapters.FormInputAdapter */ -abstract class Dataset(context: Context, currentLanguage: Languages) { +abstract class Dataset(context: Context, val currentLanguage: Languages) { /** * Resource object of currently selected language. To be used to get language specific strings from strings.xml. @@ -41,13 +46,18 @@ abstract class Dataset(context: Context, currentLanguage: Languages) { * Helper function to get resource instance chosen language. */ - protected companion object { + companion object { fun getLongFromDate(dateString: String?): Long { + if (dateString.isNullOrEmpty()) return 0L val f = SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH) - val date = dateString?.let { f.parse(it) } - return date?.time ?: 0L + return try { + f.parse(dateString)?.time ?: 0L + } catch (e: java.text.ParseException) { + 0L + } } + fun getFinancialYear(dateString: String?): String? { val f = SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH) val date = dateString?.let { f.parse(it) } @@ -80,6 +90,30 @@ abstract class Dataset(context: Context, currentLanguage: Languages) { } + fun dateFormate(dateStr: String): String? { + val inputFormat = SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) + val outputFormat = SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH) + + val dateResponse = inputFormat.parse(dateStr) + return outputFormat.format(dateResponse!!) + + + } + + fun dateReverseFormat(dateStr: String): String? { + if (dateStr.isEmpty()) return null + + return try { + val inputFormat = SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH) + val outputFormat = SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) + + val dateResponse = inputFormat.parse(dateStr) ?: return null + outputFormat.format(dateResponse) + } catch (e: Exception) { + null + } + } + fun getMinDateOfReg(): Long { return Calendar.getInstance().apply { set(Calendar.YEAR, 2020) @@ -91,6 +125,7 @@ abstract class Dataset(context: Context, currentLanguage: Languages) { } + private val listMutex = Mutex() private val list = mutableListOf() private val _listFlow = MutableStateFlow>(emptyList()) @@ -108,11 +143,18 @@ abstract class Dataset(context: Context, currentLanguage: Languages) { } protected fun FormElement.getStringFromPosition(position: Int): String? { - return if (position <= 0) null else entries?.get(position - 1) + return if (position <= 0) null else entries?.getOrNull(position - 1) + } + + protected fun FormElement.getStringSpauseFromPosition(position: Int): String? { + return if (position <= 0) entries?.getOrNull(1) else entries?.getOrNull(position - 1) } protected fun FormElement.getEnglishStringFromPosition(position: Int): String? { - return if (position <= 0) null else englishResources.getStringArray(arrayId)[position - 1] + return if (position <= 0) null else { + val array = englishResources.getStringArray(arrayId) + return if (position in 1..array.size) array[position - 1] else null + } } @@ -121,29 +163,37 @@ abstract class Dataset(context: Context, currentLanguage: Languages) { abstract fun mapValues(cacheModel: FormDataModel, pageNumber: Int = 0) protected fun getIndexOfElement(element: FormElement) = list.indexOf(element) suspend fun updateList(formId: Int, index: Int) { - list.find { it.id == formId }?.let { - if (it.inputType == InputType.DROPDOWN) { - it.errorText = null + listMutex.withLock { + list.find { it.id == formId }?.let { + if (it.inputType == InputType.DROPDOWN) { + it.errorText = null + } } - } - val updateIndex = handleListOnValueChanged(formId, index) - if (updateIndex != -1) { - val newList = list.toMutableList() + val updateIndex = handleListOnValueChanged(formId, index) + if (updateIndex != -1) { + val newList = list.toMutableList() // if (updateUIForCurrentElement) { // Timber.d("Updating UI element ...") // newList[updateIndex] = list[updateIndex].cloneForm() // updateUIForCurrentElement = false // } - Timber.d("Emitting ${newList}}") + Timber.d("Emitting ${newList}}") // _listFlow.emit(emptyList()) - _listFlow.emit(newList) + _listFlow.emit(newList) + } } } protected suspend fun setUpPage(mList: List) { - list.clear() - list.addAll(mList) - _listFlow.emit(list.toMutableList()) + listMutex.withLock { + try { + list.clear() + list.addAll(mList) + _listFlow.emit(list.toMutableList()) + } catch (e: Exception) { + e.printStackTrace() + } + } } @@ -163,9 +213,8 @@ abstract class Dataset(context: Context, currentLanguage: Languages) { listIndex } else -1 } else { - val anyRemoved = list.removeAll( - target - ) + var anyRemoved = false + target.forEach { if (list.remove(it)) anyRemoved = true } if (anyRemoved) { targetSideEffect?.let { sideEffectList -> @@ -173,7 +222,7 @@ abstract class Dataset(context: Context, currentLanguage: Languages) { it.value = null it.errorText = null } - list.removeAll(sideEffectList) + sideEffectList.forEach { list.remove(it) } } target.forEach { it.value = null @@ -200,15 +249,14 @@ abstract class Dataset(context: Context, currentLanguage: Languages) { listIndex } else -1 } else { - val anyRemoved = list.removeAll( - target - ) + var anyRemoved = false + target.forEach { if (list.remove(it)) anyRemoved = true } if (anyRemoved) { target.forEach { it.value = null } targetSideEffect?.let { sideEffectList -> - list.removeAll(sideEffectList) + sideEffectList.forEach { list.remove(it) } sideEffectList.forEach { it.value = null } } list.indexOf(source) @@ -227,26 +275,52 @@ abstract class Dataset(context: Context, currentLanguage: Languages) { return if (passedIndex == triggerIndex) { if (!list.contains(target)) { val listIndex = list.indexOf(source) - list.add( - listIndex + 1, target - ) + val safeIndex = (listIndex + 1).coerceAtMost(list.size) + list.add(safeIndex, target) listIndex - } else -1 + } else { + -1 + } } else { - val anyRemoved = list.remove( - target - ) + val anyRemoved = list.remove(target) if (anyRemoved) { + target.value = null + targetSideEffect?.forEach { element -> + if (list.contains(element)) { + list.remove(element) + element.value = null + } + } + list.indexOf(source) + } else { + -1 + } + } + } + + + + protected fun triggerforHide( + source: FormElement, + passedIndex: Int, + triggerIndex: Int, + target: FormElement, + targetSideEffect: List? = null + ): Int { + val anyRemoved = list.remove(target) + return if (anyRemoved) { target.value = null targetSideEffect?.let { sideEffectList -> - list.removeAll(sideEffectList) + sideEffectList.forEach { list.remove(it) } sideEffectList.forEach { it.value = null } } list.indexOf(source) } else -1 - } + + } + protected fun triggerDependants( age: Int, // ageUnit: FormElement, @@ -276,7 +350,7 @@ abstract class Dataset(context: Context, currentLanguage: Languages) { if (anyRemoved) { target.value = null targetSideEffect?.let { sideEffectList -> - list.removeAll(sideEffectList) + sideEffectList.forEach { list.remove(it) } sideEffectList.forEach { it.value = null } } 302 @@ -284,6 +358,28 @@ abstract class Dataset(context: Context, currentLanguage: Languages) { } } + protected fun infantTriggerDependants( + source: FormElement, + removeItems: List, + addItems: List, + position: Int = -1, + ): Int { + + removeItems.forEach { it.value = null } + removeItems.forEach { list.remove(it) } + + addItems.forEach { + if (list.contains(it)) list.remove(it) + } + + // FORCE ADD AT BOTTOM → correct sequence always + val addPosition = list.lastIndex + 1 + + list.addAll(addPosition, addItems) + + return addPosition + } + protected fun triggerDependants( source: FormElement, removeItems: List, @@ -293,7 +389,7 @@ abstract class Dataset(context: Context, currentLanguage: Languages) { removeItems.forEach { it.value = null } - list.removeAll(removeItems) + removeItems.forEach { list.remove(it) } // list.removeAll(addItems) addItems.forEach { if (list.contains(it)) list.remove(it) @@ -364,11 +460,19 @@ abstract class Dataset(context: Context, currentLanguage: Languages) { add(Calendar.WEEK_OF_YEAR, 40) }.timeInMillis + protected fun getANCMaxFromLmp(lmp: Long) = + + Calendar.getInstance().apply { + timeInMillis = lmp + add(Calendar.DAY_OF_YEAR, 294) + // add(Calendar.WEEK_OF_YEAR, 42) + }.timeInMillis + protected fun getMinFromMaxForLmp(lmp: Long) = Calendar.getInstance().apply { timeInMillis = lmp - add(Calendar.WEEK_OF_YEAR, -40) + add(Calendar.WEEK_OF_YEAR, -57) }.timeInMillis @@ -487,15 +591,57 @@ abstract class Dataset(context: Context, currentLanguage: Languages) { ?: false protected fun validateAllCapsOrSpaceOnEditText(formElement: FormElement): Int { + if (currentLanguage.toString() == english) { + if (formElement.allCaps) { + formElement.value?.takeIf { it.isNotEmpty() }?.isAllUppercaseOrSpace()?.let { + Timber.d("Is ok : $it") + formElement.errorText = if (it) null + else resources.getString(R.string.form_input_upper_case_error) + } ?: run { + if (!formElement.required) formElement.errorText = null + } + } + } + return -1 + } + + protected fun validateAllCapsOrSpaceOnEditTextWithHindiEnabled(formElement: FormElement): Int { + val value = formElement.value.orEmpty().trim() + + // Function to check if a character is Hindi or Assamese + fun Char.isHindiOrAssamese(): Boolean { + return this in '\u0900'..'\u097F' || this in '\u0980'..'\u09FF' + } + + // Function to check if a string contains only uppercase English letters or spaces + fun String.isAllUppercaseOrSpace(): Boolean { + return this.all { it.isUpperCase() || it.isWhitespace() || it.isHindiOrAssamese() } + } + if (formElement.allCaps) { - formElement.value?.takeIf { it.isNotEmpty() }?.isAllUppercaseOrSpace()?.let { - Timber.d("Is ok : $it") - formElement.errorText = if (it) null - else resources.getString(R.string.form_input_upper_case_error) - } ?: run { - if (!formElement.required) formElement.errorText = null + when { + value.isEmpty() -> { + if (formElement.required) { + formElement.errorText = resources.getString(R.string.form_input_empty_error) + } else { + formElement.errorText = null + } + return -1 + } + !value.isAllUppercaseOrSpace() -> { + formElement.errorText = resources.getString(R.string.form_input_upper_case_error) + return -1 + } } + + // Convert only English letters to uppercase, keep Hindi/Assamese as is + val transformedValue = value.map { + if (it.isLowerCase() && !it.isHindiOrAssamese()) it.uppercaseChar() else it + }.joinToString("") + + formElement.value = transformedValue } + return -1 } @@ -537,6 +683,21 @@ abstract class Dataset(context: Context, currentLanguage: Languages) { return -1 } + protected fun validateAllAlphabetsSpecialAndNumericOnEditText(formElement: FormElement): Int { + formElement.value?.takeIf { it.isNotEmpty() }?.let { input -> + val regex = "^[\\p{L}\\p{M}0-9\\s\\p{Punct}]+$".toRegex() // allows Unicode letters, combining marks (vowel signs, anusvara, nukta), numbers, spaces, and special characters + + val isValid = regex.matches(input) + if (!isValid) { + formElement.errorText = resources.getString(R.string.form_input_alphabet_special__digit_only_error) + } else { + formElement.errorText = null + } + } + return -1 + } + + protected fun validateAllAlphaNumericSpaceOnEditText(formElement: FormElement): Int { formElement.value?.takeIf { it.isNotEmpty() }?.let { val isValid = it.isAllAlphaNumericAndSpace() @@ -550,6 +711,23 @@ abstract class Dataset(context: Context, currentLanguage: Languages) { } return -1 } + fun String.isValid(): Boolean { + return this.matches(Regex("^\\d{14}$")) + } + + protected fun validateABHANumberEditText(formElement: FormElement): Int { + formElement.value?.takeIf { it.isNotEmpty() }?.let { + val isValid = it.isValid() + if (formElement.errorText != null && formElement.errorText != resources.getString(R.string.abha_number_digit)) + return@let + if (isValid) formElement.errorText = null + else formElement.errorText = + resources.getString(R.string.abha_number_digit) + } ?: kotlin.run { + formElement.errorText = null + } + return -1 + } protected fun validateAllAlphaNumericOnEditText(formElement: FormElement): Int { formElement.value?.takeIf { it.isNotEmpty() }?.let { @@ -564,6 +742,23 @@ abstract class Dataset(context: Context, currentLanguage: Languages) { } return -1 } + private fun String.isValidFormat() = takeIf { + matches(Regex("^[A-Z]{4}[0-9]{7}$")) + } != null + + protected fun validateIFSCEditText(formElement: FormElement): Int { + formElement.value?.takeIf { it.isNotEmpty() }?.let { + val isValid = it.isValidFormat() + if (formElement.errorText != null && formElement.errorText != resources.getString(R.string.ifsc)) + return@let + if (isValid) formElement.errorText = null + else formElement.errorText = + resources.getString(R.string.ifsc) + } ?: kotlin.run { + formElement.errorText = null + } + return -1 + } protected fun validateNoAlphabetSpaceOnEditText(formElement: FormElement): Int { formElement.value?.takeIf { it.isNotEmpty() }?.isAnyAlphabetOrSpace()?.let { @@ -590,7 +785,29 @@ abstract class Dataset(context: Context, currentLanguage: Languages) { return -1 } - protected fun validateIntMinMax(formElement: FormElement): Int { + protected fun validateUploads( + uploads: List, + minRequired: Int = 2 + ): Int { + val uploadCount = uploads.count { !it.value.isNullOrEmpty() } + + return if (uploadCount < minRequired) { + uploads.forEach { + if (it.value.isNullOrEmpty()) { + it.errorText = resources.getString(R.string.form_input_empty_error) + } + } + 0 + } else { + uploads.forEach { it.errorText = null } + -1 + } + } + + + + + /* protected fun validateIntMinMax(formElement: FormElement): Int { formElement.errorText = formElement.value?.takeIf { it.isNotEmpty() }?.toLong()?.let { formElement.min?.let { min -> formElement.max?.let { max -> @@ -606,15 +823,44 @@ abstract class Dataset(context: Context, currentLanguage: Languages) { } } } + return -1 + }*/ + + protected fun validateIntMinMax(formElement: FormElement): Int { + val inputValue = formElement.value + + val longValue = inputValue?.takeIf { it.isNotEmpty() }?.toLongOrNull() + + formElement.errorText = if (longValue == null) { + null + } else { + formElement.min?.let { min -> + formElement.max?.let { max -> + when { + longValue < min -> resources.getString( + R.string.form_input_min_limit_error, formElement.title, min + ) + + longValue > max -> resources.getString( + R.string.form_input_max_limit_error, formElement.title, max + ) + + else -> null + } + } + } + } + return -1 } + protected fun validateDoubleMinMax(formElement: FormElement): Int { formElement.errorText = formElement.value?.takeIf { it.isNotEmpty() }?.let { if (it.first() == '.') "0$it" else it - }?.toDouble()?.let { + }?. toDoubleOrNull()?.let { formElement.minDecimal?.let { min -> formElement.maxDecimal?.let { max -> if (it < min) { @@ -659,7 +905,7 @@ abstract class Dataset(context: Context, currentLanguage: Languages) { protected fun validateMobileNumberOnEditText(formElement: FormElement): Int { - formElement.errorText = formElement.value?.takeIf { it.isNotEmpty() }?.toLong()?.let { + formElement.errorText = formElement.value?.takeIf { it.isNotEmpty() }?.toLongOrNull()?.let { if (it < 6_000_000_000L || it == 6666666666L || it == 7777777777L || it == 8888888888L || it == 9999999999L ) resources.getString(R.string.form_input_error_invalid_mobile) else null @@ -667,6 +913,18 @@ abstract class Dataset(context: Context, currentLanguage: Languages) { return -1 } +// protected fun validateNumberOnEditText(formElement: FormElement): Int { +// val input = formElement.value?.trim() ?: "" +// +// formElement.errorText = when { +//// input.isEmpty() -> resources.getString(R.string.form_input_error_mandatory) +//// input.any { !it.isDigit() } -> resources.getString(R.string.form_input_error_numeric_only) +// input.length > 4 -> resources.getString(R.string.form_input_error_max_digits) +// else -> null +// } +// +// return -1 +// } protected fun validateRchIdOnEditText(formElement: FormElement): Int { formElement.errorText = formElement.value?.takeIf { it.isNotEmpty() }?.let { text -> @@ -692,6 +950,41 @@ abstract class Dataset(context: Context, currentLanguage: Languages) { return -1 } + protected fun validateWeightOnEditText(formElement: FormElement): Int { + val value = formElement.value?.trim() + + if (value.isNullOrEmpty()) { + formElement.errorText = null + return -1 + } + + val weight = value.toDoubleOrNull() + if (weight == null) { + formElement.errorText = resources.getString(R.string.weight_enter_in_grams) + return -1 + } + + when { + weight <= 0 -> { + formElement.errorText = resources.getString(R.string.weight_cannot_be_zero) + } + weight in 1.0..10.0 -> { + formElement.errorText = resources.getString(R.string.weight_enter_in_grams_example) + } + weight < 500 -> { + formElement.errorText = resources.getString(R.string.weight_min_500) + } + weight > 7000 -> { + formElement.errorText = resources.getString(R.string.weight_max_7000) + } + else -> { + formElement.errorText = null + } + } + + return -1 + } + protected fun validateMcpOnEditText(formElement: FormElement): Int { formElement.errorText = formElement.value?.takeIf { it.isNotEmpty() }?.let { @@ -724,15 +1017,15 @@ abstract class Dataset(context: Context, currentLanguage: Languages) { } val matchResult = bpRegex.matchEntire(bp.value!!) if (matchResult == null) - bp.errorText = "Invalid format. Should be like 123/56" + bp.errorText = resources.getString(R.string.bp_invalid_format) else { val sys = matchResult.groupValues[1].toInt() val dia = matchResult.groupValues[2].toInt() - bp.errorText = if (sys < minSys) "Systole should not be less than $minSys" - else if (sys > maxSys) "Systole should not be greater than $maxSys" - else if (dia < minDia) "Diastole should not be less then $minDia" - else if (dia > maxDia) "Diastole should not be greater than $maxDia" - else if (dia > sys) "Diastole cannot be greater than systole" + bp.errorText = if (sys < minSys) resources.getString(R.string.bp_systole_less_than, minSys) + else if (sys > maxSys) resources.getString(R.string.bp_systole_greater_than, maxSys) + else if (dia < minDia) resources.getString(R.string.bp_diastole_less_than, minDia) + else if (dia > maxDia) resources.getString(R.string.bp_diastole_greater_than, maxDia) + else if (dia > sys) resources.getString(R.string.bp_diastole_greater_than_systole) else null } return -1 @@ -772,21 +1065,113 @@ abstract class Dataset(context: Context, currentLanguage: Languages) { return dob.timeInMillis } + fun getLocalValueInArray(arrayId: Int, entry: String?): String? { - return if (entry.isNullOrEmpty()) { - null - } else { - resources.getStringArray(arrayId)[englishResources.getStringArray(arrayId) - .indexOf(entry)] + if (entry.isNullOrEmpty()) return null + + val englishArray = englishResources.getStringArray(arrayId) + val localizedArray = resources.getStringArray(arrayId) + + // Try English lookup first (value was stored in English) + val englishIndex = englishArray.indexOf(entry) + if (englishIndex in englishArray.indices) { + return localizedArray[englishIndex] } + + // Fallback: value was stored in localized language, find it directly + val localIndex = localizedArray.indexOf(entry) + if (localIndex in localizedArray.indices) { + return localizedArray[localIndex] + } + + Log.w("Dataset", "Entry '$entry' not found in array for ID $arrayId") + return null } + + + /* fun getLocalValueInArray(arrayId: Int, entry: String?): String? { + return if (entry.isNullOrEmpty()) { + null + } else { + resources.getStringArray(arrayId)[englishResources.getStringArray(arrayId) + .indexOf(entry)] + } + }*/ + fun getEnglishValueInArray(arrayId: Int, entry: String?): String? { - entry?.let { - return englishResources.getStringArray(arrayId)[resources.getStringArray(arrayId) - .indexOf(it)] + if (entry.isNullOrEmpty()) return null + + val localizedArray = resources.getStringArray(arrayId) + val englishArray = englishResources.getStringArray(arrayId) + val index = localizedArray.indexOf(entry) + + return if (index in localizedArray.indices) { + englishArray[index] + } else { + Log.w("Dataset", "Entry '$entry' not found in localized array for ID $arrayId") + null } - return null + } + + fun getEnglishCheckboxValues(arrayId: Int, indexValues: String?): String? { + if (indexValues.isNullOrEmpty()) return null + val englishArray = englishResources.getStringArray(arrayId) + return indexValues.split("|") + .mapNotNull { it.trim().toIntOrNull() } + .filter { it in englishArray.indices } + .joinToString("|") { englishArray[it] } + .ifEmpty { null } + } + + fun getCheckboxIndexesFromValues(arrayId: Int, storedValues: String?): String? { + if (storedValues.isNullOrEmpty()) return null + val parts = storedValues.split("|") + if (parts.all { it.trim().toIntOrNull() != null }) return storedValues + val englishArray = englishResources.getStringArray(arrayId) + val localizedArray = resources.getStringArray(arrayId) + return parts.mapNotNull { value -> + val trimmed = value.trim() + val englishIdx = englishArray.indexOf(trimmed) + if (englishIdx >= 0) englishIdx + else { + val localIdx = localizedArray.indexOf(trimmed) + if (localIdx >= 0) localIdx else null + } + }.sorted().joinToString("|") { it.toString() }.ifEmpty { null } + } + + fun isValidChildGap(formElement: FormElement, firstDobStr: String?/*, secondDobStr: String?*/): Int { + val dateFormat = SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH) + try { + val firstDob = dateFormat.parse(firstDobStr) + val secondDob = dateFormat.parse(formElement.value) + + if (firstDob == null || secondDob == null) { + formElement.errorText = null //return false + } + + // If twins (same DOB), it's valid + if (firstDob == secondDob){ + formElement.errorText = null + return -1 + } //true + + val diffInMillis = abs(secondDob.time - firstDob.time) + val diffInDays = diffInMillis / (1000 * 60 * 60 * 24) + + // Valid if gap is 365 days (approx. 12 months) or more + if (diffInDays >= 365){ + formElement.errorText = null + }else{ + formElement.errorText = "Invalid date of birth! The minimum age difference should be at least 12 months." + } + + } catch (e: Exception) { + formElement.errorText = "Invalid date format or parsing error"//null // Invalid date format or parsing error + } + + return -1 } } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/DeliveryOutcomeDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/DeliveryOutcomeDataset.kt index 1e1a04a64..13f9ee2c5 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/configuration/DeliveryOutcomeDataset.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/DeliveryOutcomeDataset.kt @@ -1,19 +1,44 @@ package org.piramalswasthya.sakhi.configuration import android.content.Context +import android.net.Uri +import java.util.Locale +import android.util.Log +import org.piramalswasthya.sakhi.BuildConfig import org.piramalswasthya.sakhi.R import org.piramalswasthya.sakhi.helpers.Languages +import org.piramalswasthya.sakhi.model.BenStatus import org.piramalswasthya.sakhi.model.DeliveryOutcomeCache import org.piramalswasthya.sakhi.model.FormElement import org.piramalswasthya.sakhi.model.InputType +import org.piramalswasthya.sakhi.model.InputType.DATE_PICKER +import org.piramalswasthya.sakhi.model.InputType.DROPDOWN +import org.piramalswasthya.sakhi.model.InputType.EDIT_TEXT import org.piramalswasthya.sakhi.model.PregnantWomanAncCache import org.piramalswasthya.sakhi.model.PregnantWomanRegistrationCache +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Date import java.util.concurrent.TimeUnit open class DeliveryOutcomeDataset( context: Context, currentLanguage: Languages ) : Dataset(context, currentLanguage) { + companion object{ + @Throws(Exception::class) + fun getOneMonthLater(deliveryDate: String?): String { + val sdf: SimpleDateFormat = SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH) + val date: Date = sdf.parse(deliveryDate) + + val calendar: Calendar = Calendar.getInstance() + calendar.setTime(date) + calendar.add(Calendar.MONTH, 1) // Add 1 month + + return sdf.format(calendar.getTime()) + } + } + private val dateOfDelivery = FormElement( id = 1, inputType = InputType.DATE_PICKER, @@ -38,7 +63,7 @@ open class DeliveryOutcomeDataset( inputType = InputType.DROPDOWN, title = resources.getString(R.string.do_delivery_place), entries = resources.getStringArray(R.array.do_place_of_delivery_array), - required = false, + required = true, hasDependants = false ) @@ -47,7 +72,7 @@ open class DeliveryOutcomeDataset( inputType = InputType.RADIO, title = resources.getString(R.string.do_delivery_type), entries = resources.getStringArray(R.array.do_type_of_delivery_array), - required = false, + required = true, hasDependants = false ) @@ -78,6 +103,7 @@ open class DeliveryOutcomeDataset( hasDependants = true ) + private var otherCauseOfDeath = FormElement( id = 8, inputType = InputType.EDIT_TEXT, @@ -88,6 +114,30 @@ open class DeliveryOutcomeDataset( hasDependants = false ) + private val dateOfDeath = FormElement( + id = 51, + inputType = DATE_PICKER, + title = context.getString(R.string.date_of_death), + max = System.currentTimeMillis(), + required = true, + ) + + private val placeOfDeath = FormElement( + id = 54, + inputType = DROPDOWN, + title = context.getString(R.string.place_of_death), + arrayId = R.array.death_place_array, + entries = resources.getStringArray(R.array.death_place_array), + required = true, + ) + private val otherPlaceOfDeath = FormElement( + id = 55, + inputType = EDIT_TEXT, + title = context.getString(R.string.other_place_of_death), + required = true, + hasDependants = true, + ) + private var otherComplication = FormElement( id = 9, inputType = InputType.EDIT_TEXT, @@ -102,7 +152,7 @@ open class DeliveryOutcomeDataset( id = 10, inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.do_delivery_outcome), - required = false, + required = true, hasDependants = false, etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, etMaxLength = 1, @@ -114,7 +164,7 @@ open class DeliveryOutcomeDataset( id = 11, inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.do_live_birth), - required = false, + required = true, hasDependants = false, etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, etMaxLength = 1, @@ -126,7 +176,7 @@ open class DeliveryOutcomeDataset( id = 12, inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.do_still_birth), - required = false, + required = true, hasDependants = false, etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, etMaxLength = 1, @@ -161,7 +211,27 @@ open class DeliveryOutcomeDataset( title = resources.getString(R.string.do_is_jsy_beneficiary), entries = resources.getStringArray(R.array.do_is_jsy_beneficiary_array), required = false, - hasDependants = false + hasDependants = true + ) + + private val mcpFileUpload1 = FormElement( + id = 21, + inputType = InputType.FILE_UPLOAD, + title = context.getString(R.string.mcp_card_1), + required = false, + ) + private val mcpFileUpload2 = FormElement( + id = 22, + inputType = InputType.FILE_UPLOAD, + title = context.getString(R.string.mcp_card_2), + required = false, + ) + private val jsyFileUpload = FormElement( + id = 23, + inputType = InputType.FILE_UPLOAD, + title = context.getString(R.string.jsy_payment_voucher), + required = false, + hasDependants = true ) suspend fun setUpPage( @@ -184,11 +254,22 @@ open class DeliveryOutcomeDataset( stillBirth, dateOfDischarge, timeOfDischarge, - isJSYBenificiary + isJSYBenificiary, + mcpFileUpload1, + mcpFileUpload2 ) + + if (BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true)) { + list.remove(mcpFileUpload1) + list.remove(mcpFileUpload2) + } if (saved == null) { dateOfDelivery.value = getDateFromLong(System.currentTimeMillis()) - dateOfDischarge.min = System.currentTimeMillis() + //dateOfDischarge.min = System.currentTimeMillis() + stillBirth.value = "0" + + dateOfDischarge.min = getLongFromDate(dateOfDelivery.value) + dateOfDischarge.max =getLongFromDate(getOneMonthLater(dateOfDelivery.value)) } else { list = mutableListOf( dateOfDelivery, @@ -205,15 +286,55 @@ open class DeliveryOutcomeDataset( stillBirth, dateOfDischarge, timeOfDischarge, - isJSYBenificiary + isJSYBenificiary, + mcpFileUpload1, + mcpFileUpload2 ) + if (BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true)) { + list.remove(mcpFileUpload1) + list.remove(mcpFileUpload2) + } +// hadComplications.value = if (saved.hadComplications == true) "Yes" else "No" + if (saved.hadComplications == true) { + list.add(list.indexOf(hadComplications) + 1 ,complication) + hadComplications.value = hadComplications.entries!![0] + } else { + hadComplications.value = hadComplications.entries!![1] + } + + + if(saved.complication.equals("Other Delivery Complication", ignoreCase = true)) + { + list.add(list.indexOf(complication) + 1 ,otherComplication) + } + if(saved.complication.equals("DEATH", ignoreCase = true)) + { + list.add(list.indexOf(complication) + 1 ,causeOfDeath) + list.add(list.indexOf(causeOfDeath) + 1 ,dateOfDeath) + list.add(list.indexOf(dateOfDeath) + 1 ,placeOfDeath) + val localPlaceOfDeath = getLocalValueInArray(R.array.death_place_array, saved.placeOfDeath) + placeOfDeath.entries?.indexOf(localPlaceOfDeath)?.takeIf { it >= 0 }?.let { index -> + if (index == 8) { + list.add(list.indexOf(placeOfDeath) + 1, otherPlaceOfDeath) + } + } + + } + mcpFileUpload1.value=saved.mcp1File + mcpFileUpload2.value=saved.mcp2File + jsyFileUpload.value=saved.jsyFile + dateOfDeath.value=saved.dateOfDeath + placeOfDeath.value=getLocalValueInArray(R.array.death_place_array, saved.placeOfDeath) + otherPlaceOfDeath.value=saved.otherPlaceOfDeath + + dateOfDelivery.value = saved.dateOfDelivery?.let { getDateFromLong(it) } timeOfDelivery.value = saved.timeOfDelivery placeOfDelivery.value = getLocalValueInArray(R.array.do_place_of_delivery_array, saved.placeOfDelivery) typeOfDelivery.value = getLocalValueInArray(R.array.do_type_of_delivery_array, saved.typeOfDelivery) - hadComplications.value = if (saved.hadComplications == true) "Yes" else "No" +// hadComplications.value = if (saved.hadComplications == true) "Yes" else "No" complication.value = getLocalValueInArray(R.array.do_complications_array, saved.complication) causeOfDeath.value = @@ -225,14 +346,21 @@ open class DeliveryOutcomeDataset( stillBirth.value = saved.stillBirth.toString() dateOfDischarge.value = saved.dateOfDischarge?.let { getDateFromLong(it) } timeOfDischarge.value = saved.timeOfDischarge - isJSYBenificiary.value = if (saved.isJSYBenificiary == true) "Yes" else "No" + if (saved.isJSYBenificiary == true) { + list.add(list.indexOf(isJSYBenificiary) + 1 ,jsyFileUpload) + isJSYBenificiary.value = isJSYBenificiary.entries!![0] + } else { + isJSYBenificiary.value = isJSYBenificiary.entries!![1] + } } + dateOfDeath.min=pwr.lmpDate dateOfDelivery.min = maxOf(pwr.lmpDate + TimeUnit.DAYS.toMillis(21 * 7), anc.ancDate) dateOfDelivery.max = minOf( System.currentTimeMillis(), getEddFromLmp(pwr.lmpDate) + TimeUnit.DAYS.toMillis(25) ) + setUpPage(list) } @@ -241,6 +369,7 @@ open class DeliveryOutcomeDataset( return when (formId) { dateOfDelivery.id -> { dateOfDischarge.min = getLongFromDate(dateOfDelivery.value) + dateOfDischarge.max =getLongFromDate(getOneMonthLater(dateOfDelivery.value)) -1 } @@ -250,16 +379,33 @@ open class DeliveryOutcomeDataset( passedIndex = index, triggerIndex = 0, target = complication, - targetSideEffect = listOf(causeOfDeath, otherComplication, otherCauseOfDeath) + targetSideEffect = listOf(causeOfDeath, otherComplication, otherCauseOfDeath,dateOfDeath,placeOfDeath,otherPlaceOfDeath) + ) + } + placeOfDeath.id -> { + val index = placeOfDeath.entries?.indexOf(placeOfDeath.value).takeIf { it!! >= 0 } ?: return -1 + val triggerIndex = 8 + return triggerDependants( + source = placeOfDeath, + passedIndex = index, + triggerIndex = triggerIndex, + target = otherPlaceOfDeath ) } complication.id -> { if (index == 6) { + liveBirth.value = "0" + liveBirth.isEnabled =false + + handleListOnValueChanged(liveBirth.id, 0) + triggerDependants( source = complication, - addItems = listOf(causeOfDeath), - removeItems = listOf(otherComplication, otherCauseOfDeath) + addItems = listOf(causeOfDeath,dateOfDeath,placeOfDeath), + removeItems = listOf(otherComplication, otherCauseOfDeath, dateOfDischarge, + timeOfDischarge, + isJSYBenificiary) ) } else if (index == 7) { triggerDependants( @@ -271,7 +417,7 @@ open class DeliveryOutcomeDataset( triggerDependants( source = complication, addItems = listOf(), - removeItems = listOf(otherComplication, otherCauseOfDeath, causeOfDeath) + removeItems = listOf(otherComplication, otherCauseOfDeath, causeOfDeath,dateOfDeath,placeOfDeath,otherPlaceOfDeath) ) } } @@ -285,6 +431,21 @@ open class DeliveryOutcomeDataset( ) } + isJSYBenificiary.id -> { + val isYes = isJSYBenificiary.value == isJSYBenificiary.entries!![0] + if (!BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true)) { + triggerDependants( + source = isJSYBenificiary, + passedIndex = index, + triggerIndex = if (isYes) index else -1, + target = jsyFileUpload + ) + } + return -1 + } + + + otherCauseOfDeath.id -> { validateAllAlphabetsSpecialOnEditText(otherCauseOfDeath) } @@ -331,6 +492,10 @@ open class DeliveryOutcomeDataset( if (deliveryOutcome.value!!.toInt() != liveBirth.value!!.toInt() + stillBirth.value!!.toInt()) { formElement.errorText = "Outcome of Delivery should be equal to sum of Live and Still births" + }else{ + deliveryOutcome.errorText = null + liveBirth.errorText = null + stillBirth.errorText = null } } if (!deliveryOutcome.value.isNullOrEmpty()) { @@ -343,22 +508,61 @@ open class DeliveryOutcomeDataset( override fun mapValues(cacheModel: FormDataModel, pageNumber: Int) { (cacheModel as DeliveryOutcomeCache).let { form -> form.dateOfDelivery = getLongFromDate(dateOfDelivery.value) + form.mcp1File = mcpFileUpload1.value + form.mcp2File = mcpFileUpload2.value + form.jsyFile = jsyFileUpload.value + form.dateOfDeath=dateOfDeath.value + form.placeOfDeath=getEnglishValueInArray(R.array.death_place_array, placeOfDeath.value) + form.otherPlaceOfDeath=otherPlaceOfDeath.value + val englishComplication = getEnglishValueInArray(R.array.do_complications_array, complication.value) + if (englishComplication.equals("Death", ignoreCase = true)){ + form.isDeath=true + form.isDeathValue="Death" + } + form.placeOfDeathId = placeOfDeath.entries?.indexOf(placeOfDeath.value ?: "") + ?.takeIf { it != -1 } + form.timeOfDelivery = timeOfDelivery.value - form.placeOfDelivery = placeOfDelivery.value - form.typeOfDelivery = typeOfDelivery.value - form.hadComplications = hadComplications.value == "Yes" - form.complication = complication.value - form.causeOfDeath = causeOfDeath.value + form.placeOfDelivery = getEnglishValueInArray(R.array.do_place_of_delivery_array, placeOfDelivery.value) + form.typeOfDelivery = getEnglishValueInArray(R.array.do_type_of_delivery_array, typeOfDelivery.value) + form.hadComplications = hadComplications.value == hadComplications.entries!![0] + form.complication = englishComplication + form.causeOfDeath = getEnglishValueInArray(R.array.do_cause_of_death_array, causeOfDeath.value) form.otherCauseOfDeath = otherCauseOfDeath.value form.otherComplication = otherComplication.value form.deliveryOutcome = deliveryOutcome.value?.toInt() form.liveBirth = liveBirth.value?.toInt() form.stillBirth = stillBirth.value?.toInt() - form.dateOfDischarge = (dateOfDischarge.value?.let { + form.dateOfDischarge = dateOfDischarge.value?.takeIf { it.isNotBlank() }?.let { getLongFromDate(it) - }) + } form.timeOfDischarge = timeOfDischarge.value - form.isJSYBenificiary = isJSYBenificiary.value == "Yes" + form.isJSYBenificiary = isJSYBenificiary.value == isJSYBenificiary.entries!![0] + } + } + fun getIndexOfMCP1() = getIndexById(mcpFileUpload1.id) + fun getIndexOfMCP2() = getIndexById(mcpFileUpload2.id) + fun getIndexOfIsjsyFileUpload() = getIndexById(jsyFileUpload.id) + + + fun setImageUriToFormElement(lastImageFormId: Int, dpUri: Uri) { + + when (lastImageFormId) { + 21 -> { + mcpFileUpload1.value = dpUri.toString() + mcpFileUpload1.errorText = null + } + + 22 -> { + mcpFileUpload2.value = dpUri.toString() + mcpFileUpload2.errorText = null + } + + 23 -> { + jsyFileUpload.value = dpUri.toString() + jsyFileUpload.errorText = null + } + } } } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/DewormingDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/DewormingDataset.kt new file mode 100644 index 000000000..c19760a08 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/DewormingDataset.kt @@ -0,0 +1,178 @@ +package org.piramalswasthya.sakhi.configuration + +import android.content.Context +import android.net.Uri +import android.text.InputType +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.helpers.Languages +import org.piramalswasthya.sakhi.model.AHDCache +import org.piramalswasthya.sakhi.model.DewormingCache +import org.piramalswasthya.sakhi.model.FormElement +import org.piramalswasthya.sakhi.model.InputType.* +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Date +import java.util.Locale + +class DewormingDataset( + context: Context, + currentLanguage: Languages +) : Dataset(context, currentLanguage) { + + + companion object { + + fun getMinDate(): Long { + return Calendar.getInstance().apply { + add(Calendar.MONTH, -1) + }.timeInMillis + } + + fun getMaxDate(): Long { + return Calendar.getInstance().timeInMillis + } + } + + private val formElementList = mutableListOf() + private val dewormingDone = FormElement( + id = 6, + inputType = RADIO, + title = resources.getString(R.string.was_the_deworming_round_conducted), + entries = resources.getStringArray(R.array.yes_no_options), + required = true, + hasDependants = true + ) + + private val dewormingDate = FormElement( + id = 5, + inputType = DATE_PICKER, + title = resources.getString(R.string.date_of_ndd), + required = true, + min = getMinDate(), + max = getMaxDate(), + ) + + private val dewormingLocation = FormElement( + id = 4, + inputType = DROPDOWN, + title = resources.getString(R.string.place_of_ndd), + entries = resources.getStringArray(R.array.deworming_location_options), + required = true, + ) + + private val ageGroup = FormElement( + id = 3, + inputType = EDIT_TEXT, + title = resources.getString(R.string.number_of_out_of_school_children_sent_to_anganwad), + etMaxLength = 2, + etInputType = InputType.TYPE_CLASS_NUMBER, + required = true + ) + + private val pic1 = FormElement( + id = 1, + inputType = org.piramalswasthya.sakhi.model.InputType.FILE_UPLOAD, + title = resources.getString(R.string.upload_image), + arrayId = -1, + required = false + ) + + private val pic2 = FormElement( + id = 2, + inputType = org.piramalswasthya.sakhi.model.InputType.FILE_UPLOAD, + title = resources.getString(R.string.upload_image), + arrayId = -1, + required = false + ) + + suspend fun setUpPage(deworming: DewormingCache?) { + + if (pic1.value.isNullOrBlank()) { + pic1.value = "default" + } + + if (pic2.value.isNullOrBlank()) { + pic2.value = "default" + } + val list = mutableListOf( + dewormingDone, + ageGroup, + pic1, + pic2 + ) + deworming?.let { loadCachedData(it,list) } + formElementList.addAll(list) + setUpPage(list) + } + fun getFormElementList(): List = formElementList + private fun loadCachedData(deworming: DewormingCache, list: MutableList) { + dewormingDone.value = getLocalValueInArray(R.array.yes_no_options, deworming.dewormingDone) + dewormingDate.value = deworming.dewormingDate + dewormingLocation.value = getLocalValueInArray(R.array.deworming_location_options, deworming.dewormingLocation) + ageGroup.value = deworming.ageGroup?.toString() + pic1.value = deworming.image1 + pic2.value = deworming.image2 + + if (dewormingDone.value == dewormingDone.entries!!.first()) { + list.add(dewormingDone.getPosition() ,dewormingDate) + list.add(dewormingDone.getPosition() + 1,dewormingLocation) + } + } + + override suspend fun handleListOnValueChanged(formId: Int, index: Int): Int { + return when (formId) { + dewormingDone.id -> { + if (dewormingDone.value == dewormingDone.entries!!.first()) { + if (!formElementList.contains(dewormingDate)) { + formElementList.add(dewormingDate) + } + if (!formElementList.contains(dewormingLocation)) { + formElementList.add(dewormingLocation) + } + } + else{ + formElementList.remove(dewormingDate) + formElementList.remove(dewormingLocation) + } + triggerDependants( + source = dewormingDone, + passedIndex = index, + triggerIndex = 0, + target = listOf(dewormingDate,dewormingLocation), + ) + } + ageGroup.id -> { + validateEmptyOnEditText(ageGroup) + } + else -> -1 + } + } + + override fun mapValues(cacheModel: FormDataModel, pageNumber: Int) { + (cacheModel as DewormingCache).let { form -> + val formatter = SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH) + val currentDate = formatter.format(Date()) + form.dewormingDone = getEnglishValueInArray(R.array.yes_no_options, dewormingDone.value) ?: dewormingDone.value!! + form.dewormingDate = dewormingDate.value + form.dewormingLocation = getEnglishValueInArray(R.array.deworming_location_options, dewormingLocation.value) + form.ageGroup = ageGroup.value?.toInt() + form.image1 = pic1.value + form.image2 = pic2.value + form.regDate = currentDate + } + } + + + fun setImageUriToFormElement(lastImageFormId: Int, dpUri: Uri) { + when (lastImageFormId) { + pic1.id -> { + pic1.value = dpUri.toString() + pic1.errorText = null + } + pic2.id -> { + pic2.value = dpUri.toString() + pic2.errorText = null + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/EligibleCoupleRegistrationDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/EligibleCoupleRegistrationDataset.kt index d8df66614..c3d0bdcb9 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/configuration/EligibleCoupleRegistrationDataset.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/EligibleCoupleRegistrationDataset.kt @@ -1,13 +1,15 @@ package org.piramalswasthya.sakhi.configuration import android.content.Context +import android.net.Uri import android.text.InputType +import androidx.lifecycle.MutableLiveData +import org.piramalswasthya.sakhi.BuildConfig import org.piramalswasthya.sakhi.R import org.piramalswasthya.sakhi.database.room.SyncState import org.piramalswasthya.sakhi.helpers.Konstants import org.piramalswasthya.sakhi.helpers.Languages import org.piramalswasthya.sakhi.helpers.setToStartOfTheDay -import org.piramalswasthya.sakhi.model.AgeUnit import org.piramalswasthya.sakhi.model.BenBasicCache import org.piramalswasthya.sakhi.model.BenRegCache import org.piramalswasthya.sakhi.model.EligibleCoupleRegCache @@ -25,7 +27,12 @@ import java.util.Calendar import java.util.Locale import java.util.concurrent.TimeUnit -class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : +class EligibleCoupleRegistrationDataset( + var reqContext: Context, + var context: Context, + language: Languages, + var showDialogEvent: MutableLiveData +) : Dataset(context, language) { companion object { @@ -41,6 +48,12 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : return date?.time ?: throw IllegalStateException("Invalid date for dateReg") } + private fun getMinLmpMillis(): Long { + val cal = Calendar.getInstance() + cal.add(Calendar.DAY_OF_YEAR, -1 * 400) //before it is 280 + return cal.timeInMillis + } + private fun getMinDobMillis(): Long { val cal = Calendar.getInstance() cal.add(Calendar.YEAR, -15) @@ -51,6 +64,28 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : private fun getMaxDobMillis(): Long { return System.currentTimeMillis() } + + fun getMinimumSecondChildDob(firstChildDobStr: String?): String { + + val dateFormat = SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH) + val firstChildDob = dateFormat.parse(firstChildDobStr) + + val calendar = Calendar.getInstance() + calendar.time = firstChildDob!! + + // Instead of adding 12 months as minimum, allow same day or after 12 months + // So minimum acceptable DOB is the same date + return dateFormat.format(calendar.time) + + /* val dateFormat = SimpleDateFormat("dd-MM-yyyy", Locale.getDefault()) + val firstChildDob = dateFormat.parse(firstChildDobStr) + + val calendar = Calendar.getInstance() + calendar.time = firstChildDob!! + calendar.add(Calendar.DAY_OF_YEAR, 365) // Add 365 days (approx. 12 months) + + return dateFormat.format(calendar.time)*/ + } } //////////////////////////////// First Page ///////////////////////////////////////// @@ -63,7 +98,10 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : required = true, max = System.currentTimeMillis(), min = 0L, - hasDependants = true + hasDependants = true, + backgroundDrawable=R.drawable.ic_bg_circular, + iconDrawableRes=R.drawable.ic_anc_date, + showDrawable = true ) private val rchId = FormElement( @@ -76,7 +114,10 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : isMobileNumber = true, etMaxLength = 12, max = 999999999999L, - min = 0L + min = 0L, + backgroundDrawable=R.drawable.ic_bg_circular, + iconDrawableRes=R.drawable.ic_rch_id_no, + showDrawable = true ) private val name = FormElement( @@ -88,7 +129,10 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : allCaps = true, hasSpeechToText = false, etInputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS, - isEnabled = false + isEnabled = false, + backgroundDrawable=R.drawable.ic_bg_circular, + iconDrawableRes=R.drawable.ic_women, + showDrawable = true ) private val husbandName = FormElement( @@ -100,7 +144,10 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : allCaps = true, hasSpeechToText = false, etInputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS, - isEnabled = false + isEnabled = false, + backgroundDrawable=R.drawable.ic_bg_circular, + iconDrawableRes=R.drawable.ic_husbund, + showDrawable = true ) private val age = FormElement( @@ -113,6 +160,9 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : etMaxLength = 2, max = Konstants.maxAgeForGenBen.toLong(), min = Konstants.minAgeForGenBen.toLong(), + backgroundDrawable=R.drawable.ic_bg_circular, + iconDrawableRes=R.drawable.ic__reproductive_age, + showDrawable = true ) private val ageAtMarriage = FormElement( @@ -125,9 +175,68 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : etMaxLength = 2, max = Konstants.maxAgeForGenBen.toLong(), min = Konstants.minAgeForGenBen.toLong(), - isEnabled = false + isEnabled = false, + backgroundDrawable=R.drawable.ic_bg_circular, + iconDrawableRes=R.drawable.ic_age_of_women_at_marriage, + showDrawable = true + ) + + private var lmpDate = FormElement( + id = 73, + inputType = DATE_PICKER, + title = resources.getString(R.string.lmp_date), + arrayId = -1, + required = true, + max = System.currentTimeMillis(), + min = getMinLmpMillis(), + hasDependants = true, + backgroundDrawable=R.drawable.ic_bg_circular, + iconDrawableRes=R.drawable.ic_anc_date, + showDrawable = true ) + private val nayiPahelKitHandOver = FormElement( + id = 78, + inputType = RADIO, + title = resources.getString(R.string.ecrdset_is_nai_pahel_kit_handover), + arrayId = -1, + entries = resources.getStringArray(R.array.yes_no), + required = true, + hasDependants = true, + ) + + private val kithandOverDate = FormElement( + id = 74, + inputType = DATE_PICKER, + title = resources.getString(R.string.ecrdset_is_nai_pahel_kit_handover_date), + arrayId = -1, + required = true, + max = System.currentTimeMillis(), + min = 0L, + hasDependants = true, + showDrawable = true + ) + + private val ashaPhotoTitle = FormElement( + id = 77, + inputType = HEADLINE, + title = resources.getString(R.string.asha_photo), + arrayId = -1, + required = false + ) + + private val kitPhotoUploadOne = FormElement( + id = 75, + inputType = org.piramalswasthya.sakhi.model.InputType.FILE_UPLOAD, + title = context.getString(R.string.asha_photo_one), + required = false, + ) + private val kitPhotoUploadTwo = FormElement( + id = 76, + inputType = org.piramalswasthya.sakhi.model.InputType.FILE_UPLOAD, + title = context.getString(R.string.asha_photo_two), + required = false, + ) private val womanDetails = FormElement( id = 6, inputType = HEADLINE, @@ -157,9 +266,12 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : required = false, etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, isMobileNumber = true, - etMaxLength = 18, + etMaxLength = 20, max = 999999999999999999L, - min = 0L + min = 10000000L, + backgroundDrawable=R.drawable.ic_bg_circular, + iconDrawableRes=R.drawable.ic_bank_acc_no, + showDrawable = true ) private val bankName = FormElement( @@ -168,7 +280,10 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : title = resources.getString(R.string.ecrdset_bank_name), arrayId = -1, required = false, - etMaxLength = 50 + etMaxLength = 50, + backgroundDrawable=R.drawable.ic_bg_circular, + iconDrawableRes=R.drawable.ic_bank_name, + showDrawable = true ) private val branchName = FormElement( @@ -177,7 +292,10 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : title = resources.getString(R.string.ecrdset_branch_name), arrayId = -1, required = false, - etMaxLength = 50 + etMaxLength = 50, + backgroundDrawable=R.drawable.ic_bg_circular, + iconDrawableRes=R.drawable.ic_branch_name, + showDrawable = true ) private val ifsc = FormElement( @@ -187,6 +305,9 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : arrayId = -1, required = false, etMaxLength = 11, + backgroundDrawable=R.drawable.ic_bg_circular, + iconDrawableRes=R.drawable.ic_ifsc_code, + showDrawable = true ) private val noOfChildren = FormElement( @@ -194,11 +315,16 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : inputType = EDIT_TEXT, title = resources.getString(R.string.ecrdset_ttl_child_born), arrayId = -1, - required = true, + required = false, etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, etMaxLength = 1, max = 9, min = 0, + value = "0", + isEnabled = false, + backgroundDrawable=R.drawable.ic_bg_circular, + iconDrawableRes=R.drawable.ic_total_no_child_born, + showDrawable = true ) private val noOfLiveChildren = FormElement( @@ -206,11 +332,16 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : inputType = EDIT_TEXT, title = resources.getString(R.string.ecrdset_no_live_child), arrayId = -1, - required = true, + required = false, etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, etMaxLength = 1, max = 9, - min = 0 + value = "0", + isEnabled = false, + min = 0, + backgroundDrawable=R.drawable.ic_bg_circular, + iconDrawableRes=R.drawable.ic_no_of_live_child, + showDrawable = true ) private val numMale = FormElement( @@ -223,6 +354,10 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : etMaxLength = 1, max = 9, min = 0, + isEnabled = false, + backgroundDrawable=R.drawable.ic_bg_circular, + iconDrawableRes=R.drawable.ic_male, + showDrawable = true ) private val numFemale = FormElement( @@ -231,10 +366,15 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : title = resources.getString(R.string.ecrdset_female), arrayId = -1, required = false, + isEnabled = false, etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, etMaxLength = 1, max = 9, min = 0, + backgroundDrawable=R.drawable.ic_bg_circular, + iconDrawableRes=R.drawable.ic_female, + showDrawable = true + ) private val firstChildDetails = FormElement( @@ -242,7 +382,8 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : inputType = HEADLINE, title = resources.getString(R.string.ecrdset_dls_1_child), arrayId = -1, - required = false + required = false, + ) private val dob1 = FormElement( @@ -254,6 +395,10 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : hasDependants = true, max = getMaxDobMillis(), min = getMinDobMillis(), + isEnabled = false, + backgroundDrawable=R.drawable.ic_bg_circular, + iconDrawableRes=R.drawable.ic_anc_date, + showDrawable = true ) private val age1 = FormElement( @@ -263,10 +408,14 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : arrayId = -1, required = true, hasDependants = true, + isEnabled = false, etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, etMaxLength = 2, max = Konstants.maxAgeForAdolescent.toLong(), min = 0, + backgroundDrawable=R.drawable.ic_bg_circular, + iconDrawableRes=R.drawable.ic_no_of_live_child, + showDrawable = true ) private val gender1 = FormElement( @@ -274,6 +423,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : inputType = RADIO, title = resources.getString(R.string.ecrdset_1_child_sex), arrayId = -1, + isEnabled = false, entries = resources.getStringArray(R.array.ecr_gender_array), required = true, hasDependants = true, @@ -289,6 +439,10 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : etMaxLength = 2, max = 99, min = 0, + isEnabled = false, + backgroundDrawable=R.drawable.ic_bg_circular, + iconDrawableRes=R.drawable.ic_gap_bet_marriage_child, + showDrawable = true ) private val secondChildDetails = FormElement( @@ -306,6 +460,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : arrayId = -1, required = true, hasDependants = true, + isEnabled = false, max = getMaxDobMillis(), min = getMinDobMillis(), ) @@ -317,6 +472,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : arrayId = -1, required = true, hasDependants = true, + isEnabled = false, etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, etMaxLength = 2, max = Konstants.maxAgeForAdolescent.toLong(), @@ -328,6 +484,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : inputType = RADIO, title = resources.getString(R.string.ecrdset_2_child_sex), arrayId = -1, + isEnabled = false, entries = resources.getStringArray(R.array.ecr_gender_array), required = true, hasDependants = true, @@ -339,6 +496,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : title = resources.getString(R.string.ecrdset_gap_1_child_2_child), arrayId = -1, required = true, + isEnabled = false, etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, etMaxLength = 2, max = 99, @@ -360,6 +518,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : arrayId = -1, required = true, hasDependants = true, + isEnabled = false, max = getMaxDobMillis(), min = getMinDobMillis(), ) @@ -371,6 +530,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : arrayId = -1, required = true, hasDependants = true, + isEnabled = false, etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, etMaxLength = 2, max = Konstants.maxAgeForAdolescent.toLong(), @@ -382,6 +542,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : inputType = RADIO, title = resources.getString(R.string.ecrdset_3_child_sex), arrayId = -1, + isEnabled = false, entries = resources.getStringArray(R.array.ecr_gender_array), required = true, hasDependants = true, @@ -393,6 +554,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : title = resources.getString(R.string.ecrdset_gap_bet_2_3_child_sex), arrayId = -1, required = true, + isEnabled = false, etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, etMaxLength = 2, max = 99, @@ -414,6 +576,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : arrayId = -1, required = true, hasDependants = true, + isEnabled = false, max = getMaxDobMillis(), min = getMinDobMillis(), ) @@ -425,6 +588,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : arrayId = -1, required = true, hasDependants = true, + isEnabled = false, etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, etMaxLength = 2, max = Konstants.maxAgeForAdolescent.toLong(), @@ -436,6 +600,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : inputType = RADIO, title = resources.getString(R.string.ecrdset_4_child_sex), arrayId = -1, + isEnabled = false, entries = resources.getStringArray(R.array.ecr_gender_array), required = true, hasDependants = true, @@ -446,6 +611,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : inputType = TEXT_VIEW, title = resources.getString(R.string.ecrdset_bet_3_4_child), arrayId = -1, + isEnabled = false, required = true, etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, etMaxLength = 2, @@ -465,6 +631,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : id = 37, inputType = DATE_PICKER, title = resources.getString(R.string.ecrdset_5_child_bth), + isEnabled = false, arrayId = -1, required = true, hasDependants = true, @@ -475,6 +642,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : private val age5 = FormElement( id = 38, inputType = TEXT_VIEW, + isEnabled = false, title = resources.getString(R.string.ecrdset_5_child_age_yrs), arrayId = -1, required = true, @@ -489,6 +657,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : id = 39, inputType = RADIO, title = resources.getString(R.string.ecrdset_5_child_sex), + isEnabled = false, arrayId = -1, entries = resources.getStringArray(R.array.ecr_gender_array), required = true, @@ -501,6 +670,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : title = resources.getString(R.string.ecrdset_gap_4_5_child), arrayId = -1, required = true, + isEnabled = false, etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, etMaxLength = 2, max = 99, @@ -519,6 +689,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : id = 42, inputType = DATE_PICKER, title = resources.getString(R.string.ecrdset_dts_6_bth), + isEnabled = false, arrayId = -1, required = true, hasDependants = true, @@ -532,6 +703,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : title = resources.getString(R.string.ecrdset_6_child_age), arrayId = -1, required = true, + isEnabled = false, hasDependants = true, etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, etMaxLength = 2, @@ -543,6 +715,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : id = 44, inputType = RADIO, title = resources.getString(R.string.ecrdset_6_child_sex), + isEnabled = false, arrayId = -1, entries = resources.getStringArray(R.array.ecr_gender_array), required = true, @@ -555,6 +728,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : title = resources.getString(R.string.ecrdset_gap_5_6_child), arrayId = -1, required = true, + isEnabled = false, etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, etMaxLength = 2, max = 99, @@ -573,6 +747,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : id = 47, inputType = DATE_PICKER, title = resources.getString(R.string.ecrdset_7_child_bth), + isEnabled = false, arrayId = -1, required = true, hasDependants = true, @@ -587,6 +762,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : arrayId = -1, required = true, hasDependants = true, + isEnabled = false, etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, etMaxLength = 2, max = Konstants.maxAgeForAdolescent.toLong(), @@ -598,6 +774,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : inputType = RADIO, title = resources.getString(R.string.ecrdset_7_child_sex), arrayId = -1, + isEnabled = false, entries = resources.getStringArray(R.array.ecr_gender_array), required = true, hasDependants = true, @@ -609,6 +786,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : title = resources.getString(R.string.ecrdset_gap_6_7_child), arrayId = -1, required = true, + isEnabled = false, etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, etMaxLength = 2, max = 99, @@ -629,6 +807,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : title = resources.getString(R.string.ecrdset_8_child_bth), arrayId = -1, required = true, + isEnabled = false, hasDependants = true, max = getMaxDobMillis(), min = getMinDobMillis(), @@ -641,6 +820,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : arrayId = -1, required = true, hasDependants = true, + isEnabled = false, etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, etMaxLength = 2, max = Konstants.maxAgeForAdolescent.toLong(), @@ -651,6 +831,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : id = 54, inputType = RADIO, title = resources.getString(R.string.ecrdset_8_child_sex), + isEnabled = false, arrayId = -1, entries = resources.getStringArray(R.array.ecr_gender_array), required = true, @@ -663,6 +844,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : title = resources.getString(R.string.ecrdset_gap_7_8_child), arrayId = -1, required = true, + isEnabled = false, etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, etMaxLength = 2, max = 99, @@ -680,6 +862,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : private val dob9 = FormElement( id = 57, inputType = DATE_PICKER, + isEnabled = false, title = resources.getString(R.string.ecrdset_9_bth), arrayId = -1, required = true, @@ -692,6 +875,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : id = 58, inputType = TEXT_VIEW, title = resources.getString(R.string.ecrdset_9_age_yrs), + isEnabled = false, arrayId = -1, required = true, hasDependants = true, @@ -705,6 +889,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : id = 59, inputType = RADIO, title = resources.getString(R.string.ecrdset_9_child_sex), + isEnabled = false, arrayId = -1, entries = resources.getStringArray(R.array.ecr_gender_array), required = true, @@ -716,6 +901,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : id = 60, inputType = TEXT_VIEW, title = resources.getString(R.string.ecrdset_gap_8_9_child), + isEnabled = false, arrayId = -1, required = true, etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, @@ -834,10 +1020,17 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : private var lastDeliveryDate = 0L + + fun getIndexofAshaKitPhotoOne() = getIndexById(kitPhotoUploadOne.id) + fun getIndexofAshaKitPhoto() = getIndexById(kitPhotoUploadTwo.id) + + + suspend fun setUpPage( ben: BenRegCache?, assess: HRPNonPregnantAssessCache?, - saved: EligibleCoupleRegCache? + saved: EligibleCoupleRegCache?, + childList: List, ) { val list = mutableListOf( dateOfReg, @@ -846,13 +1039,11 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : husbandName, // age, ageAtMarriage, + lmpDate, + nayiPahelKitHandOver, womanDetails, // aadharNo, - bankAccount, - bankName, - branchName, - ifsc, - noOfChildren, +// noOfChildren, noOfLiveChildren, numMale, numFemale, @@ -870,6 +1061,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : pastCSection, ) dateOfReg.value = getDateFromLong(System.currentTimeMillis()) +// lmpDate.value = getDateFromLong(1735237800000L) // dateOfReg.value?.let { // val long = Dataset.getLongFromDate(it) // dateOfhivTestDone.min = long @@ -922,18 +1114,86 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : || medicalIssues.value == resources.getStringArray(R.array.yes_no)[0] || pastCSection.value == resources.getStringArray( R.array.yes_no )[0]) + + } + var insertIndex = list.indexOf(noOfLiveChildren) + 1 + + noOfLiveChildren.value = childList.size.coerceAtMost(9).toString() + val limitedChildList = childList.take(9) + numFemale.value = limitedChildList.count { it.gender == Gender.FEMALE }.toString() + numMale.value = limitedChildList.count { it.gender == Gender.MALE }.toString() + childList.take(9).forEachIndexed { index, child -> + val bundle = children[index] + bundle.dob.value = getDateFromLong(child.dob) + bundle.age.value = child.age?.toString() + + bundle.gender.value = getLocalValueInArray( + R.array.ecr_gender_array, + child.gender?.name + ?.lowercase() + ?.replaceFirstChar { it.uppercase() } + ) + + if (index == 0) { + setSiblingAgeDiff(timeAtMarriage, child.dob, bundle.gap) + } else { + setSiblingAgeDiff( + childList[index - 1].dob, + child.dob, + bundle.gap + ) + } + + val childViews = listOf( + bundle.dob, + bundle.age, + bundle.gender, + bundle.gap + ) + + list.addAll(insertIndex, childViews) + + insertIndex += childViews.size } + saved?.let { ecCache -> dateOfReg.value = getDateFromLong(ecCache.dateOfReg) bankAccount.value = ecCache.bankAccount?.toString() bankName.value = ecCache.bankName branchName.value = ecCache.branchName ifsc.value = ecCache.ifsc - noOfChildren.value = ecCache.noOfChildren.toString() - noOfLiveChildren.value = ecCache.noOfLiveChildren.toString() - numMale.value = ecCache.noOfMaleChildren.toString() - numFemale.value = ecCache.noOfFemaleChildren.toString() - if (ecCache.noOfLiveChildren > 0) { +// noOfChildren.value = ecCache.noOfChildren.toString() +// noOfLiveChildren.value = ecCache.noOfLiveChildren.toString() + lmpDate.value = getDateFromLong(ecCache.lmpDate) +// numMale.value = ecCache.noOfMaleChildren.toString() +// numFemale.value = ecCache.noOfFemaleChildren.toString() + val isKitHandedOver = ecCache.isKitHandedOver == true + nayiPahelKitHandOver.value = if (isKitHandedOver) nayiPahelKitHandOver.entries!![0] else nayiPahelKitHandOver.entries!![1] + if (isKitHandedOver) { + list.addAll( + list.indexOf(nayiPahelKitHandOver) + 1, + listOf(kithandOverDate) + ) + if (!BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true)) { + list.addAll( + list.indexOf(kithandOverDate) + 1, + listOf(ashaPhotoTitle,kitPhotoUploadOne, kitPhotoUploadTwo) + ) + } + + + + + ecCache.kitHandedOverDate?.let { kithandOverDate.value = getDateFromLong(it) } + kitPhotoUploadOne.value = ecCache.kitPhoto1 + kitPhotoUploadTwo.value = ecCache.kitPhoto2 + } else { + kitPhotoUploadOne.value = null + kitPhotoUploadTwo.value = null + } + + + /* if (ecCache.noOfLiveChildren > 0) { ecCache.dob1?.let { dob1.value = getDateFromLong(it) age1.value = if (BenBasicCache.getAgeUnitFromDob(it) @@ -1118,12 +1378,13 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : list.indexOf(seventhAndEighthChildGap) + 1, listOf(ninthChildDetails, dob9, age9, gender9, eighthAndNinthChildGap) ) - } + }*/ } setUpPage(list) } + override suspend fun handleListOnValueChanged(formId: Int, index: Int): Int { return when (formId) { @@ -1133,6 +1394,41 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : // updateAgeCheck(dateOfBirth, getLongFromDate(dateOfReg.value)) // handleListOnValueChanged(ageCheck.id, 0) // } + nayiPahelKitHandOver.id -> { + nayiPahelKitHandOver.isEnabled = true + if (nayiPahelKitHandOver.value == resources.getStringArray(R.array.yes_no)[0] ) + { + + showDialogEvent.value = "Please upload photo of \"couple with Nayi Pahal kit\", to claim your Incentive." + var list1: List = emptyList() + if (!BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true)) { + list1 = listOf( + kithandOverDate, + ashaPhotoTitle, + kitPhotoUploadOne, + kitPhotoUploadTwo + ) + } else { + list1 = listOf(kithandOverDate) + } + triggerDependants( + source = nayiPahelKitHandOver, + passedIndex = index, + triggerIndex = 0, + target =list1 //listOf(kithandOverDate, ashaPhotoTitle,kitPhotoUploadOne, kitPhotoUploadTwo), + ) + } else if (nayiPahelKitHandOver.value == resources.getStringArray(R.array.yes_no)[1]) { + triggerforHide( + source = nayiPahelKitHandOver, + passedIndex = index, + triggerIndex = 1, + target = kithandOverDate, + targetSideEffect = listOf(kithandOverDate,ashaPhotoTitle, kitPhotoUploadOne, kitPhotoUploadTwo), + ) + } + 0 + } + rchId.id -> { validateRchIdOnEditText(rchId) } @@ -1142,7 +1438,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : } bankAccount.id -> { - validateAllZerosOnEditText(bankAccount) + validateIntMinMax(bankAccount) } bankName.id -> { @@ -1157,6 +1453,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : validateAllAlphaNumericOnEditText(ifsc) } + ageAtMarriage.id -> { if (ageAtMarriage.value.isNullOrEmpty()) { validateEmptyOnEditText(ageAtMarriage) @@ -1211,97 +1508,107 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : assignValuesToAgeFromDob(dob1Long, age1) validateIntMinMax(age1) setSiblingAgeDiff(timeAtMarriage, dob1Long, marriageFirstChildGap) - dob2.min = dob1Long + // dob2.min = dob1Long + dob2.min = getLongFromDate(getMinimumSecondChildDob(dob1.value)) updateTimeLessThan18() } -1 } dob2.id -> { + // dob2.inputType = EDIT_TEXT + isValidChildGap(dob2,dob1.value) if (dob1.value != null && dob2.value != null) { val dob2Long = getLongFromDate(dob2.value) val dob1Long = getLongFromDate(dob1.value) assignValuesToAgeFromDob(dob2Long, age2) setSiblingAgeDiff(dob1Long, dob2Long, firstAndSecondChildGap) - dob3.min = dob2Long + dob3.min = getLongFromDate(getMinimumSecondChildDob(dob2.value)) //dob2Long updateTimeLessThan18() } -1 } dob3.id -> { + isValidChildGap(dob3,dob2.value) if (dob2.value != null && dob3.value != null) { val dob2Long = getLongFromDate(dob2.value) val dob3Long = getLongFromDate(dob3.value) assignValuesToAgeFromDob(dob3Long, age3) setSiblingAgeDiff(dob2Long, dob3Long, secondAndThirdChildGap) - dob4.min = dob3Long + dob4.min = getLongFromDate(getMinimumSecondChildDob(dob3.value))//dob3Long updateTimeLessThan18() } -1 } dob4.id -> { + isValidChildGap(dob4,dob3.value) if (dob3.value != null && dob4.value != null) { val dob3Long = getLongFromDate(dob3.value) val dob4Long = getLongFromDate(dob4.value) assignValuesToAgeFromDob(dob4Long, age4) setSiblingAgeDiff(dob3Long, dob4Long, thirdAndFourthChildGap) - dob5.min = dob4Long + dob5.min = getLongFromDate(getMinimumSecondChildDob(dob4.value))//dob4Long updateTimeLessThan18() } -1 } dob5.id -> { + isValidChildGap(dob5,dob4.value) if (dob4.value != null && dob5.value != null) { val dob4Long = getLongFromDate(dob4.value) val dob5Long = getLongFromDate(dob5.value) assignValuesToAgeFromDob(dob5Long, age5) setSiblingAgeDiff(dob4Long, dob5Long, fourthAndFifthChildGap) - dob6.min = dob5Long + dob6.min = getLongFromDate(getMinimumSecondChildDob(dob5.value)) //dob5Long updateTimeLessThan18() } -1 } dob6.id -> { + isValidChildGap(dob6,dob5.value) if (dob5.value != null && dob6.value != null) { val dob5Long = getLongFromDate(dob5.value) val dob6Long = getLongFromDate(dob6.value) assignValuesToAgeFromDob(dob6Long, age6) setSiblingAgeDiff(dob5Long, dob6Long, fifthAndSixthChildGap) - dob7.min = dob6Long + dob7.min = getLongFromDate(getMinimumSecondChildDob(dob6.value)) //dob6Long updateTimeLessThan18() } -1 } dob7.id -> { + isValidChildGap(dob7,dob6.value) if (dob6.value != null && dob7.value != null) { val dob6Long = getLongFromDate(dob6.value) val dob7Long = getLongFromDate(dob7.value) assignValuesToAgeFromDob(dob7Long, age7) setSiblingAgeDiff(dob6Long, dob7Long, sixthAndSeventhChildGap) - dob8.min = dob7Long + dob8.min =getLongFromDate(getMinimumSecondChildDob(dob7.value))// dob7Long updateTimeLessThan18() } -1 } dob8.id -> { + isValidChildGap(dob8,dob7.value) if (dob7.value != null && dob8.value != null) { val dob7Long = getLongFromDate(dob7.value) val dob8Long = getLongFromDate(dob8.value) assignValuesToAgeFromDob(dob8Long, age8) setSiblingAgeDiff(dob7Long, dob8Long, seventhAndEighthChildGap) - dob9.min = dob8Long + dob9.min =getLongFromDate(getMinimumSecondChildDob(dob8.value)) // dob8Long updateTimeLessThan18() } -1 } dob9.id -> { + isValidChildGap(dob9,dob8.value) if (dob8.value != null && dob9.value != null) { val dob8Long = getLongFromDate(dob8.value) val dob9Long = getLongFromDate(dob9.value) @@ -1720,12 +2027,15 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : timeInMillis = new } val diff = getDiffYears(calOld, calNew) - target.value = diff.toString() + target.value = "${diff.toString()} years" } override fun mapValues(cacheModel: FormDataModel, pageNumber: Int) { (cacheModel as EligibleCoupleRegCache).let { ecr -> ecr.dateOfReg = getLongFromDate(dateOfReg.value!!) + lmpDate.value?.let { + ecr.lmpDate = getLongFromDate(it) + } ecr.bankAccount = bankAccount.value?.takeIf { it.isNotBlank() }?.toLong() ecr.bankName = bankName.value ecr.branchName = branchName.value @@ -1741,8 +2051,12 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : gender1.entries!![1] -> Gender.FEMALE else -> null } - ecr.marriageFirstChildGap = - marriageFirstChildGap.value?.takeIf { it.isNotBlank() }?.toInt() + ecr.isKitHandedOver = nayiPahelKitHandOver.value == nayiPahelKitHandOver.entries!![0] + ecr.kitHandedOverDate = getLongFromDate(kithandOverDate.value) + ecr.kitPhoto1 = kitPhotoUploadOne.value + ecr.kitPhoto2 = kitPhotoUploadTwo.value + ecr.marriageFirstChildGap =marriageFirstChildGap.value?.filter { it.isDigit() }?.toIntOrNull() ?: 0 + // marriageFirstChildGap.value?.takeIf { it.isNotBlank() }?.toInt() if (noOfLiveChildren.value?.toInt()!! > 1) { ecr.dob2 = getLongFromDate(dob2.value) ecr.age2 = age2.value?.toInt() @@ -1751,8 +2065,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : gender2.entries!![1] -> Gender.FEMALE else -> null } - ecr.firstAndSecondChildGap = - firstAndSecondChildGap.value?.takeIf { it.isNotBlank() }?.toInt() + ecr.firstAndSecondChildGap =firstAndSecondChildGap.value?.filter { it.isDigit() }?.toIntOrNull() ?: 0 } if (noOfLiveChildren.value?.toInt()!! > 2) { ecr.dob3 = getLongFromDate(dob3.value) @@ -1762,8 +2075,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : gender3.entries!![1] -> Gender.FEMALE else -> null } - ecr.secondAndThirdChildGap = - secondAndThirdChildGap.value?.takeIf { it.isNotBlank() }?.toInt() + ecr.secondAndThirdChildGap =secondAndThirdChildGap.value?.filter { it.isDigit() }?.toIntOrNull() ?: 0 } if (noOfLiveChildren.value?.toInt()!! > 3) { ecr.dob4 = getLongFromDate(dob4.value) @@ -1773,8 +2085,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : gender4.entries!![1] -> Gender.FEMALE else -> null } - ecr.thirdAndFourthChildGap = - thirdAndFourthChildGap.value?.takeIf { it.isNotBlank() }?.toInt() + ecr.thirdAndFourthChildGap =thirdAndFourthChildGap.value?.filter { it.isDigit() }?.toIntOrNull() ?: 0 } if (noOfLiveChildren.value?.toInt()!! > 4) { ecr.dob5 = getLongFromDate(dob5.value) @@ -1784,8 +2095,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : gender5.entries!![1] -> Gender.FEMALE else -> null } - ecr.fourthAndFifthChildGap = - fourthAndFifthChildGap.value?.takeIf { it.isNotBlank() }?.toInt() + ecr.fourthAndFifthChildGap =fourthAndFifthChildGap.value?.filter { it.isDigit() }?.toIntOrNull() ?: 0 } if (noOfLiveChildren.value?.toInt()!! > 5) { ecr.dob6 = getLongFromDate(dob6.value) @@ -1795,8 +2105,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : gender6.entries!![1] -> Gender.FEMALE else -> null } - ecr.fifthANdSixthChildGap = - fifthAndSixthChildGap.value?.takeIf { it.isNotBlank() }?.toInt() + ecr.fifthANdSixthChildGap =fifthAndSixthChildGap.value?.filter { it.isDigit() }?.toIntOrNull() ?: 0 } if (noOfLiveChildren.value?.toInt()!! > 6) { ecr.dob7 = getLongFromDate(dob7.value) @@ -1806,8 +2115,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : gender7.entries!![1] -> Gender.FEMALE else -> null } - ecr.sixthAndSeventhChildGap = - sixthAndSeventhChildGap.value?.takeIf { it.isNotBlank() }?.toInt() + ecr.sixthAndSeventhChildGap =sixthAndSeventhChildGap.value?.filter { it.isDigit() }?.toIntOrNull() ?: 0 } if (noOfLiveChildren.value?.toInt()!! > 7) { ecr.dob8 = getLongFromDate(dob8.value) @@ -1817,8 +2125,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : gender8.entries!![1] -> Gender.FEMALE else -> null } - ecr.seventhAndEighthChildGap = - seventhAndEighthChildGap.value?.takeIf { it.isNotBlank() }?.toInt() + ecr.seventhAndEighthChildGap = seventhAndEighthChildGap.value?.filter { it.isDigit() }?.toIntOrNull() ?: 0 } if (noOfLiveChildren.value?.toInt()!! > 8) { ecr.dob9 = getLongFromDate(dob9.value) @@ -1828,8 +2135,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : gender9.entries!![1] -> Gender.FEMALE else -> null } - ecr.eighthAndNinthChildGap = - eighthAndNinthChildGap.value?.takeIf { it.isNotBlank() }?.toInt() + ecr.eighthAndNinthChildGap = eighthAndNinthChildGap.value?.filter { it.isDigit() }?.toIntOrNull() ?: 0 } } } @@ -1846,21 +2152,7 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : isUpdated = true } } -// aadharNo.value?.takeIf { -// aadharNo.inputType == EDIT_TEXT && -// it.isNotEmpty() -// }?.let { -// val last4 = "*".repeat(8) + it.takeLast(4) -// if ( -// last4 -// != aadharNoFromBen -// ) { -// ben?.hasAadhar = true -// ben?.hasAadharId = 1 -// ben?.aadharNum = last4 -// isUpdated = true -// } -// } + isHighRisk().let { highRisk -> if (highRisk) { ben?.isHrpStatus = true @@ -1960,4 +2252,51 @@ class EligibleCoupleRegistrationDataset(context: Context, language: Languages) : fun getIndexOfGap9() = getIndexById(eighthAndNinthChildGap.id) fun getIndexOfTimeLessThan18() = getIndexById(timeLessThan18m.id) + + fun setImageUriToFormElement(lastImageFormId: Int, dpUri: Uri) { + if (lastImageFormId == 75) { + kitPhotoUploadOne.value = dpUri.toString() + kitPhotoUploadOne.errorText = null + + } else { + kitPhotoUploadTwo.value = dpUri.toString() + kitPhotoUploadTwo.errorText = null + + } + + } + + private val children = listOf( + ChildBundle(firstChildDetails, dob1, age1, gender1, marriageFirstChildGap), + ChildBundle(secondChildDetails, dob2, age2, gender2, firstAndSecondChildGap), + ChildBundle(thirdChildDetails, dob3, age3, gender3, secondAndThirdChildGap), + ChildBundle(fourthChildDetails, dob4, age4, gender4, thirdAndFourthChildGap), + ChildBundle(fifthChildDetails, dob5, age5, gender5, fourthAndFifthChildGap), + ChildBundle(sixthChildDetails, dob6, age6, gender6, fifthAndSixthChildGap), + ChildBundle(seventhChildDetails, dob7, age7, gender7, sixthAndSeventhChildGap), + ChildBundle(eighthChildDetails, dob8, age8, gender8, seventhAndEighthChildGap), + ChildBundle(ninthChildDetails, dob9, age9, gender9, eighthAndNinthChildGap) + ) + + data class ChildBundle( + val details: FormElement, + val dob: FormElement, + val age: FormElement, + val gender: FormElement, + val gap: FormElement + ) { + fun isEmpty(): Boolean { + return dob.value.isNullOrEmpty() && + age.value.isNullOrEmpty() && + gender.value.isNullOrEmpty() + } + fun toFormList() = listOf(details, dob, age, gender, gap) + fun clearValues() { + + dob.value = null + age.value = null + gender.value = null + gap.value = null + } + } } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/EligibleCoupleTrackingDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/EligibleCoupleTrackingDataset.kt index 84099366a..b893fc144 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/configuration/EligibleCoupleTrackingDataset.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/EligibleCoupleTrackingDataset.kt @@ -1,7 +1,10 @@ package org.piramalswasthya.sakhi.configuration import android.content.Context +import android.net.Uri +import org.piramalswasthya.sakhi.BuildConfig import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.configuration.PregnantWomanRegistrationDataset.Companion.getMinLmpMillis import org.piramalswasthya.sakhi.database.room.SyncState import org.piramalswasthya.sakhi.helpers.Languages import org.piramalswasthya.sakhi.helpers.setToStartOfTheDay @@ -9,12 +12,17 @@ import org.piramalswasthya.sakhi.model.BenRegCache import org.piramalswasthya.sakhi.model.EligibleCoupleTrackingCache import org.piramalswasthya.sakhi.model.FormElement import org.piramalswasthya.sakhi.model.InputType +import java.text.SimpleDateFormat import java.util.Calendar +import java.util.Locale class EligibleCoupleTrackingDataset( context: Context, currentLanguage: Languages ) : Dataset(context, currentLanguage) { + var antraDoseValue="N/A" + var noOfChildrens=-1 + private var dateOfVisit = FormElement( id = 1, inputType = InputType.DATE_PICKER, @@ -26,6 +34,35 @@ class EligibleCoupleTrackingDataset( ) + private var dateOfSterilisation = FormElement( + id = 1, + inputType = InputType.DATE_PICKER, + title = resources.getString(R.string.date_of_sterilisation), + arrayId = -1, + required = true, + min = Calendar.getInstance().apply { + set(Calendar.DAY_OF_MONTH, 1) + set(Calendar.HOUR_OF_DAY, 0) + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + }.timeInMillis, + max = System.currentTimeMillis(), + hasDependants = true + + ) + + private var lmpDate = FormElement( + id = 11, + inputType = InputType.DATE_PICKER, + title = resources.getString(R.string.lmp_date), + arrayId = -1, + required = true, + max = System.currentTimeMillis(), + min = getMinLmpMillis(), + hasDependants = true + ) + private val financialYear = FormElement( id = 2, inputType = InputType.TEXT_VIEW, @@ -77,9 +114,27 @@ class EligibleCoupleTrackingDataset( required = false, hasDependants = true ) + // Q2 – Only for Mitanin + private val usingFamilyPlanningMitanin = FormElement( + id = 8, + inputType = InputType.RADIO, + title = resources.getString(R.string.ectdset_fly_plan_mthd_mitanin), + entries = resources.getStringArray(R.array.ectdset_yes_no), + required = false, + hasDependants = true + ) + + private val wantToUseFamilyPlanning = FormElement( + id = 9, + inputType = InputType.RADIO, + title = resources.getString(R.string.ectdset_want_to_use_fly_plan_mthd_mitanin), + entries = resources.getStringArray(R.array.ectdset_yes_no), + required = false, + hasDependants = true + ) private val methodOfContraception = FormElement( - id = 8, + id = 10, inputType = InputType.DROPDOWN, title = resources.getString(R.string.ectdset_mthd_conpt), arrayId = R.array.method_of_contraception, @@ -89,8 +144,19 @@ class EligibleCoupleTrackingDataset( ) + private val antraDoses = FormElement( + id = 11, + inputType = InputType.DROPDOWN, + title = "Antra injection", + arrayId = R.array.antra_doses, + entries = resources.getStringArray(R.array.antra_doses), + required = true, + hasDependants = true + + ) + private val anyOtherMethod = FormElement( - id = 9, + id = 12, inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.ectdset_other_mthd), required = true, @@ -98,23 +164,74 @@ class EligibleCoupleTrackingDataset( etMaxLength = 50 ) + private var dateOfAntraInjection = FormElement( + id = 13, + inputType = InputType.DATE_PICKER, + title = context.getString(R.string.date_of_antra_injection), + arrayId = -1, + required = true, + max = System.currentTimeMillis(), + hasDependants = true + + ) + private var dueDateOfAntraInjection = FormElement( + id = 14, + inputType = InputType.TEXT_VIEW, + title = context.getString(R.string.due_date_of_next_injection), + required = false, + + ) + private val mpaFileUpload1 = FormElement( + id = 23, + inputType = InputType.FILE_UPLOAD, + title = context.getString(R.string.mpa_card), + required = false, + ) + + + private val deliveryDischargeSummary1 = FormElement( + id = 60, + inputType = InputType.FILE_UPLOAD, + title = resources.getString(R.string.ectdset_discharge_summary_1), + required = false + + ) + private val deliveryDischargeSummary2 = FormElement( + id =61, + inputType = InputType.FILE_UPLOAD, + title = resources.getString(R.string.ectdset_discharge_summary_2), + required = false + ) + var lastDose: String? = null + var lastDateofDose: String? = null fun getIndexOfIsPregnant() = getIndexById(isPregnant.id) suspend fun setUpPage( ben: BenRegCache?, dateOfReg: Long, lastTrack: EligibleCoupleTrackingCache?, - saved: EligibleCoupleTrackingCache? + saved: EligibleCoupleTrackingCache?, + noOfChildren: Int? ) { + noOfChildrens = noOfChildren!! + lastDose = lastTrack?.antraDose + lastDateofDose = lastTrack?.dateOfAntraInjection + + methodOfContraception.entries = + if (noOfChildren == 0) + resources.getStringArray(R.array.method_of_contraception_for_zero_child) + else + resources.getStringArray(R.array.method_of_contraception) + val list = mutableListOf( dateOfVisit, + lmpDate, financialYear, month, isPregnancyTestDone, - isPregnant, -// usingFamilyPlanning, ) + if (saved == null) { dateOfVisit.value = getDateFromLong(System.currentTimeMillis()) dateOfVisit.value?.let { @@ -123,6 +240,10 @@ class EligibleCoupleTrackingDataset( resources.getStringArray(R.array.visit_months)[Companion.getMonth(it)!!] } + if (ben != null) { + dateOfAntraInjection.min = ben.regDate + } + dateOfVisit.min = lastTrack?.let { Calendar.getInstance().apply { timeInMillis = it.visitDate @@ -137,111 +258,524 @@ class EligibleCoupleTrackingDataset( setToStartOfTheDay() }.timeInMillis } ?: dateOfReg + } else { + + // -------- EDIT ENTRY -------- + dateOfVisit.value = getDateFromLong(saved.visitDate) + dateOfSterilisation.value = getDateFromLong(saved.dateOfSterilisation) + lmpDate.value = + if (saved.lmpDate != null) + getDateFromLong(saved.lmpDate) + else + getDateFromLong(System.currentTimeMillis()) + financialYear.value = getFinancialYear(dateString = dateOfVisit.value) month.value = resources.getStringArray(R.array.visit_months)[Companion.getMonth(dateOfVisit.value)!!] + isPregnancyTestDone.value = getLocalValueInArray(R.array.yes_no, saved.isPregnancyTestDone) + if (isPregnancyTestDone.value == resources.getStringArray(R.array.yes_no)[0]) { + list.add(list.indexOf(isPregnancyTestDone) + 1, pregnancyTestResult) - pregnancyTestResult.value = saved.pregnancyTestResult - } - isPregnant.value = getLocalValueInArray(R.array.yes_no, saved.isPregnant) - if (isPregnant.value == resources.getStringArray(R.array.yes_no)[1]) { - list.add(usingFamilyPlanning) - saved.usingFamilyPlanning?.let { + pregnancyTestResult.value = getLocalValueInArray(R.array.ectdset_po_neg, saved.pregnancyTestResult) + + } else { + + // Restore FP answers + if (!BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true)) { + usingFamilyPlanning.value = - if (it) resources.getStringArray(R.array.yes_no)[0] else resources.getStringArray( - R.array.yes_no - )[1] + if (saved.usingFamilyPlanning == true) + resources.getStringArray(R.array.yes_no)[0] + else + resources.getStringArray(R.array.yes_no)[1] + + if (saved.usingFamilyPlanning == true) { + list.add(methodOfContraception) + } + + } else { + + usingFamilyPlanningMitanin.value = + if (saved.usingFamilyPlanning == true) + resources.getStringArray(R.array.ectdset_yes_no)[0] + else + resources.getStringArray(R.array.ectdset_yes_no)[1] + + if (saved.usingFamilyPlanning == true) { + list.add(methodOfContraception) + } else { + list.add(wantToUseFamilyPlanning) + + if (saved.methodOfContraception != null) { + wantToUseFamilyPlanning.value = + resources.getStringArray(R.array.ectdset_yes_no)[0] + list.add(methodOfContraception) + } + } } - usingFamilyPlanning.value = - if (saved.usingFamilyPlanning == true) resources.getStringArray(R.array.yes_no)[1] else resources.getStringArray( - R.array.yes_no - )[1] - if (saved.usingFamilyPlanning == true) { - list.add(methodOfContraception) - if (saved.methodOfContraception in resources.getStringArray(R.array.method_of_contraception)) { - methodOfContraception.value = saved.methodOfContraception - } else if (saved.methodOfContraception != null) { - methodOfContraception.value = - resources.getStringArray(R.array.method_of_contraception).last() - list.add(anyOtherMethod) - anyOtherMethod.value = saved.methodOfContraception + + // Restore FP method details + saved.methodOfContraception?.let { method -> + val methods = resources.getStringArray(R.array.method_of_contraception) + val sterilizationIndices = listOf(7, 8) + val methodPart = method.split("/")[0] + val localMethodPart = getLocalValueInArray(R.array.method_of_contraception, methodPart) ?: methodPart + + when { + localMethodPart in methods && !method.contains("/") -> { + methodOfContraception.value = localMethodPart + val selectedIndex = methods.indexOf(localMethodPart) + + if (selectedIndex in sterilizationIndices && + !BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true) + ) { + list.add(deliveryDischargeSummary1) + list.add(deliveryDischargeSummary2) + deliveryDischargeSummary1.value = saved.dischargeSummary1 + deliveryDischargeSummary2.value = saved.dischargeSummary2 + } + } + + localMethodPart == methods[1] -> { + methodOfContraception.value = methods[1] + list.add(antraDoses) + list.add(dateOfAntraInjection) + list.add(dueDateOfAntraInjection) + + if (!BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true)) { + list.add(mpaFileUpload1) + mpaFileUpload1.value = saved.mpaFile + } + + dateOfAntraInjection.value = saved.dateOfAntraInjection + dueDateOfAntraInjection.value = saved.dueDateOfAntraInjection + + if (saved.antraDose != null) { + antraDoseValue = saved.antraDose!! + antraDoses.value = saved.antraDose + } + } + + else -> { + methodOfContraception.value = methods.last() + list.add(anyOtherMethod) + anyOtherMethod.value = method + } } } } + isPregnant.value = getLocalValueInArray(R.array.yes_no, saved.isPregnant) } - setUpPage(list) + val nextDose = getNextDose(lastDose, lastDateofDose, dateOfVisit.value!!) + antraDoses.value = nextDose + antraDoseValue = nextDose + antraDoses.isEnabled = false + + setUpPage(list) } + override suspend fun handleListOnValueChanged(formId: Int, index: Int): Int { return when (formId) { dateOfVisit.id -> { financialYear.value = Companion.getFinancialYear(dateOfVisit.value) - month.value = - resources.getStringArray(R.array.visit_months)[Companion.getMonth(dateOfVisit.value)!!] + month.value = resources.getStringArray(R.array.visit_months)[Companion.getMonth(dateOfVisit.value)!!] + val nextDose = getNextDose(lastDose, lastDateofDose, dateOfVisit.value!!) + antraDoses.value = nextDose + antraDoseValue=nextDose + antraDoses.isEnabled = false + -1 + } + dateOfAntraInjection.id -> { + val injectionDate = dateOfAntraInjection.value ?: "" + + val (minDate, maxDate) = calculateNextInjectionDate(injectionDate, 76, 120) + + dueDateOfAntraInjection.value = + if (minDate.isNotEmpty() && maxDate.isNotEmpty()) { + "$minDate to $maxDate" + } else { + resources.getString(R.string.invalid_injection_date) + } -1 } isPregnancyTestDone.id -> { + antraDoses.value = antraDoseValue isPregnant.isEnabled = true - triggerDependants( - source = isPregnancyTestDone, - passedIndex = index, - triggerIndex = 0, - target = pregnancyTestResult - ) + if (isPregnancyTestDone.value == resources.getStringArray(R.array.ectdset_yes_no_dont)[0]) { + triggerDependants( + source = isPregnancyTestDone, + removeItems = listOf(isPregnant,usingFamilyPlanning,usingFamilyPlanningMitanin,wantToUseFamilyPlanning,methodOfContraception,antraDoses,dateOfAntraInjection,dueDateOfAntraInjection,anyOtherMethod,mpaFileUpload1,dateOfSterilisation,deliveryDischargeSummary1,deliveryDischargeSummary2), + addItems = listOf(pregnancyTestResult) + ) + } + else{ + if (!BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true)) { + triggerDependants( + source = isPregnancyTestDone, + removeItems = listOf(isPregnant,pregnancyTestResult,usingFamilyPlanningMitanin,wantToUseFamilyPlanning,methodOfContraception,antraDoses,dateOfAntraInjection,dueDateOfAntraInjection,anyOtherMethod,mpaFileUpload1,dateOfSterilisation,deliveryDischargeSummary1,deliveryDischargeSummary2), + addItems = listOf(usingFamilyPlanning) + ) + }else{ + triggerDependants( + source = isPregnancyTestDone, + removeItems = listOf(isPregnant,pregnancyTestResult,usingFamilyPlanning,methodOfContraception,antraDoses,dateOfAntraInjection,dueDateOfAntraInjection,anyOtherMethod,mpaFileUpload1,dateOfSterilisation,deliveryDischargeSummary1,deliveryDischargeSummary2,wantToUseFamilyPlanning), + addItems = listOf(usingFamilyPlanningMitanin) + ) + } + } + + return 0 } pregnancyTestResult.id -> { + antraDoses.value = antraDoseValue if (pregnancyTestResult.value == resources.getStringArray(R.array.ectdset_po_neg)[0]) { isPregnant.value = resources.getStringArray(R.array.yes_no)[0] isPregnant.isEnabled = false - } else { + + if (!BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true)) { + triggerDependants( + source = pregnancyTestResult, + passedIndex = index, + triggerIndex = 0, + target = isPregnant, + targetSideEffect = listOf(isPregnant,usingFamilyPlanning,methodOfContraception,anyOtherMethod,antraDoses,dateOfAntraInjection,dueDateOfAntraInjection,mpaFileUpload1,dateOfSterilisation,deliveryDischargeSummary1,deliveryDischargeSummary2) + ) + triggerforHide( + source = pregnancyTestResult, + passedIndex = index, + triggerIndex = 1, + target = usingFamilyPlanning, + targetSideEffect = listOf(usingFamilyPlanning,methodOfContraception,antraDoses,dateOfAntraInjection,dueDateOfAntraInjection,anyOtherMethod,mpaFileUpload1,dateOfSterilisation,deliveryDischargeSummary1,deliveryDischargeSummary2) + ) + }else{ + + triggerDependants( + source = pregnancyTestResult, + passedIndex = index, + triggerIndex = 0, + target = isPregnant, + targetSideEffect = listOf(isPregnant,usingFamilyPlanningMitanin,wantToUseFamilyPlanning,methodOfContraception,anyOtherMethod,antraDoses,dateOfAntraInjection,dueDateOfAntraInjection,mpaFileUpload1,dateOfSterilisation,deliveryDischargeSummary1,deliveryDischargeSummary2) + ) + triggerforHide( + source = pregnancyTestResult, + passedIndex = index, + triggerIndex = 1, + target = usingFamilyPlanningMitanin, + targetSideEffect = listOf(usingFamilyPlanningMitanin,wantToUseFamilyPlanning,methodOfContraception,antraDoses,dateOfAntraInjection,dueDateOfAntraInjection,anyOtherMethod,mpaFileUpload1,dateOfSterilisation,deliveryDischargeSummary1,deliveryDischargeSummary2) + ) + } + } + else if (pregnancyTestResult.value == resources.getStringArray(R.array.ectdset_po_neg)[1]) { + isPregnant.isEnabled = false + isPregnant.value = resources.getStringArray(R.array.yes_no)[1] + + + if (!BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true)) { + triggerDependants( + source = pregnancyTestResult, + passedIndex = index, + triggerIndex = 1, + target = isPregnant, + targetSideEffect = listOf(isPregnant,usingFamilyPlanning,methodOfContraception,anyOtherMethod,antraDoses,dateOfAntraInjection,dueDateOfAntraInjection,mpaFileUpload1,dateOfSterilisation,deliveryDischargeSummary1,deliveryDischargeSummary2) + ) + triggerDependants( + source = isPregnant, + passedIndex = index, + triggerIndex = 1, + target = usingFamilyPlanning, + targetSideEffect = listOf(methodOfContraception,anyOtherMethod,antraDoses,dateOfAntraInjection,dueDateOfAntraInjection,mpaFileUpload1,dateOfSterilisation,deliveryDischargeSummary1,deliveryDischargeSummary2) + ) + }else{ + + triggerDependants( + source = pregnancyTestResult, + passedIndex = index, + triggerIndex = 1, + target = isPregnant, + targetSideEffect = listOf(isPregnant,usingFamilyPlanningMitanin,wantToUseFamilyPlanning,methodOfContraception,anyOtherMethod,antraDoses,dateOfAntraInjection,dueDateOfAntraInjection,mpaFileUpload1,dateOfSterilisation,deliveryDischargeSummary1,deliveryDischargeSummary2) + ) + triggerDependants( + source = isPregnant, + passedIndex = index, + triggerIndex = 1, + target = usingFamilyPlanningMitanin, + targetSideEffect = listOf(wantToUseFamilyPlanning,methodOfContraception,anyOtherMethod,antraDoses,dateOfAntraInjection,dueDateOfAntraInjection,mpaFileUpload1,dateOfSterilisation,deliveryDischargeSummary1,deliveryDischargeSummary2) + ) + + } + + + } + else { isPregnant.value = null isPregnant.isEnabled = true } - handleListOnValueChanged(isPregnant.id, 0) + return 0 } isPregnant.id -> { - triggerDependants( - source = isPregnant, - passedIndex = index, - triggerIndex = 1, - target = usingFamilyPlanning, - targetSideEffect = listOf(methodOfContraception, anyOtherMethod) - ) + antraDoses.value = antraDoseValue + var list1: List = emptyList() + if (!BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true)) { + list1=listOf(methodOfContraception,anyOtherMethod,antraDoses,dateOfAntraInjection,dueDateOfAntraInjection,mpaFileUpload1,dateOfSterilisation,deliveryDischargeSummary1,deliveryDischargeSummary2) + }else{ + list1= listOf(methodOfContraception,anyOtherMethod,antraDoses,dateOfAntraInjection,dueDateOfAntraInjection,wantToUseFamilyPlanning,dateOfSterilisation,deliveryDischargeSummary1,deliveryDischargeSummary2) + + } + if (isPregnant.value == resources.getStringArray(R.array.ectdset_yes_no_dont)[0]) { + + if (!BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true)) { + triggerDependants( + source = isPregnant, + passedIndex = index, + triggerIndex = 1, + target = usingFamilyPlanning, + targetSideEffect = list1 + ) + }else{ + triggerDependants( + source = isPregnant, + passedIndex = index, + triggerIndex = 1, + target = usingFamilyPlanningMitanin, + targetSideEffect = list1 + ) + } + } + else if (isPregnant.value == resources.getStringArray(R.array.ectdset_yes_no_dont)[1]) { + if (!BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true)) { + triggerDependants( + source = isPregnant, + passedIndex = index, + triggerIndex = 1, + target = usingFamilyPlanning, + targetSideEffect = list1 + ) + }else{ + triggerDependants( + source = isPregnant, + passedIndex = index, + triggerIndex = 1, + target = usingFamilyPlanningMitanin, + targetSideEffect = list1 + ) + } + } + else { + if (!BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true)) { + triggerDependants( + source = isPregnant, + passedIndex = index, + triggerIndex = 2, + target = usingFamilyPlanning, + targetSideEffect = list1 ) + }else{ + triggerDependants( + source = isPregnant, + passedIndex = index, + triggerIndex = 2, + target = usingFamilyPlanningMitanin, + targetSideEffect = list1 ) + } + } + return 0 + + } usingFamilyPlanning.id -> { - triggerDependants( - source = usingFamilyPlanning, - passedIndex = index, - triggerIndex = 0, - target = methodOfContraception, - targetSideEffect = listOf(anyOtherMethod) - ) + antraDoses.value = antraDoseValue + methodOfContraception.inputType= InputType.DROPDOWN + methodOfContraception.entries = + if (noOfChildrens == 0) + resources.getStringArray(R.array.method_of_contraception_for_zero_child) + else + resources.getStringArray(R.array.method_of_contraception) + if (usingFamilyPlanning.value == resources.getStringArray(R.array.ectdset_yes_no)[0]) { + triggerDependants( + source = usingFamilyPlanning, + removeItems = emptyList(), + addItems = listOf(methodOfContraception) + ) + + } + else{ + triggerDependants( + source = usingFamilyPlanning, + removeItems = listOf(methodOfContraception,dateOfSterilisation,antraDoses,dateOfAntraInjection,dueDateOfAntraInjection,anyOtherMethod,mpaFileUpload1,deliveryDischargeSummary1,deliveryDischargeSummary2), + addItems = emptyList() + ) + } + } methodOfContraception.id -> { - triggerDependants( - source = methodOfContraception, - passedIndex = index, - triggerIndex = methodOfContraception.entries!!.lastIndex, - target = anyOtherMethod - ) + antraDoses.value = antraDoseValue + when (methodOfContraception.value) { + + resources.getStringArray(R.array.method_of_contraception)[1] -> { + var list1: List = emptyList() + if (!BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true)) { + list1=listOf(antraDoses,dateOfAntraInjection,dueDateOfAntraInjection,mpaFileUpload1) + + }else{ + list1= listOf(antraDoses,dateOfAntraInjection,dueDateOfAntraInjection) + + } + + triggerDependants( + source = methodOfContraception, + addItems = list1, + removeItems = listOf(anyOtherMethod,dateOfSterilisation,deliveryDischargeSummary1, + deliveryDischargeSummary2), + position = getIndexById(methodOfContraception.id) + 1 + ) + } + + resources.getStringArray(R.array.method_of_contraception).last() -> { + triggerDependants( + source = methodOfContraception, + addItems = listOf(anyOtherMethod), + removeItems = listOf(antraDoses,dateOfAntraInjection,dueDateOfAntraInjection,deliveryDischargeSummary1, + deliveryDischargeSummary2,mpaFileUpload1,dateOfSterilisation), + position = getIndexById(methodOfContraception.id) + 1 + ) + } + + else -> { + + if(noOfChildrens!=0) + { + val methods = resources.getStringArray(R.array.method_of_contraception).toMutableList() + val sterilizationIndices = listOf(7, 8) + val selectedIndex = methods.indexOf(methodOfContraception.value) + if (selectedIndex in sterilizationIndices) { + triggerDependants( + source = methodOfContraception, + addItems = + if (!BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true)) + listOf(dateOfSterilisation,deliveryDischargeSummary1, deliveryDischargeSummary2) + else + listOf(dateOfSterilisation), + removeItems = listOf( + antraDoses, + anyOtherMethod, + dateOfAntraInjection, + dueDateOfAntraInjection, + mpaFileUpload1, + ), + position = -1 + ) + } + else{ + triggerDependants( + source = methodOfContraception, + addItems = emptyList(), + removeItems = listOf(antraDoses,dateOfSterilisation, anyOtherMethod,dateOfAntraInjection,dueDateOfAntraInjection,mpaFileUpload1, deliveryDischargeSummary1, + deliveryDischargeSummary2), + position = -1 + ) + } + }else{ + triggerDependants( + source = methodOfContraception, + addItems = emptyList(), + removeItems = listOf(antraDoses,dateOfSterilisation, anyOtherMethod,dateOfAntraInjection,dueDateOfAntraInjection,mpaFileUpload1, deliveryDischargeSummary1, + deliveryDischargeSummary2), + position = -1 + ) + } + + } + } + return 0 } + anyOtherMethod.id -> { validateAllAlphabetsSpaceOnEditText(anyOtherMethod) } + usingFamilyPlanningMitanin.id -> { + antraDoses.value = antraDoseValue + methodOfContraception.inputType = InputType.DROPDOWN + methodOfContraception.entries = + if (noOfChildrens == 0) + resources.getStringArray(R.array.method_of_contraception_for_zero_child) + else + resources.getStringArray(R.array.method_of_contraception) + + if (usingFamilyPlanningMitanin.value == + resources.getStringArray(R.array.ectdset_yes_no)[0] + ) { + + triggerDependants( + source = usingFamilyPlanningMitanin, + removeItems = listOf(wantToUseFamilyPlanning), + addItems = listOf(methodOfContraception) + ) + + methodOfContraception.value = methodOfContraception.value + + } else { + triggerDependants( + source = usingFamilyPlanningMitanin, + removeItems = listOf( + methodOfContraception, + antraDoses, + dateOfSterilisation, + dateOfAntraInjection, + dueDateOfAntraInjection, + anyOtherMethod, + mpaFileUpload1, + deliveryDischargeSummary1, + deliveryDischargeSummary2 + ), + addItems = listOf(wantToUseFamilyPlanning) + ) + } + return 0 + } + + wantToUseFamilyPlanning.id -> { + antraDoses.value = antraDoseValue + if (wantToUseFamilyPlanning.value == resources.getStringArray(R.array.ectdset_yes_no)[0]) { + // YES → show FP Methods + triggerDependants( + source = wantToUseFamilyPlanning, + removeItems = emptyList(), + addItems = listOf(methodOfContraception) + ) + } else { + triggerDependants( + source = wantToUseFamilyPlanning, + removeItems = listOf( + methodOfContraception, + antraDoses, + dateOfAntraInjection, + dueDateOfAntraInjection, + anyOtherMethod, + mpaFileUpload1, + dateOfSterilisation, + deliveryDischargeSummary1, + deliveryDischargeSummary2 + ), + addItems = emptyList() + ) + } + return 0 + } + else -> -1 } @@ -249,18 +783,36 @@ class EligibleCoupleTrackingDataset( override fun mapValues(cacheModel: FormDataModel, pageNumber: Int) { (cacheModel as EligibleCoupleTrackingCache).let { form -> + form.dateOfSterilisation = getLongFromDate(dateOfSterilisation.value) form.visitDate = getLongFromDate(dateOfVisit.value) - form.isPregnancyTestDone = isPregnancyTestDone.value - form.pregnancyTestResult = pregnancyTestResult.value - form.isPregnant = isPregnant.value + form.lmpDate = getLongFromDate(lmpDate.value) + form.dateOfAntraInjection=dateOfAntraInjection.value + form.dueDateOfAntraInjection=dueDateOfAntraInjection.value + form.mpaFile=mpaFileUpload1.value + form.antraDose=antraDoses.value + form.isPregnancyTestDone = getEnglishValueInArray(R.array.yes_no, isPregnancyTestDone.value) ?: isPregnancyTestDone.value + form.pregnancyTestResult = getEnglishValueInArray(R.array.ectdset_po_neg, pregnancyTestResult.value) + form.isPregnant = getEnglishValueInArray(R.array.yes_no, isPregnant.value) + form.dischargeSummary1 = deliveryDischargeSummary1.value + form.dischargeSummary2 = deliveryDischargeSummary2.value + + form.usingFamilyPlanning = - usingFamilyPlanning.value?.let { it == resources.getStringArray(R.array.yes_no)[0] } - if (methodOfContraception.value == resources.getStringArray(R.array.method_of_contraception) - .last() - ) { + if (BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true)) { + usingFamilyPlanningMitanin.value == resources.getStringArray(R.array.ectdset_yes_no)[0] || + wantToUseFamilyPlanning.value == resources.getStringArray(R.array.ectdset_yes_no)[0] + } else { + usingFamilyPlanning.value == resources.getStringArray(R.array.yes_no)[0] + } + + val methods = resources.getStringArray(R.array.method_of_contraception) + if (methodOfContraception.value == methods.last()) { form.methodOfContraception = anyOtherMethod.value + } else if (methodOfContraception.value == methods[1]) { + val englishMethod = getEnglishValueInArray(R.array.method_of_contraception, methodOfContraception.value) ?: methodOfContraception.value + form.methodOfContraception = "${englishMethod}/${antraDoses.value}" } else { - form.methodOfContraception = methodOfContraception.value + form.methodOfContraception = getEnglishValueInArray(R.array.method_of_contraception, methodOfContraception.value) } } } @@ -268,10 +820,79 @@ class EligibleCoupleTrackingDataset( fun updateBen(benRegCache: BenRegCache) { benRegCache.genDetails?.let { it.reproductiveStatus = - englishResources.getStringArray(R.array.nbr_reproductive_status_array)[1] + englishResources.getStringArray(R.array.nbr_reproductive_status_array2)[1] it.reproductiveStatusId = 2 } if (benRegCache.processed != "N") benRegCache.processed = "U" benRegCache.syncState = SyncState.UNSYNCED } + + + fun getIndexOfMPA() = getIndexById(mpaFileUpload1.id) + fun getIndexDeliveryDischargeSummary1 () = getIndexById(deliveryDischargeSummary1.id) + fun getIndexDeliveryDischargeSummary2 () = getIndexById(deliveryDischargeSummary2.id) + fun setImageUriToFormElement(lastImageFormId: Int, dpUri: Uri) { + + when (lastImageFormId) { + 21 -> { + mpaFileUpload1.value = dpUri.toString() + mpaFileUpload1.errorText = null + } + 58 -> { + deliveryDischargeSummary1.value = dpUri.toString() + deliveryDischargeSummary1.errorText = null + } + 59 -> { + deliveryDischargeSummary2.value = dpUri.toString() + deliveryDischargeSummary2.errorText = null + } + + } + } + private fun calculateNextInjectionDate( + injectionDate: String?, + minDays: Int, + maxDays: Int + ): Pair { + return try { + val sdf = SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH) + val date = sdf.parse(injectionDate ?: "") ?: return "" to "" + + val cal = Calendar.getInstance() + cal.time = date + + cal.add(Calendar.DAY_OF_YEAR, minDays) + val minDate = sdf.format(cal.time) + + cal.time = date + cal.add(Calendar.DAY_OF_YEAR, maxDays) + val maxDate = sdf.format(cal.time) + + minDate to maxDate + } catch (e: Exception) { + "" to "" + } + } + + + private fun getNextDose(lastDose: String?, lastDate: String?, visitDate: String): String { + + if (lastDose == null || lastDate == null) { + return "Dose-1" + } + + val sdf = SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH) + + val last = sdf.parse(lastDate) ?: return "Dose-1" + val visit = sdf.parse(visitDate) ?: return "Dose-1" + + val diffDays = ((visit.time - last.time) / (1000 * 60 * 60 * 24)) + if (diffDays > 120) { + return "Dose-1" + } + val doseNum = lastDose.filter { it.isDigit() }.toIntOrNull() ?: 0 + val next = doseNum + 1 + return if (next in 1..10) "Dose-$next" else "No More Doses" + } + } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/FilariaFormDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/FilariaFormDataset.kt new file mode 100644 index 000000000..9072311f8 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/FilariaFormDataset.kt @@ -0,0 +1,303 @@ +package org.piramalswasthya.sakhi.configuration + +import android.content.Context +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.helpers.Languages +import org.piramalswasthya.sakhi.model.BenRegCache +import org.piramalswasthya.sakhi.model.FilariaScreeningCache +import org.piramalswasthya.sakhi.model.FormElement +import org.piramalswasthya.sakhi.model.Gender +import org.piramalswasthya.sakhi.model.InputType +import org.piramalswasthya.sakhi.utils.HelperUtil.parseSelections +import java.util.Calendar + +class FilariaFormDataset( + context: Context, + currentLanguage: Languages +) : Dataset(context, currentLanguage) { + + private val dateOfCase = FormElement( + id = 1, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.home_visit_date), + arrayId = -1, + required = true, + max = System.currentTimeMillis(), + hasDependants = true + ) + + private val isSuffering = FormElement( + id = 2, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.suffering_from_filaries), + arrayId = R.array.yes_no, + entries = resources.getStringArray(R.array.yes_no), + required = false, + hasDependants = true + ) + + private val whichPartOfBodyMale = FormElement( + id = 3, + inputType = InputType.CHECKBOXES, + title = resources.getString(R.string.body_part), + arrayId = R.array.body_part_male, + entries = resources.getStringArray(R.array.body_part_male), + required = false, + hasDependants = true + ) + + private val whichPartOfBodyFemale = FormElement( + id = 4, + inputType = InputType.CHECKBOXES, + title = resources.getString(R.string.body_part), + arrayId = R.array.body_part_female, + entries = resources.getStringArray(R.array.body_part_female), + required = false, + hasDependants = true + ) + + private val decAndAlbDoseStatus = FormElement( + id = 5, + inputType = InputType.RADIO, + title = resources.getString(R.string.is_medicine_distributed), + arrayId = R.array.yes_no, + entries = resources.getStringArray(R.array.yes_no), + required = false, + hasDependants = true + ) + + private val medicineSideEffect = FormElement( + id = 7, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.medicine_side_effect), + arrayId = R.array.side_effect_of_medicine, + entries = resources.getStringArray(R.array.side_effect_of_medicine), + required = false, + hasDependants = true + ) + + private val sideEffectOther = FormElement( + id = 8, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.other), + required = true, + hasDependants = false + ) + + var isMale = false + var benAge = 0L + + fun isMaleFemale(ben: BenRegCache?): Boolean { + return when (ben?.gender) { + Gender.MALE -> { + isMale = true + true + } + Gender.FEMALE -> { + isMale = false + false + } + else -> false + } + } + + private fun toCsv(rawValue: String?, entries: Array): String { + return parseSelections(rawValue, entries).joinToString(", ") + } + + suspend fun setUpPage(ben: BenRegCache?, saved: FilariaScreeningCache?) { + + val list = mutableListOf(dateOfCase, isSuffering) + + isMaleFemale(ben) + benAge = ben!!.dob + decAndAllDoseFieldValidation(benAge) + + if (saved == null) { + dateOfCase.value = getDateFromLong(System.currentTimeMillis()) + } else { + dateOfCase.value = getDateFromLong(saved.mdaHomeVisitDate) + isSuffering.value = + getLocalValueInArray( + R.array.yes_no, + if (saved.sufferingFromFilariasis == true) "Yes" else "No" + ) + + if (isSuffering.value == isSuffering.entries!!.first()) { + + val bodyField = if (isMale) whichPartOfBodyMale else whichPartOfBodyFemale + + list.add(list.indexOf(isSuffering) + 1, bodyField) + list.add(list.indexOf(isSuffering) + 2, decAndAlbDoseStatus) + list.add(list.indexOf(isSuffering) + 3, medicineSideEffect) + + val parsedList = parseSelections(saved.affectedBodyPart, bodyField.entries!!) + + bodyField.value = if (parsedList.isNotEmpty()) { + parsedList.joinToString(", ") + } else { + saved.affectedBodyPart ?: "" + } + + bodyField.isEnabled = true + + decAndAlbDoseStatus.value = + getLocalValueInArray(decAndAlbDoseStatus.arrayId, saved.doseStatus) + + medicineSideEffect.value = + getLocalValueInArray(medicineSideEffect.arrayId, saved.medicineSideEffect) + + if (medicineSideEffect.value == + medicineSideEffect.entries!![medicineSideEffect.entries!!.size - 2] + ) { + list.add(list.indexOf(medicineSideEffect) + 1, sideEffectOther) + sideEffectOther.value = saved.otherSideEffectDetails + sideEffectOther.isEnabled = false + } + } + } + + setUpPage(list) + } + + private fun decAndAllDoseFieldValidation(age: Long) { + if (isYoung(age)) { + decAndAlbDoseStatus.isEnabled = false + decAndAlbDoseStatus.value = + getLocalValueInArray(decAndAlbDoseStatus.arrayId, "Yes") + } else { + decAndAlbDoseStatus.isEnabled = true + } + } + + override suspend fun handleListOnValueChanged(formId: Int, index: Int): Int { + when (formId) { + + isSuffering.id -> { + isSuffering.isEnabled = true + + if (isSuffering.value == resources.getStringArray(R.array.yes_no)[0]) { + + if (isMale) { + triggerDependants( + source = isSuffering, + passedIndex = index, + triggerIndex = 0, + target = listOf(whichPartOfBodyMale, decAndAlbDoseStatus, medicineSideEffect) + ) + } else { + triggerDependants( + source = isSuffering, + passedIndex = index, + triggerIndex = 0, + target = listOf(whichPartOfBodyFemale, decAndAlbDoseStatus, medicineSideEffect) + ) + whichPartOfBodyFemale.isEnabled = true + decAndAlbDoseStatus.isEnabled = true + decAndAllDoseFieldValidation(benAge) + } + + } else { + + val targetField = + if (isMale) whichPartOfBodyMale else whichPartOfBodyFemale + + triggerforHide( + source = isSuffering, + passedIndex = index, + triggerIndex = 1, + target = targetField, + targetSideEffect = listOf( + targetField, + decAndAlbDoseStatus, + medicineSideEffect, + sideEffectOther + ) + ) + } + + return 0 + } + + medicineSideEffect.id -> { + if (medicineSideEffect.value == + medicineSideEffect.entries!![medicineSideEffect.entries!!.size - 2] + ) { + triggerDependants( + source = medicineSideEffect, + addItems = listOf(sideEffectOther), + removeItems = listOf() + ) + } else { + triggerDependants( + source = medicineSideEffect, + addItems = listOf(), + removeItems = listOf(sideEffectOther) + ) + } + return 0 + } + + sideEffectOther.id -> { + validateEmptyOnEditText(sideEffectOther) + return 0 + } + } + return -1 + } + + override fun mapValues(cacheModel: FormDataModel, pageNumber: Int) { + (cacheModel as FilariaScreeningCache).let { form -> + + form.mdaHomeVisitDate = getLongFromDate(dateOfCase.value) + form.createdDate = getLongFromDate(dateOfCase.value) + form.sufferingFromFilariasis = isSuffering() + form.diseaseTypeID = 4 + + val bodyField = if (isMale) whichPartOfBodyMale else whichPartOfBodyFemale + + form.affectedBodyPart = toCsv(bodyField.value, bodyField.entries!!) + + form.otherSideEffectDetails = sideEffectOther.value + form.doseStatus = getEnglishValueInArray(R.array.yes_no, decAndAlbDoseStatus.value) + form.medicineSideEffect = + getEnglishValueInArray(R.array.side_effect_of_medicine, medicineSideEffect.value) + } + } + + fun isSuffering(): Boolean { + return isSuffering.value == isSuffering.entries!!.first() + } + + fun updateBen(benRegCache: BenRegCache) { + benRegCache.genDetails?.let { + it.reproductiveStatus = + englishResources.getStringArray(R.array.nbr_reproductive_status_array2)[1] + it.reproductiveStatusId = 2 + } + if (benRegCache.processed != "N") benRegCache.processed = "U" + } + + fun getIndexOfDate(): Int { + return getIndexById(dateOfCase.id) + } + + fun isYoung(age: Long): Boolean { + val calDob = Calendar.getInstance().apply { timeInMillis = age } + val calNow = Calendar.getInstance() + + val years = calNow.get(Calendar.YEAR) - calDob.get(Calendar.YEAR) + val months = calNow.get(Calendar.MONTH) - calDob.get(Calendar.MONTH) + val days = calNow.get(Calendar.DAY_OF_MONTH) - calDob.get(Calendar.DAY_OF_MONTH) + + var totalYears = years + var totalMonths = months + + if (totalMonths < 0 || (totalMonths == 0 && days < 0)) { + totalYears-- + totalMonths += 12 + } + + return totalYears < 2 + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/HBNCFormDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/HBNCFormDataset.kt index dcdd714a4..687731883 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/configuration/HBNCFormDataset.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/HBNCFormDataset.kt @@ -99,7 +99,7 @@ class HBNCFormDataset( if (currentDay == null) { dateOfHomeVisit.value = getDateFromLong(System.currentTimeMillis()) firstDay?.let { - childImmunizationStatus.value = it.babyImmunizationStatus + childImmunizationStatus.value = getCheckboxIndexesFromValues(R.array.hbnc_child_immunization_status, it.babyImmunizationStatus) } } else { setExistingValuesForVisitPage(currentDay, list) @@ -146,7 +146,7 @@ class HBNCFormDataset( healthSubCenterName.id -> { healthSubCenterName.value?.let { if (it.length > 10 && healthSubCenterName.errorText == null) { - healthSubCenterName.errorText = "Yay, it is working!!!" + healthSubCenterName.errorText = resources.getString(R.string.str_yay_it_is_working) Timber.d("Yay, it is working!!!") return -1 } @@ -411,7 +411,7 @@ class HBNCFormDataset( timeOfMotherDeath = timeOfMotherDeath.value, placeOfMotherDeath = placeOfMotherDeath.getPosition(), otherPlaceOfMotherDeath = otherPlaceOfMotherDeath.value, - motherAnyProblem = motherProblems.value, + motherAnyProblem = getEnglishCheckboxValues(R.array.hbnc_mother_problems, motherProblems.value), babyFirstFed = babyFedAfterBirth.getPosition(), otherBabyFirstFed = otherBabyFedAfterBirth.value, timeBabyFirstFed = whenBabyFirstFed.value, @@ -462,7 +462,7 @@ class HBNCFormDataset( babyWeight = babyWeight.value, babyTemperature = babyTemperature.value, babyYellow = yellowJaundice.getPosition(), - babyImmunizationStatus = childImmunizationStatus.value, + babyImmunizationStatus = getEnglishCheckboxValues(R.array.hbnc_child_immunization_status, childImmunizationStatus.value), babyReferred = babyReferred.getPosition(), dateOfBabyReferral = getLongFromDate(dateOfBabyReferral.value), placeOfBabyReferral = placeOfBabyReferral.getPosition(), @@ -502,7 +502,7 @@ class HBNCFormDataset( private val healthSubCenterName = FormElement( id = 2, inputType = InputType.EDIT_TEXT, - title = "Health Subcenter Name ", + title = resources.getString(R.string.str_health_subcenter_name), arrayId = -1, // etMaxLength = 6, required = false, @@ -512,14 +512,14 @@ class HBNCFormDataset( private val motherName = FormElement( id = 4, inputType = InputType.TEXT_VIEW, - title = "Mother Name", + title = resources.getString(R.string.mother_name), arrayId = -1, required = false ) private val fatherName = FormElement( id = 5, inputType = InputType.TEXT_VIEW, - title = "Father Name", + title = resources.getString(R.string.tv_father_name_ph), arrayId = -1, required = false ) @@ -527,7 +527,7 @@ class HBNCFormDataset( private val dateOfDelivery = FormElement( id = 6, inputType = InputType.DATE_PICKER, - title = "Date of Delivery", + title = resources.getString(R.string.str_date_of_delivery), arrayId = -1, required = false, max = System.currentTimeMillis(), @@ -537,48 +537,35 @@ class HBNCFormDataset( private val placeOfDelivery = FormElement( id = 7, inputType = InputType.DROPDOWN, - title = "Place of Delivery", + title = resources.getString(R.string.str_place_of_delivery), arrayId = -1, - entries = arrayOf( - "House", - "Health center", - "CHC", - "PHC", - ), + entries = resources.getStringArray(R.array.hbnc_place_of_delivery_array), required = false ) private val gender = FormElement( - id = 8, inputType = InputType.RADIO, title = "Baby Gender", arrayId = -1, entries = arrayOf( - "Male", - "Female", - "Transgender", - ), required = false + id = 8, inputType = InputType.RADIO, title = resources.getString(R.string.str_baby_gender), arrayId = -1, entries = resources.getStringArray(R.array.hbnc_baby_gender), required = false ) private val typeOfDelivery = FormElement( id = 9, inputType = InputType.RADIO, - title = "Type of Delivery", + title = resources.getString(R.string.str_type_of_delivery), arrayId = -1, - entries = arrayOf( - "Normal Delivery", "C - Section", "Assisted" - ), + entries = resources.getStringArray(R.array.hbnc_type_of_delivery), required = false ) private val startedBreastFeeding = FormElement( id = 10, inputType = InputType.DROPDOWN, - title = "Started Breastfeeding", + title = resources.getString(R.string.str_started_breastfeeding), arrayId = -1, - entries = arrayOf( - "Within an hour", "1 - 4 hours", "4.1 - 24 hours", "After 24 hours" - ), + entries = resources.getStringArray(R.array.hbnc_started_breastfeeding), required = false ) private val weightAtBirth = FormElement( id = 11, inputType = InputType.EDIT_TEXT, - title = "Weight at birth ( grams )", + title = resources.getString(R.string.str_weight_at_birth_gram), arrayId = -1, required = false, etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL @@ -586,7 +573,7 @@ class HBNCFormDataset( private val dateOfDischargeFromHospitalMother = FormElement( id = 12, inputType = InputType.DATE_PICKER, - title = "Discharge Date of Mother", + title = resources.getString(R.string.str_discharge_date_of_mother), arrayId = -1, required = false, max = System.currentTimeMillis(), @@ -595,7 +582,7 @@ class HBNCFormDataset( private val dateOfDischargeFromHospitalBaby = FormElement( id = 13, inputType = InputType.DATE_PICKER, - title = "Discharge Date of Baby", + title = resources.getString(R.string.str_discharge_date_of_baby), arrayId = -1, required = false, max = System.currentTimeMillis(), @@ -604,7 +591,7 @@ class HBNCFormDataset( private val registrationOfBirth = FormElement( id = 15, inputType = InputType.RADIO, - title = "Registration Of Birth", + title = resources.getString(R.string.str_registration_of_birth), arrayId = -1, entries = arrayOf( "Yes", @@ -616,27 +603,18 @@ class HBNCFormDataset( private val childImmunizationStatus = FormElement( id = 18, inputType = InputType.CHECKBOXES, - title = "Child Immunization Status", + title = resources.getString(R.string.str_child_immunizaation_status), arrayId = -1, - entries = arrayOf( - "BCG", "Polio", "DPT 1", "Hepatitis-B" - ), + entries = resources.getStringArray(R.array.hbnc_child_immunization_status), required = false ) private val babyFedAfterBirth = FormElement( id = 26, inputType = InputType.DROPDOWN, - title = "What was the baby fed after birth ", + title =resources.getString(R.string.str_child_feed_after_birth), arrayId = -1, - entries = arrayOf( - "Mother Milk", - "Water", - "Honey", - "Mishri water", - "Goat Milk", - "Other", - ), + entries =resources.getStringArray(R.array.hbnc_feeding_of_baby), required = false, hasDependants = true ) @@ -644,30 +622,23 @@ class HBNCFormDataset( private val howBabyTookFirstFeed = FormElement( id = 27, inputType = InputType.DROPDOWN, - title = "How did the baby breastfeed? ", + title = resources.getString(R.string.str_how_did_the_baby_breastfeed), arrayId = -1, - entries = arrayOf( - "Forcefully", - "Weakly ", - "Could not breastfeed but had to be fed with spoon", - "Could neither breast-feed nor could take milk given by spoon", - ), + entries = resources.getStringArray(R.array.hbnc_how_breastfeed_baby), required = false ) private val babyEyeCondition = FormElement( id = 32, inputType = InputType.RADIO, - title = "Baby eye condition", + title =resources.getString(R.string.str_baby_eye_condition), arrayId = -1, - entries = arrayOf( - "Normal ", "Swelling", "oozing pus" - ), + entries = resources.getStringArray(R.array.hbnc_baby_eye_condition), required = false ) private val babyBleedUmbilicalCord = FormElement( id = 33, inputType = InputType.RADIO, - title = "Is there bleeding from the baby umbilical cord ", + title = resources.getString(R.string.str_is_there_bleeding), arrayId = -1, entries = arrayOf( "Yes", @@ -678,25 +649,23 @@ class HBNCFormDataset( private val babyWeightColor = FormElement( id = 34, inputType = InputType.RADIO, - title = "Weighing machine scale color", + title = resources.getString(R.string.str_weighing_machine_scale_coloer), arrayId = -1, - entries = arrayOf( - "Red", "Yellow", "Green" - ), + entries = resources.getStringArray(R.array.hbnc_scale_color), required = false ) private val titleBabyPhysicalCondition = FormElement( id = 35, inputType = InputType.HEADLINE, - title = "Enter the child physical condition", + title = resources.getString(R.string.str_child_physical_condition), arrayId = -1, required = false ) private val allLimbsLimp = FormElement( id = 36, inputType = InputType.RADIO, - title = "All limbs limp", + title = resources.getString(R.string.str_all_limbs_limp), arrayId = -1, entries = arrayOf("Yes", "No"), required = false, @@ -704,7 +673,7 @@ class HBNCFormDataset( private val feedingLessStop = FormElement( id = 37, inputType = InputType.RADIO, - title = "Feeding less/stop", + title =resources.getString(R.string.str_feeding_less_stop), arrayId = -1, entries = arrayOf("Yes", "No"), required = false, @@ -712,7 +681,7 @@ class HBNCFormDataset( private val wrapClothKeptMother = FormElement( id = 45, inputType = InputType.RADIO, - title = "The child is wrapped in cloth and kept to the mother", + title = resources.getString(R.string.str_the_child_is_wrapped_in_cloth), arrayId = -1, entries = arrayOf("Yes", "No"), required = false, @@ -720,7 +689,7 @@ class HBNCFormDataset( private val onlyBreastMilk = FormElement( id = 46, inputType = InputType.RADIO, - title = "Started breastfeeding only/ only given breast milk", + title = resources.getString(R.string.str_started_breastfeeding_only), arrayId = -1, entries = arrayOf("Yes", "No"), required = false, @@ -732,7 +701,7 @@ class HBNCFormDataset( private val titleAskMotherA = FormElement( id = 48, inputType = InputType.HEADLINE, - title = "Newborn first training (A) Ask mother", + title = resources.getString(R.string.str_newborn_first_training), arrayId = -1, required = false ) @@ -740,7 +709,7 @@ class HBNCFormDataset( private val timesMotherFed24hr = FormElement( id = 49, inputType = InputType.EDIT_TEXT, - title = "How many times the mother feeds her stomach in 24 hours. Action – If the mother does not eat full stomach or eat less than 4 times, advise mother to do so", + title = resources.getString(R.string.str_baby_stomach_feeding), arrayId = -1, required = false, hasAlertError = true, @@ -752,7 +721,7 @@ class HBNCFormDataset( private val timesPadChanged = FormElement( id = 50, inputType = InputType.EDIT_TEXT, - title = "How many pads have been changed in a day for bleeding? Action – If more than 2 pad, refer the mother to the hospital.", + title = resources.getString(R.string.str_baby_pad_changed), arrayId = -1, required = false, hasAlertError = true, @@ -763,7 +732,7 @@ class HBNCFormDataset( private val babyKeptWarmWinter = FormElement( id = 51, inputType = InputType.RADIO, - title = "During the winter season, is the baby kept warm? (Closer to the mother, dressed well and wrapped). - If it is not being done, ask the mother to do it.", + title =resources.getString(R.string.str_baby_in_winter), arrayId = -1, entries = arrayOf("Yes", "No"), required = false, @@ -772,7 +741,7 @@ class HBNCFormDataset( private val babyBreastFedProperly = FormElement( id = 52, inputType = InputType.RADIO, - title = "Is the child breastfed properly? (Whenever feeling hungry or breastfeeding at least 7 – 8 times in 24 hours). Action – if it is not being done then ask the mother to do it. ", + title = resources.getString(R.string.str_baby_breastfed_properly), arrayId = -1, entries = arrayOf("Yes", "No"), required = false, @@ -780,7 +749,7 @@ class HBNCFormDataset( private val babyCryContinuously = FormElement( id = 53, inputType = InputType.RADIO, - title = "Does the child cry continuously or urinate less than 6 times a day? Action – Advice the mother for breast-feeding", + title = resources.getString(R.string.str_baby_urination_per_day), arrayId = -1, entries = arrayOf("Yes", "No"), required = false, @@ -789,7 +758,7 @@ class HBNCFormDataset( private val motherBodyTemperature = FormElement( id = 55, inputType = InputType.EDIT_TEXT, - title = "Measure and check the temperature. Action – Give the patient paracetamol tablet if the temperature is 102°F (38.9°C) and refer to the hospital if the temperature is higher than this.", + title = resources.getString(R.string.str_mother_body_temperature), arrayId = -1, required = false, etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, @@ -798,7 +767,7 @@ class HBNCFormDataset( private val motherWaterDischarge = FormElement( id = 56, inputType = InputType.RADIO, - title = "Water discharge with foul smell and fever 102 degree Fahrenheit (38.9 degree C). ", + title = resources.getString(R.string.str_water_discharge), arrayId = -1, entries = arrayOf("Yes", "No"), required = false, @@ -806,7 +775,7 @@ class HBNCFormDataset( private val motherSpeakAbnormalFits = FormElement( id = 57, inputType = InputType.RADIO, - title = "Is mother speaking abnormally or having fits?", + title = resources.getString(R.string.str_is_mother_speaking_abnormally), arrayId = -1, entries = arrayOf("Yes", "No"), required = false, @@ -814,7 +783,7 @@ class HBNCFormDataset( private val motherNoOrLessMilk = FormElement( id = 58, inputType = InputType.RADIO, - title = "Mothers milk is not being produced after delivery or she thinks less milk is being produced.", + title =resources.getString(R.string.str_mother_milk_is_not_produced), arrayId = -1, entries = arrayOf("Yes", "No"), required = false, @@ -822,7 +791,7 @@ class HBNCFormDataset( private val motherBreastProblem = FormElement( id = 59, inputType = InputType.RADIO, - title = "Does the mother have cracked nipple / pain and / or hard breasts", + title = resources.getString(R.string.str_mother_cracked_nipple), arrayId = -1, entries = arrayOf("Yes", "No"), required = false, @@ -831,7 +800,7 @@ class HBNCFormDataset( private val babyEyesSwollen = FormElement( id = 61, inputType = InputType.RADIO, - title = "Are the eyes swollen / Are there pus from the eyes?", + title = resources.getString(R.string.str_are_the_eyes_swollen), arrayId = -1, entries = arrayOf("Yes", "No"), required = false, @@ -839,7 +808,7 @@ class HBNCFormDataset( private val babyWeight = FormElement( id = 62, inputType = InputType.EDIT_TEXT, - title = "Weight on Day ${if (nthDay > 0) nthDay else 1}", + title = "${resources.getString(R.string.str_weight_on_day)} ${if (nthDay > 0) nthDay else 1}", arrayId = -1, required = false, etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_FLAG_DECIMAL, @@ -850,7 +819,7 @@ class HBNCFormDataset( private val yellowJaundice = FormElement( id = 66, inputType = InputType.RADIO, - title = "Yellowing of the eye/palm/sole/skin (jaundice)", + title = resources.getString(R.string.str_yellowing_of_eye), arrayId = -1, entries = arrayOf("Yes", "No"), required = false @@ -860,7 +829,7 @@ class HBNCFormDataset( private val breathFast = FormElement( id = 68, inputType = InputType.RADIO, - title = "Respiratory rate more than 60 per minute", + title = resources.getString(R.string.str_respiratory_rate), arrayId = -1, entries = arrayOf("Yes", "No"), required = false, @@ -869,8 +838,8 @@ class HBNCFormDataset( private val titleSepsisD = FormElement( id = 70, inputType = InputType.HEADLINE, - title = "(D) Sepsis", - subtitle = "Examine the following symptoms of sepsis. If symptoms are present, then write, Yes, if symptoms are not present, then do not write. Enter the symptoms seen from the health check-up on the first day of the newborns birth.", + title =resources.getString(R.string.str_d_sepsis), + subtitle = resources.getString(R.string.str_symptoms_of_sepsis), arrayId = -1, required = false ) @@ -878,7 +847,7 @@ class HBNCFormDataset( private val bloatedStomach = FormElement( id = 74, inputType = InputType.RADIO, - title = "Distended abdomen or mother says baby vomits often", + title = resources.getString(R.string.str_distended_abdomen), arrayId = -1, entries = arrayOf( "Yes", @@ -889,7 +858,7 @@ class HBNCFormDataset( private val childColdOnTouch = FormElement( id = 75, inputType = InputType.RADIO, - title = "The mother tells that the child feels cold when touching or the temperature of the child is more than 89 degrees Fahrenheit (37.5 degrees C)", + title = resources.getString(R.string.str_child_feels_cold), arrayId = -1, entries = arrayOf( "Yes", @@ -901,7 +870,7 @@ class HBNCFormDataset( private val childChestDrawing = FormElement( id = 76, inputType = InputType.RADIO, - title = "and the chest is pulled inward while breathing.", + title = resources.getString(R.string.str_chest_is_pulled), arrayId = -1, entries = arrayOf( "Yes", @@ -914,35 +883,35 @@ class HBNCFormDataset( private val pusNavel = FormElement( id = 77, inputType = InputType.EDIT_TEXT, - title = "Pus in the navel", + title = resources.getString(R.string.str_pus_in_the_navel), arrayId = -1, required = false ) private val ashaName = FormElement( id = 78, inputType = InputType.TEXT_VIEW, - title = "ASHA NAME", + title = resources.getString(R.string.str_asha_name), arrayId = -1, required = false ) private val villageName = FormElement( id = 79, inputType = InputType.TEXT_VIEW, - title = "Village Name", + title = resources.getString(R.string.str_village_name), arrayId = -1, required = false ) private val blockName = FormElement( id = 80, inputType = InputType.TEXT_VIEW, - title = "Block Name", + title = resources.getString(R.string.str_block_name), arrayId = -1, required = false ) private val stillBirth = FormElement( id = 81, inputType = InputType.RADIO, - title = "Still Birth", + title = resources.getString(R.string.str_still_birth), arrayId = -1, entries = arrayOf( "Yes", @@ -953,7 +922,7 @@ class HBNCFormDataset( private val supRemark = FormElement( id = 82, inputType = InputType.EDIT_TEXT, - title = "Supervisors Remark ", + title = resources.getString(R.string.str_supervisors_remark), arrayId = -1, required = false, etMaxLength = 500, @@ -962,19 +931,15 @@ class HBNCFormDataset( private val sup = FormElement( id = 83, inputType = InputType.DROPDOWN, - title = "Supervisor", + title = resources.getString(R.string.str_supervisor), arrayId = -1, - entries = arrayOf( - "ASHA Facilitator", - "ANM", - "MPW", - ), + entries = resources.getStringArray(R.array.hbnc_supervisor), required = false ) private val supName = FormElement( id = 84, inputType = InputType.EDIT_TEXT, - title = "Supervisor name", + title = resources.getString(R.string.str_supervisor_name), arrayId = -1, required = false, allCaps = true, @@ -983,7 +948,7 @@ class HBNCFormDataset( private val dateOfSupSig = FormElement( id = 86, inputType = InputType.DATE_PICKER, - title = "Signature with Date of Supervisor", + title = resources.getString(R.string.str_signature_with_date), arrayId = -1, required = false, max = System.currentTimeMillis(), @@ -993,14 +958,14 @@ class HBNCFormDataset( private val titleVisitCard = FormElement( id = 87, inputType = InputType.HEADLINE, - title = "Mother-Newborn Home Visit Card", + title = resources.getString(R.string.str_mother_newborn_home_visit_card), arrayId = -1, required = false ) private val titleVisitCardDischarge = FormElement( id = 88, inputType = InputType.HEADLINE, - title = "Discharge of Institutional Delivery", + title = resources.getString(R.string.str_discharge_of_institutinal_delivery), arrayId = -1, required = false ) @@ -1008,14 +973,14 @@ class HBNCFormDataset( private val dateOfHomeVisit = FormElement( id = 89, inputType = InputType.TEXT_VIEW, - title = "Date of Home Visit", + title = resources.getString(R.string.str_date_of_home_visit), arrayId = -1, required = false ) private val babyAlive = FormElement( id = 90, inputType = InputType.RADIO, - title = "Is the baby alive?", + title = resources.getString(R.string.str_baby_alive), arrayId = -1, entries = arrayOf( "Yes", @@ -1027,7 +992,7 @@ class HBNCFormDataset( private val dateOfBabyDeath = FormElement( id = 91, inputType = InputType.DATE_PICKER, - title = "Date of death of baby", + title = resources.getString(R.string.str_date_of_death_of_baby), arrayId = -1, required = false, max = System.currentTimeMillis(), @@ -1036,36 +1001,30 @@ class HBNCFormDataset( private val timeOfBabyDeath = FormElement( id = 92, inputType = InputType.TIME_PICKER, - title = "Time of death of baby", + title = resources.getString(R.string.str_time_of_death), arrayId = -1, required = false ) private val placeOfBabyDeath = FormElement( id = 93, inputType = InputType.DROPDOWN, - title = "Place of Baby Death", + title = resources.getString(R.string.str_place_of_baby_death), arrayId = -1, - entries = arrayOf( - "Home", - "Sub-center", - "PHC", - "CHC", - "Other", - ), + entries = resources.getStringArray(R.array.hbnc_place_of_baby_death), required = false, hasDependants = true, ) private val otherPlaceOfBabyDeath = FormElement( id = 94, inputType = InputType.EDIT_TEXT, - title = "Other place of Baby Death", + title = resources.getString(R.string.str_other_place_of_baby_death), arrayId = -1, required = false ) private val babyPreterm = FormElement( id = 95, inputType = InputType.RADIO, - title = "Is the baby preterm?", + title =resources.getString(R.string.str_is_baby_preterm), arrayId = -1, entries = arrayOf( "Yes", @@ -1078,21 +1037,17 @@ class HBNCFormDataset( private val gestationalAge = FormElement( id = 96, inputType = InputType.RADIO, - title = "How many weeks has it been since baby born (Gestational Age)", + title = resources.getString(R.string.str_how_many_weeks), // orientation = LinearLayout.VERTICAL, arrayId = -1, - entries = arrayOf( - "24 – 34 Weeks", - "34 – 36 Weeks", - "36 – 38 Weeks", - ), + entries = resources.getStringArray(R.array.hbnc_gestational_age), required = true, hasAlertError = true, ) private val dateOfBabyFirstExamination = FormElement( id = 97, inputType = InputType.DATE_PICKER, - title = "Date of First examination of baby", + title = resources.getString(R.string.str_date_of_first_examination), arrayId = -1, required = false, max = System.currentTimeMillis(), @@ -1101,7 +1056,7 @@ class HBNCFormDataset( private val timeOfBabyFirstExamination = FormElement( id = 98, inputType = InputType.TIME_PICKER, - title = "Time of First examination of baby", + title = resources.getString(R.string.str_time_of_first_examination), arrayId = -1, required = false ) @@ -1110,7 +1065,7 @@ class HBNCFormDataset( private val motherAlive = FormElement( id = 99, inputType = InputType.RADIO, - title = "Is the mother alive?", + title = resources.getString(R.string.str_is_the_mother_alive), arrayId = -1, entries = arrayOf( "Yes", @@ -1122,7 +1077,7 @@ class HBNCFormDataset( private val dateOfMotherDeath = FormElement( id = 100, inputType = InputType.DATE_PICKER, - title = "Date of death of mother", + title = resources.getString(R.string.str_date_of_death_of_mother), arrayId = -1, required = false, max = System.currentTimeMillis(), @@ -1131,29 +1086,23 @@ class HBNCFormDataset( private val timeOfMotherDeath = FormElement( id = 101, inputType = InputType.TIME_PICKER, - title = "Time of death of mother", + title = resources.getString(R.string.str_time_of_death_of_mother), arrayId = -1, required = false ) private val placeOfMotherDeath = FormElement( id = 102, inputType = InputType.DROPDOWN, - title = "Place of mother Death", + title = resources.getString(R.string.str_place_of_mother_death), arrayId = -1, - entries = arrayOf( - "Home", - "Sub-center", - "PHC", - "CHC", - "Other", - ), + entries = resources.getStringArray(R.array.hbnc_place_of_mother_death), required = false, hasDependants = true, ) private val otherPlaceOfMotherDeath = FormElement( id = 103, inputType = InputType.EDIT_TEXT, - title = "Other place of mother Death", + title = resources.getString(R.string.str_other_place_of_mother_death), arrayId = -1, required = false, hasDependants = true @@ -1161,12 +1110,9 @@ class HBNCFormDataset( private val motherProblems = FormElement( id = 104, inputType = InputType.CHECKBOXES, - title = "Does Mother have any problems", + title = resources.getString(R.string.str_does_mother_have_any_problem), arrayId = -1, - entries = arrayOf( - "Excessive Bleeding", - "Unconscious / Fits", - ), + entries = resources.getStringArray(R.array.hbnc_mother_problems), required = false, hasAlertError = true ) @@ -1174,21 +1120,21 @@ class HBNCFormDataset( private val otherBabyFedAfterBirth = FormElement( id = 105, inputType = InputType.EDIT_TEXT, - title = "Other - What was given as the first feed to baby after birth?", + title = resources.getString(R.string.str_first_feed_to_baby), arrayId = -1, required = false ) private val whenBabyFirstFed = FormElement( id = 106, inputType = InputType.TIME_PICKER, - title = "When was the baby first fed", + title = resources.getString(R.string.str_when_first_feed_to_baby), arrayId = -1, required = false ) private val motherHasBreastFeedProblem = FormElement( id = 107, inputType = InputType.RADIO, - title = "Does the mother have breastfeeding problem?", + title = resources.getString(R.string.str_does_the_mother_have_breatfeeding), arrayId = -1, entries = arrayOf("Yes", "No"), required = false, @@ -1197,7 +1143,7 @@ class HBNCFormDataset( private val motherBreastFeedProblem = FormElement( id = 108, inputType = InputType.EDIT_TEXT, - title = "Write the problem, if there is any problem in breast feeding, help the mother to overcome it", + title = resources.getString(R.string.str_write_the_problem), arrayId = -1, required = false ) @@ -1207,14 +1153,14 @@ class HBNCFormDataset( private val titleBabyFirstHealthCheckup = FormElement( id = 109, inputType = InputType.HEADLINE, - title = "Part 2: Baby first health check-up", + title =resources.getString(R.string.str_baby_first_health_checkup), arrayId = -1, required = false ) private val babyTemperature = FormElement( id = 110, inputType = InputType.EDIT_TEXT, - title = "Temperature of the baby", + title = resources.getString(R.string.str_temperature_ot_the_baby), arrayId = -1, required = false, etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, @@ -1224,7 +1170,7 @@ class HBNCFormDataset( private val actionUmbilicalBleed = FormElement( id = 111, inputType = InputType.RADIO, - title = "If yes, either ASHA, ANM/MPW or TBA can tie again with a clean thread. Action taken? ", + title = resources.getString(R.string.str_tie_again_with_clean_thread), arrayId = -1, entries = arrayOf( "Yes", @@ -1235,7 +1181,7 @@ class HBNCFormDataset( private val babyWeigntMatchesColor = FormElement( id = 112, inputType = InputType.RADIO, - title = "Weighing matches with the colour?", + title = resources.getString(R.string.str_weighing_matches), arrayId = -1, entries = arrayOf( "Yes", @@ -1246,14 +1192,14 @@ class HBNCFormDataset( private val titleRoutineNewBornCare = FormElement( id = 113, inputType = InputType.HEADLINE, - title = "Routine Newborn Care: whether the task was performed", + title = resources.getString(R.string.str_routine_newborn_care), arrayId = -1, required = false ) private val babyDry = FormElement( id = 114, inputType = InputType.RADIO, - title = "Dry the baby", + title =resources.getString(R.string.str_dry_the_baby), arrayId = -1, entries = arrayOf( "Yes", @@ -1264,7 +1210,7 @@ class HBNCFormDataset( private val cryWeakStopped = FormElement( id = 115, inputType = InputType.RADIO, - title = "Cry weak/ stopped", + title = resources.getString(R.string.str_cry_weak_stopped), arrayId = -1, entries = arrayOf( "Yes", @@ -1275,7 +1221,7 @@ class HBNCFormDataset( private val cordCleanDry = FormElement( id = 116, inputType = InputType.RADIO, - title = "Keep the cord clean and dry", + title = resources.getString(R.string.str_keep_the_cord_clean_and_dry), arrayId = -1, entries = arrayOf( "Yes", @@ -1287,16 +1233,16 @@ class HBNCFormDataset( private val unusualWithBaby = FormElement( id = 117, inputType = InputType.RADIO, - title = "Was there anything unusual with the baby?", + title = resources.getString(R.string.str_was_there_anything_unusual), arrayId = -1, - entries = arrayOf("Curved limbs", "cleft lip", "Other"), + entries = resources.getStringArray(R.array.hbnc_unusual_with_the_baby), required = false, hasDependants = true, ) private val otherUnusualWithBaby = FormElement( id = 118, inputType = InputType.EDIT_TEXT, - title = "Other - unusual with the baby", + title = resources.getString(R.string.str_other_unusual_with_the_baby), arrayId = -1, required = false ) @@ -1306,14 +1252,14 @@ class HBNCFormDataset( private val titleWashHands = FormElement( id = 119, inputType = InputType.HEADLINE, - title = "ASHA should wash hands with soap and water before touching the baby during each visit", + title = resources.getString(R.string.str_asha_hygeine), arrayId = -1, required = false ) private val babyReferred = FormElement( id = 120, inputType = InputType.RADIO, - title = "Baby referred for any reason?", + title = resources.getString(R.string.str_baby_referred_for_any_reason), arrayId = -1, entries = arrayOf("Yes", "No"), required = false, @@ -1322,7 +1268,7 @@ class HBNCFormDataset( private val dateOfBabyReferral = FormElement( id = 121, inputType = InputType.DATE_PICKER, - title = "Date of baby referral", + title = resources.getString(R.string.str_date_of_baby_referral), arrayId = -1, required = false, max = System.currentTimeMillis(), @@ -1331,34 +1277,23 @@ class HBNCFormDataset( private val placeOfBabyReferral = FormElement( id = 122, inputType = InputType.DROPDOWN, - title = "Place of baby referral", + title = resources.getString(R.string.str_place_of_baby_referral), arrayId = -1, - entries = arrayOf( - "Sub-Centre", - "PHC", - "CHC", - "Sub-District Hospital", - "District Hospital", - "Medical College Hospital", - "In Transit", - "Private Hospital", - "Accredited Private Hospital", - "Other", - ), + entries = resources.getStringArray(R.array.hbnc_place_of_baby_referral), required = false, hasDependants = true ) private val otherPlaceOfBabyReferral = FormElement( id = 123, inputType = InputType.EDIT_TEXT, - title = "Other -Place of baby referral", + title = resources.getString(R.string.str_other_place_of_baby_referral), arrayId = -1, required = false ) private val motherReferred = FormElement( id = 124, inputType = InputType.RADIO, - title = "Mother referred for any reason?", + title = resources.getString(R.string.str_mother_referred_for_any_reason), arrayId = -1, entries = arrayOf("Yes", "No"), required = false, @@ -1367,7 +1302,7 @@ class HBNCFormDataset( private val dateOfMotherReferral = FormElement( id = 125, inputType = InputType.DATE_PICKER, - title = "Date of mother referral", + title = resources.getString(R.string.str_date_of_mother_referral), arrayId = -1, required = false, max = System.currentTimeMillis(), @@ -1376,27 +1311,16 @@ class HBNCFormDataset( private val placeOfMotherReferral = FormElement( id = 126, inputType = InputType.DROPDOWN, - title = "Place of mother referral", + title = resources.getString(R.string.str_place_of_mother_referral), arrayId = -1, - entries = arrayOf( - "Sub-Centre", - "PHC", - "CHC", - "Sub-District Hospital", - "District Hospital", - "Medical College Hospital", - "In Transit", - "Private Hospital", - "Accredited Private Hospital", - "Other", - ), + entries = resources.getStringArray(R.array.hbnc_place_of_mother_referral), required = false, hasDependants = true ) private val otherPlaceOfMotherReferral = FormElement( id = 127, inputType = InputType.EDIT_TEXT, - title = "Other -Place of mother referral", + title = resources.getString(R.string.str_other_place_of_mother_referral), arrayId = -1, required = false ) @@ -1491,7 +1415,7 @@ class HBNCFormDataset( dateOfBabyFirstExamination.value = getDateFromLong(it.dateOfFirstExamination) timeOfBabyFirstExamination.value = it.timeOfFirstExamination motherAlive.value = motherAlive.getStringFromPosition(it.motherAlive) - motherProblems.value = it.motherAnyProblem + motherProblems.value = getCheckboxIndexesFromValues(R.array.hbnc_mother_problems, it.motherAnyProblem) babyFedAfterBirth.value = babyFedAfterBirth.getStringFromPosition(it.babyFirstFed) otherBabyFedAfterBirth.value = it.otherBabyFirstFed whenBabyFirstFed.value = it.timeBabyFirstFed @@ -1684,7 +1608,7 @@ class HBNCFormDataset( babyWeight.value = it.babyWeight babyTemperature.value = it.babyTemperature yellowJaundice.value = yellowJaundice.getStringFromPosition(it.babyYellow) - childImmunizationStatus.value = it.babyImmunizationStatus + childImmunizationStatus.value = getCheckboxIndexesFromValues(R.array.hbnc_child_immunization_status, it.babyImmunizationStatus) babyReferred.value = babyReferred.getStringFromPosition(it.babyReferred) motherReferred.value = motherReferred.getStringFromPosition(it.motherReferred) allLimbsLimp.value = allLimbsLimp.getStringFromPosition(it.allLimbsLimp) diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/HBYCFormDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/HBYCFormDataset.kt index 8b358cbf3..9ab05bfc3 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/configuration/HBYCFormDataset.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/HBYCFormDataset.kt @@ -9,6 +9,7 @@ import org.piramalswasthya.sakhi.model.HBYCCache import org.piramalswasthya.sakhi.model.InputType import org.piramalswasthya.sakhi.model.getDateStrFromLong import java.text.SimpleDateFormat +import java.util.Calendar import java.util.Locale class HBYCFormDataset( @@ -26,47 +27,47 @@ class HBYCFormDataset( private val month = FormElement( id = 1, inputType = InputType.EDIT_TEXT, - title = "Month", + title = resources.getString(R.string.month), etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, required = false ) private val subCenterName = FormElement( id = 2, inputType = InputType.EDIT_TEXT, - title = "Subcenter name ", + title = resources.getString(R.string.str_sub_center), required = false ) private val year = FormElement( id = 3, inputType = InputType.EDIT_TEXT, - title = "Year", + title = resources.getString(R.string.mdsr_year), etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, required = false ) private val primaryHealthCenterName = FormElement( id = 4, inputType = InputType.EDIT_TEXT, - title = "Primary Health Center Name ", + title = resources.getString(R.string.str_primary_health_center_name), required = false ) private val villagePopulation = FormElement( id = 5, inputType = InputType.EDIT_TEXT, - title = "Village Population", + title = resources.getString(R.string.str_village_population), etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, required = false ) private val infantPopulation = FormElement( id = 6, inputType = InputType.EDIT_TEXT, - title = "Total number of children aged 3 to 15 months in villages", + title = resources.getString(R.string.str_total_no_of_childrens), etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, required = false ) private val visitDate = FormElement( id = 7, inputType = InputType.DATE_PICKER, - title = "Visit Date", + title = resources.getString(R.string.str_visit), required = false, min = 0L, max = System.currentTimeMillis() @@ -74,155 +75,141 @@ class HBYCFormDataset( private val hbycAgeCategory = FormElement( id = 8, inputType = InputType.DROPDOWN, - title = "H.B.Y.C by age Tour", + title = resources.getString(R.string.str_hyyc_by_age_tour), required = false, entries = arrayOf("3", "6", "9", "12", "15") ) private val orsPacketDelivered = FormElement( id = 9, inputType = InputType.RADIO, - title = "O.R.S. Packet delivered ", + title = resources.getString(R.string.str_ors_packet_delivered), required = false, - entries = arrayOf("Yes", "No") + entries = resources.getStringArray(R.array.yes_no_options) ) private val ironFolicAcidGiven = FormElement( id = 10, inputType = InputType.RADIO, - title = "Iron folic acid syrup given (after six months of childbirth)", + title = resources.getString(R.string.str_Iron_folic_acid), required = false, - entries = arrayOf("Yes", "No") + entries = resources.getStringArray(R.array.yes_no_options) ) private val isVaccinatedByAge = FormElement( id = 11, inputType = InputType.RADIO, - title = "Child vaccinated by age", + title = resources.getString(R.string.str_child_vaccinated_by_age), required = false, - entries = arrayOf("Yes", "No") + entries = resources.getStringArray(R.array.yes_no_options) ) private val wasIll = FormElement( id = 12, inputType = InputType.RADIO, - title = "The child was ill", + title =resources.getString(R.string.str_child_was_ill), required = false, - entries = arrayOf("Yes", "No") + entries = resources.getStringArray(R.array.yes_no_options) ) private val referred = FormElement( id = 13, inputType = InputType.RADIO, - title = "If yes refer hospital", + title = resources.getString(R.string.str_if_yes_refer_hospital), required = false, - entries = arrayOf("Yes", "No") + entries = resources.getStringArray(R.array.yes_no_options) ) private val supplementsGiven = FormElement( id = 14, inputType = InputType.RADIO, - title = "Supplements given to the child according to age", + title = resources.getString(R.string.str_suppliments_given_to_child), required = false, - entries = arrayOf("Yes", "No") + entries = resources.getStringArray(R.array.yes_no_options) ) private val byHeightLength = FormElement( id = 15, inputType = InputType.RADIO, - title = "By height / length (color inscribed in mcp card)", + title = resources.getString(R.string.str_by_height_length), required = false, - entries = arrayOf("Yes", "No") + entries = resources.getStringArray(R.array.yes_no_options) ) private val childrenWeighingLessReferred = FormElement( id = 16, inputType = InputType.RADIO, - title = "Number of children weighing less than –3SD orange color indicated in MCCP card referred to VHSND/PHC/NRC", + title = resources.getString(R.string.str_no_of_children_weighing_less), required = false, - entries = arrayOf("Yes", "No") + entries = resources.getStringArray(R.array.yes_no_options) ) private val weightAccordingToAge = FormElement( id = 17, inputType = InputType.RADIO, - title = "Weight according to child’s age (color inscribed in MCP card)", + title = resources.getString(R.string.str_weight_acc_to_child), required = false, - entries = arrayOf("Yes", "No") + entries = resources.getStringArray(R.array.yes_no_options) ) private val delayInDevelopment = FormElement( id = 18, inputType = InputType.RADIO, - title = "Delay / constraint found in the physical and mental development of the child according to age", + title = resources.getString(R.string.str_delay_constraint_found), required = false, - entries = arrayOf("Yes", "No") + entries = resources.getStringArray(R.array.yes_no_options) ) private val referredToHealthInstitite = FormElement( id = 19, inputType = InputType.RADIO, - title = "If yes then Health Institute R.B.S.K. Team / A.N.M. referred to", + title = resources.getString(R.string.str_if_yes_then_health_institute), required = false, - entries = arrayOf("Yes", "No") + entries = resources.getStringArray(R.array.yes_no_options) ) private val vitaminASupplementsGiven = FormElement( id = 20, inputType = InputType.RADIO, - title = "Vitamin – A supplements given to the child according to age", + title = resources.getString(R.string.str_vit_a_suppliment), required = false, - entries = arrayOf("Yes", "No") + entries = resources.getStringArray(R.array.yes_no_options) ) private val deathAge = FormElement( id = 21, inputType = InputType.DROPDOWN, - title = "Mark age (in month) at the time of death", + title = resources.getString(R.string.str_mark_age), required = false, - entries = arrayOf( - "3 Month", - "4 Month", - "5 Month", - "6 Month", - "7 Month", - "8 Month", - "9 Month", - "10 Month", - "11 Month", - "12 Month", - "13 Month", - "14 Month", - "15 Month", - ) + entries = resources.getStringArray(R.array.hbyc_month_array) ) private val deathCause = FormElement( id = 22, inputType = InputType.EDIT_TEXT, - title = "Cause of death", + title = resources.getString(R.string.mdsr_cause), required = false ) private val qmOrAnmInformed = FormElement( id = 23, inputType = InputType.RADIO, - title = "Q.M/A.N.M was informed", + title = resources.getString(R.string.str_qm_informed), required = false, - entries = arrayOf("Yes", "No") + entries = resources.getStringArray(R.array.yes_no_options) ) private val deathPlace = FormElement( id = 24, inputType = InputType.DROPDOWN, - title = "Place of death", + title = resources.getString(R.string.str_place_of_death), required = false, - entries = arrayOf("Home", "Health Center", "On the Way") + entries = resources.getStringArray(R.array.do_cause_of_death_array) ) private val superVisorOn = FormElement( id = 25, inputType = InputType.RADIO, - title = "Supervisor from block/district/state level in last on", + title =resources.getString(R.string.str_supervisor_from_block), required = false, - entries = arrayOf("Yes", "No") + entries = resources.getStringArray(R.array.yes_no_options) ) private val orsShortage = FormElement( id = 26, inputType = InputType.RADIO, - title = "ORS in last one month Packet shortage", + title = resources.getString(R.string.str_ors_in_last_one_month), required = false, - entries = arrayOf("Yes", "No") + entries = resources.getStringArray(R.array.yes_no_options) ) private val ifaDecreased = FormElement( id = 27, inputType = InputType.RADIO, - title = "IFA in last one month Decreased syrup", + title = resources.getString(R.string.str_ifa_last_month), required = false, - entries = arrayOf("Yes", "No") + entries = resources.getStringArray(R.array.yes_no_options) ) val firstPage by lazy { @@ -290,6 +277,18 @@ class HBYCFormDataset( month.value = monthVal + ben?.let { benReg -> + val monthInt = monthVal.toIntOrNull() ?: 0 + if (monthInt > 0 && benReg.dob > 0) { + val cal = Calendar.getInstance() + cal.timeInMillis = benReg.dob + cal.add(Calendar.MONTH, monthInt) + visitDate.min = cal.timeInMillis + } else if (benReg.dob > 0) { + visitDate.min = benReg.dob + } + } + saved?.let { hbycCache -> month.value = hbycCache.month subCenterName.value = hbycCache.subcenterName @@ -300,43 +299,43 @@ class HBYCFormDataset( visitDate.value = getDateStrFromLong(hbycCache.visitdate) hbycAgeCategory.value = hbycCache.hbycAgeCategory orsPacketDelivered.value = - if (hbycCache.orsPacketDelivered == 0) null else resources.getStringArray(R.array.yes_no)[hbycCache.orsPacketDelivered - 1] + if (hbycCache.orsPacketDelivered == 0) null else resources.getStringArray(R.array.yes_no_options)[hbycCache.orsPacketDelivered - 1] ironFolicAcidGiven.value = - if (hbycCache.ironFolicAcidGiven == 0) null else resources.getStringArray(R.array.yes_no)[hbycCache.ironFolicAcidGiven - 1] + if (hbycCache.ironFolicAcidGiven == 0) null else resources.getStringArray(R.array.yes_no_options)[hbycCache.ironFolicAcidGiven - 1] isVaccinatedByAge.value = - if (hbycCache.isVaccinatedByAge == 0) null else resources.getStringArray(R.array.yes_no)[hbycCache.isVaccinatedByAge - 1] + if (hbycCache.isVaccinatedByAge == 0) null else resources.getStringArray(R.array.yes_no_options)[hbycCache.isVaccinatedByAge - 1] wasIll.value = - if (hbycCache.wasIll == 0) null else resources.getStringArray(R.array.yes_no)[hbycCache.wasIll - 1] + if (hbycCache.wasIll == 0) null else resources.getStringArray(R.array.yes_no_options)[hbycCache.wasIll - 1] referred.value = - if (hbycCache.referred == 0) null else resources.getStringArray(R.array.yes_no)[hbycCache.referred - 1] + if (hbycCache.referred == 0) null else resources.getStringArray(R.array.yes_no_options)[hbycCache.referred - 1] supplementsGiven.value = - if (hbycCache.supplementsGiven == 0) null else resources.getStringArray(R.array.yes_no)[hbycCache.supplementsGiven - 1] + if (hbycCache.supplementsGiven == 0) null else resources.getStringArray(R.array.yes_no_options)[hbycCache.supplementsGiven - 1] byHeightLength.value = - if (hbycCache.byHeightLength == 0) null else resources.getStringArray(R.array.yes_no)[hbycCache.byHeightLength - 1] + if (hbycCache.byHeightLength == 0) null else resources.getStringArray(R.array.yes_no_options)[hbycCache.byHeightLength - 1] childrenWeighingLessReferred.value = if (hbycCache.childrenWeighingLessReferred == 0) null else resources.getStringArray( R.array.yes_no )[hbycCache.childrenWeighingLessReferred - 1] weightAccordingToAge.value = - if (hbycCache.weightAccordingToAge == 0) null else resources.getStringArray(R.array.yes_no)[hbycCache.weightAccordingToAge - 1] + if (hbycCache.weightAccordingToAge == 0) null else resources.getStringArray(R.array.yes_no_options)[hbycCache.weightAccordingToAge - 1] delayInDevelopment.value = - if (hbycCache.delayInDevelopment == 0) null else resources.getStringArray(R.array.yes_no)[hbycCache.delayInDevelopment - 1] + if (hbycCache.delayInDevelopment == 0) null else resources.getStringArray(R.array.yes_no_options)[hbycCache.delayInDevelopment - 1] referredToHealthInstitite.value = - if (hbycCache.referredToHealthInstitite == 0) null else resources.getStringArray(R.array.yes_no)[hbycCache.referredToHealthInstitite - 1] + if (hbycCache.referredToHealthInstitite == 0) null else resources.getStringArray(R.array.yes_no_options)[hbycCache.referredToHealthInstitite - 1] vitaminASupplementsGiven.value = - if (hbycCache.vitaminASupplementsGiven == 0) null else resources.getStringArray(R.array.yes_no)[hbycCache.vitaminASupplementsGiven - 1] + if (hbycCache.vitaminASupplementsGiven == 0) null else resources.getStringArray(R.array.yes_no_options)[hbycCache.vitaminASupplementsGiven - 1] deathAge.value = hbycCache.deathAge deathCause.value = hbycCache.deathCause qmOrAnmInformed.value = - if (hbycCache.qmOrAnmInformed == 0) null else resources.getStringArray(R.array.yes_no)[hbycCache.qmOrAnmInformed - 1] + if (hbycCache.qmOrAnmInformed == 0) null else resources.getStringArray(R.array.yes_no_options)[hbycCache.qmOrAnmInformed - 1] deathPlace.value = if (hbycCache.deathPlace != null) resources.getStringArray(R.array.do_cause_of_death_array)[hbycCache.deathPlace!!] else null superVisorOn.value = - if (hbycCache.superVisorOn == 0) null else resources.getStringArray(R.array.yes_no)[hbycCache.superVisorOn - 1] + if (hbycCache.superVisorOn == 0) null else resources.getStringArray(R.array.yes_no_options)[hbycCache.superVisorOn - 1] orsShortage.value = - if (hbycCache.orsShortage == 0) null else resources.getStringArray(R.array.yes_no)[hbycCache.orsShortage - 1] + if (hbycCache.orsShortage == 0) null else resources.getStringArray(R.array.yes_no_options)[hbycCache.orsShortage - 1] ifaDecreased.value = - if (hbycCache.ifaDecreased == 0) null else resources.getStringArray(R.array.yes_no)[hbycCache.ifaDecreased - 1] + if (hbycCache.ifaDecreased == 0) null else resources.getStringArray(R.array.yes_no_options)[hbycCache.ifaDecreased - 1] } setUpPage(list) } diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/HRPMicroBirthPlanDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/HRPMicroBirthPlanDataset.kt index 70a6c18e6..08c944f3b 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/configuration/HRPMicroBirthPlanDataset.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/HRPMicroBirthPlanDataset.kt @@ -7,6 +7,7 @@ import org.piramalswasthya.sakhi.model.BenRegCache import org.piramalswasthya.sakhi.model.FormElement import org.piramalswasthya.sakhi.model.HRPMicroBirthPlanCache import org.piramalswasthya.sakhi.model.InputType +import org.piramalswasthya.sakhi.model.InputType.TEXT_VIEW class HRPMicroBirthPlanDataset( context: Context, currentLanguage: Languages @@ -15,7 +16,6 @@ class HRPMicroBirthPlanDataset( id = 1, inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.name_of_the_pw), - arrayId = -1, required = true, etInputType = android.text.InputType.TYPE_CLASS_TEXT or android.text.InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS ) @@ -24,8 +24,7 @@ class HRPMicroBirthPlanDataset( id = 2, inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.nearest_sc_hwc), - arrayId = -1, - required = true, + required = false, etInputType = android.text.InputType.TYPE_CLASS_TEXT or android.text.InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS ) @@ -40,10 +39,10 @@ class HRPMicroBirthPlanDataset( private val contactNumber1 = FormElement( id = 4, - inputType = InputType.EDIT_TEXT, + inputType = InputType.TEXT_VIEW, title = resources.getString(R.string.contact_number_1), - arrayId = -1, required = true, + isEnabled = false, etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, isMobileNumber = true, etMaxLength = 10, @@ -55,8 +54,7 @@ class HRPMicroBirthPlanDataset( id = 5, inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.contact_number_2), - arrayId = -1, - required = true, + required = false, etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, isMobileNumber = true, etMaxLength = 10, @@ -67,38 +65,25 @@ class HRPMicroBirthPlanDataset( id = 6, inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.sc_hwc_tg_hosp), - arrayId = -1, - required = true, + required = false, + etMaxLength = 100, etInputType = android.text.InputType.TYPE_CLASS_TEXT or android.text.InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS ) private val block = FormElement( id = 7, inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.block), - arrayId = -1, - required = true, + required = false, + etMaxLength = 100, etInputType = android.text.InputType.TYPE_CLASS_TEXT or android.text.InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS ) - private val bankac = FormElement( - id = 8, - inputType = InputType.EDIT_TEXT, - title = resources.getString(R.string.bank_acc_no), - arrayId = -1, - required = true, - etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, - isMobileNumber = true, - etMaxLength = 12, - max = 999999999999, - min = 100000000000 - ) - private val nearestPhc = FormElement( id = 9, inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.nearest_24x7_phc), - arrayId = -1, - required = true, + required = false, + etMaxLength = 100, etInputType = android.text.InputType.TYPE_CLASS_TEXT or android.text.InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS ) @@ -106,8 +91,8 @@ class HRPMicroBirthPlanDataset( id = 10, inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.nearest_fru), - arrayId = -1, - required = true, + required = false, + etMaxLength = 100, etInputType = android.text.InputType.TYPE_CLASS_TEXT or android.text.InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS ) @@ -115,8 +100,8 @@ class HRPMicroBirthPlanDataset( id = 11, inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.blood_donors_identified_1), - arrayId = -1, - required = true, + required = false, + etMaxLength = 50, etInputType = android.text.InputType.TYPE_CLASS_TEXT or android.text.InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS ) @@ -124,8 +109,8 @@ class HRPMicroBirthPlanDataset( id = 12, inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.blood_donors_identified_2), - arrayId = -1, - required = true, + required = false, + etMaxLength = 50, etInputType = android.text.InputType.TYPE_CLASS_TEXT or android.text.InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS ) @@ -133,8 +118,8 @@ class HRPMicroBirthPlanDataset( id = 13, inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.birth_companion), - arrayId = -1, - required = true, + required = false, + etMaxLength = 50, etInputType = android.text.InputType.TYPE_CLASS_TEXT or android.text.InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS ) @@ -142,8 +127,9 @@ class HRPMicroBirthPlanDataset( id = 14, inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.person_who_will_take_care_of_children_if_any_when_the_pw_is_admitted_for_delivery), - arrayId = -1, - required = true, + required = false, + etMaxLength = 50, + etInputType = android.text.InputType.TYPE_CLASS_TEXT or android.text.InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS ) @@ -151,8 +137,8 @@ class HRPMicroBirthPlanDataset( id = 15, inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.name_of_vhsnd_community_member_for_support_during_emergency), - arrayId = -1, - required = true, + required = false, + etMaxLength = 100, etInputType = android.text.InputType.TYPE_CLASS_TEXT or android.text.InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS ) @@ -160,8 +146,7 @@ class HRPMicroBirthPlanDataset( id = 16, inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.contact_number_of_vhsnd_community_member_for_support_during_emergency), - arrayId = -1, - required = true, + required = false, etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, isMobileNumber = true, etMaxLength = 10, @@ -172,16 +157,16 @@ class HRPMicroBirthPlanDataset( id = 17, inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.mode_of_transportation_in_case_of_labour_pain), - arrayId = -1, - required = true, + required = false, + etMaxLength = 20, etInputType = android.text.InputType.TYPE_CLASS_TEXT or android.text.InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS ) private val usg = FormElement( id = 18, inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.nearest_usg_centre), - arrayId = -1, - required = true, + required = false, + etMaxLength = 100, etInputType = android.text.InputType.TYPE_CLASS_TEXT or android.text.InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS ) @@ -189,10 +174,18 @@ class HRPMicroBirthPlanDataset( id = 19, inputType = InputType.HEADLINE, title = resources.getString(R.string.micro_birth_plan_for_all_pregnant_women), - arrayId = -1, required = false ) + private val husbandName = FormElement( + id = 20, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.husband_s_name), + required = true, + isEnabled = false, + etInputType = android.text.InputType.TYPE_CLASS_TEXT or android.text.InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS + ) + suspend fun setUpPage(ben: BenRegCache?, saved: HRPMicroBirthPlanCache?) { val list = mutableListOf( mbp_label, @@ -203,27 +196,29 @@ class HRPMicroBirthPlanDataset( scHosp, usg, block, - bankac, + husbandName, nearestPhc, nearestFru, bloodDonors1, bloodDonors2, birthCompanion, -// careTaker, -// communityMember, -// communityMemberContact, + careTaker, + communityMember, + communityMemberContact, modeOfTransportation ) + + contactNumber1.value=ben?.contactNumber.toString() + husbandName.value= ben?.genDetails?.spouseName.toString() saved?.let { nearestSc.value = it.nearestSc bloodGroup.value = getLocalValueInArray(R.array.maternal_health_blood_group, it.bloodGroup) - contactNumber1.value = it.contactNumber1 + contactNumber1.value = ben?.contactNumber?.toString() contactNumber2.value = it.contactNumber2 scHosp.value = it.scHosp block.value = it.block - bankac.value = it.bankac nearestPhc.value = it.nearestPhc nearestFru.value = it.nearestFru bloodDonors1.value = it.bloodDonors1 @@ -266,10 +261,6 @@ class HRPMicroBirthPlanDataset( validateAllAlphaNumericSpaceOnEditText(block) } - bankac.id -> { - validateIntMinMax(bankac) - } - nearestPhc.id -> { validateAllAlphaNumericSpaceOnEditText(nearestPhc) } @@ -302,9 +293,6 @@ class HRPMicroBirthPlanDataset( validateMobileNumberOnEditText(communityMemberContact) } - modeOfTransportation.id -> { - validateAllAlphabetsSpaceOnEditText(modeOfTransportation) - } else -> { -1 @@ -321,7 +309,6 @@ class HRPMicroBirthPlanDataset( form.contactNumber2 = contactNumber2.value form.scHosp = scHosp.value form.block = block.value - form.bankac = bankac.value form.nearestPhc = nearestPhc.value form.nearestFru = nearestFru.value form.bloodDonors1 = bloodDonors1.value diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/HRPNonPregnantTrackDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/HRPNonPregnantTrackDataset.kt index 581a1ce6e..48348b5a1 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/configuration/HRPNonPregnantTrackDataset.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/HRPNonPregnantTrackDataset.kt @@ -143,9 +143,11 @@ class HRPNonPregnantTrackDataset( inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.systolic), arrayId = -1, - required = false, + required = true, etInputType = android.text.InputType.TYPE_CLASS_NUMBER, - etMaxLength = 3 + etMaxLength = 3, + min = 50, + max = 300 ) private val diastolic = FormElement( @@ -153,9 +155,11 @@ class HRPNonPregnantTrackDataset( inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.diastolic), arrayId = -1, - required = false, + required = true, etInputType = android.text.InputType.TYPE_CLASS_NUMBER, - etMaxLength = 4 + etMaxLength = 3, + min = 30, + max = 200 ) private val bloodGlucoseTest = FormElement( @@ -163,7 +167,7 @@ class HRPNonPregnantTrackDataset( inputType = InputType.RADIO, title = resources.getString(R.string.blood_glucose_test), entries = resources.getStringArray(R.array.sugar_test_types), - required = false, + required = true, hasDependants = true ) @@ -172,7 +176,7 @@ class HRPNonPregnantTrackDataset( inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.random_blood_glucose), arrayId = -1, - required = false, + required = true, etInputType = android.text.InputType.TYPE_CLASS_NUMBER, etMaxLength = 3 ) @@ -182,7 +186,7 @@ class HRPNonPregnantTrackDataset( inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.fasting_glucose_test), arrayId = -1, - required = false, + required = true, etInputType = android.text.InputType.TYPE_CLASS_NUMBER, etMaxLength = 3 ) @@ -192,7 +196,7 @@ class HRPNonPregnantTrackDataset( inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.post_prandial_glucose_test), arrayId = -1, - required = false, + required = true, etInputType = android.text.InputType.TYPE_CLASS_NUMBER, etMaxLength = 3 ) @@ -202,9 +206,11 @@ class HRPNonPregnantTrackDataset( inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.hemoglobin_test), arrayId = -1, - required = false, + required = true, etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_FLAG_DECIMAL, - etMaxLength = 4 + etMaxLength = 4, + minDecimal = 2.0, + maxDecimal = 15.0 ) private val ifaGiven = FormElement( @@ -221,7 +227,7 @@ class HRPNonPregnantTrackDataset( inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.issued_quantity_ifa), arrayId = -1, - required = false, + required = true, etInputType = android.text.InputType.TYPE_CLASS_NUMBER, etMaxLength = 3 ) @@ -418,10 +424,49 @@ class HRPNonPregnantTrackDataset( 1 } + systolic.id->{ + validateIntMinMax(systolic) + 1 + } + diastolic.id->{ + validateIntMinMax(diastolic) + 1 + } + rbg.id->{ + validateInput(rbg) + 1 + } + fbg.id->{ + validateInput(fbg) + 1 + } + ppbg.id->{ + validateInput(ppbg) + 1 + } + hemoglobinTest.id->{ + validateDoubleMinMax(hemoglobinTest) + 1 + } + + else -> -1 } } + private fun validateInput(formElement: FormElement): Int { + formElement.errorText = if (formElement.value.isNullOrEmpty()){ + resources.getString( + R.string.form_input_empty_error + ) + + }else{ + null + } + + return -1 + } + override fun mapValues(cacheModel: FormDataModel, pageNumber: Int) { (cacheModel as HRPNonPregnantTrackCache).let { form -> form.visitDate = getLongFromDate(dateOfVisit.value) @@ -463,7 +508,7 @@ class HRPNonPregnantTrackDataset( fun updateBen(benRegCache: BenRegCache) { benRegCache.genDetails?.let { it.reproductiveStatus = - englishResources.getStringArray(R.array.nbr_reproductive_status_array)[1] + englishResources.getStringArray(R.array.nbr_reproductive_status_array2)[1] it.reproductiveStatusId = 2 it.lastMenstrualPeriod = getLongFromDate(lmp.value) } diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/HRPPregnantTrackDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/HRPPregnantTrackDataset.kt index c7a63b104..79603008c 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/configuration/HRPPregnantTrackDataset.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/HRPPregnantTrackDataset.kt @@ -152,9 +152,11 @@ class HRPPregnantTrackDataset( inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.systolic), arrayId = -1, - required = false, + required = true, etInputType = android.text.InputType.TYPE_CLASS_NUMBER, - etMaxLength = 3 + etMaxLength = 3, + min = 50, + max = 300 ) private val diastolic = FormElement( @@ -162,9 +164,11 @@ class HRPPregnantTrackDataset( inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.diastolic), arrayId = -1, - required = false, + required = true, etInputType = android.text.InputType.TYPE_CLASS_NUMBER, - etMaxLength = 4 + etMaxLength = 3, + min = 30, + max = 200 ) private val bloodGlucoseTest = FormElement( @@ -172,7 +176,7 @@ class HRPPregnantTrackDataset( inputType = InputType.RADIO, title = resources.getString(R.string.blood_glucose_test), entries = resources.getStringArray(R.array.sugar_test_preg_types), - required = false, + required = true, hasDependants = true ) @@ -181,7 +185,7 @@ class HRPPregnantTrackDataset( inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.random_blood_glucose), arrayId = -1, - required = false, + required = true, etInputType = android.text.InputType.TYPE_CLASS_NUMBER, etMaxLength = 3 ) @@ -191,7 +195,7 @@ class HRPPregnantTrackDataset( inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.fasting_glucose_test), arrayId = -1, - required = false, + required = true, etInputType = android.text.InputType.TYPE_CLASS_NUMBER, etMaxLength = 3 ) @@ -201,7 +205,7 @@ class HRPPregnantTrackDataset( inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.post_prandial_glucose_test), arrayId = -1, - required = false, + required = true, etInputType = android.text.InputType.TYPE_CLASS_NUMBER, etMaxLength = 3 ) @@ -210,7 +214,7 @@ class HRPPregnantTrackDataset( id = 22, inputType = InputType.HEADLINE, title = resources.getString(R.string.using_75gm_ogtt), - required = false + required = true ) private val fastingGlucose = FormElement( @@ -218,7 +222,7 @@ class HRPPregnantTrackDataset( inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.fasting_glucose_test), arrayId = -1, - required = false, + required = true, etInputType = android.text.InputType.TYPE_CLASS_NUMBER, etMaxLength = 3 ) @@ -228,7 +232,7 @@ class HRPPregnantTrackDataset( inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.after_2hrs), arrayId = -1, - required = false, + required = true, etInputType = android.text.InputType.TYPE_CLASS_NUMBER, etMaxLength = 3 ) @@ -238,9 +242,11 @@ class HRPPregnantTrackDataset( inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.hemoglobin_test), arrayId = -1, - required = false, + required = true, etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_FLAG_DECIMAL, - etMaxLength = 4 + etMaxLength = 4, + minDecimal = 2.0, + maxDecimal = 15.0 ) private val ifaGiven = FormElement( @@ -257,7 +263,7 @@ class HRPPregnantTrackDataset( inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.issued_quantity_ifa_or_folic_acid), arrayId = -1, - required = false, + required = true, etInputType = android.text.InputType.TYPE_CLASS_NUMBER, etMaxLength = 3 ) @@ -550,10 +556,51 @@ class HRPPregnantTrackDataset( 1 } + systolic.id -> { + validateIntMinMax(systolic) + 1 + } + diastolic.id -> { + validateIntMinMax(diastolic) + 1 + } + hemoglobinTest.id->{ + validateDoubleMinMax(hemoglobinTest) + 1 + } + + rbg.id->{ + validateInput(rbg) + 1 + } + fbg.id->{ + validateInput(fbg) + 1 + + } + ppbg.id->{ + validateInput(ppbg) + 1 + } + else -> -1 } } + + private fun validateInput(formElement: FormElement): Int { + formElement.errorText = if (formElement.value.isNullOrEmpty()){ + resources.getString( + R.string.form_input_empty_error + ) + + }else{ + null + } + + return -1 + } + override fun mapValues(cacheModel: FormDataModel, pageNumber: Int) { (cacheModel as HRPPregnantTrackCache).let { form -> form.visitDate = getLongFromDate(dateOfVisit.value) diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/HouseholdFormDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/HouseholdFormDataset.kt index 96f422f6d..6f11be028 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/configuration/HouseholdFormDataset.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/HouseholdFormDataset.kt @@ -14,6 +14,7 @@ import org.piramalswasthya.sakhi.model.InputType.DROPDOWN import org.piramalswasthya.sakhi.model.InputType.EDIT_TEXT import org.piramalswasthya.sakhi.model.InputType.HEADLINE import org.piramalswasthya.sakhi.model.InputType.RADIO +import org.piramalswasthya.sakhi.model.InputType.TEXT_VIEW import java.text.DateFormat import java.text.SimpleDateFormat import java.util.Calendar @@ -147,6 +148,9 @@ class HouseholdFormDataset(context: Context, language: Languages) : Dataset(cont firstNameHeadOfFamily.value = saved.familyHeadName lastNameHeadOfFamily.value = saved.familyName mobileNoHeadOfFamily.value = saved.familyHeadPhoneNo.toString() + saved.familyHeadName.takeIf { it!!.isNotEmpty() }?.let { firstNameHeadOfFamily.inputType = TEXT_VIEW } + saved.familyName.takeIf { it!!.isNotEmpty() }?.let { lastNameHeadOfFamily.inputType = TEXT_VIEW } + saved.familyHeadPhoneNo.takeIf { it != null }?.let { mobileNoHeadOfFamily.inputType = TEXT_VIEW } houseNo.value = saved.houseNo wardNo.value = saved.wardNo wardName.value = saved.wardName @@ -435,10 +439,17 @@ class HouseholdFormDataset(context: Context, language: Languages) : Dataset(cont return when (formId) { firstNameHeadOfFamily.id -> { validateEmptyOnEditText(firstNameHeadOfFamily) - validateAllCapsOrSpaceOnEditText(firstNameHeadOfFamily) + // validateAllCapsOrSpaceOnEditText(firstNameHeadOfFamily) + validateAllCapsOrSpaceOnEditTextWithHindiEnabled(firstNameHeadOfFamily) + } + /*These validations commented because Hindi and Asamese language name and sirname should store in db + * + * */ + lastNameHeadOfFamily.id ->{ + validateEmptyOnEditText(lastNameHeadOfFamily) + // validateAllCapsOrSpaceOnEditText(lastNameHeadOfFamily) + validateAllCapsOrSpaceOnEditTextWithHindiEnabled(lastNameHeadOfFamily) } - - lastNameHeadOfFamily.id -> validateAllCapsOrSpaceOnEditText(lastNameHeadOfFamily) mobileNoHeadOfFamily.id -> { validateEmptyOnEditText(mobileNoHeadOfFamily) validateMobileNumberOnEditText(mobileNoHeadOfFamily) diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/IRSRoundDataSet.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/IRSRoundDataSet.kt new file mode 100644 index 000000000..57808f920 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/IRSRoundDataSet.kt @@ -0,0 +1,96 @@ +package org.piramalswasthya.sakhi.configuration + +import android.content.Context +import android.util.Log +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.helpers.Languages +import org.piramalswasthya.sakhi.model.FormElement +import org.piramalswasthya.sakhi.model.IRSRoundScreening +import org.piramalswasthya.sakhi.model.InputType + +class IRSRoundDataSet( + context: Context, currentLanguage: Languages +) : Dataset(context, currentLanguage) { + + private val headline = FormElement( + id = 1, + inputType = InputType.HEADLINE, + title = resources.getString(R.string.irs_round), + headingLine = false, + required = false, + hasDependants = false + + + ) + + private val dateOfCase = FormElement( + id = 2, + inputType = InputType.DATE_PICKER, + title = resources.getString(R.string.date), + required = false, + min = System.currentTimeMillis(), + max = System.currentTimeMillis(), + hasDependants = false + + ) + + + private val rounds = FormElement( + id = 3, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.round), + arrayId = R.array.irs_rounds, + entries = resources.getStringArray(R.array.irs_rounds), + required = false, + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + hasDependants = false + ) + + + suspend fun setUpPage( + saved: IRSRoundScreening?, + ) { + val list = mutableListOf( + headline, + dateOfCase, + rounds, + ) + if(saved != null) { + dateOfCase.value = getDateFromLong(System.currentTimeMillis()) + val index = saved.rounds + val localizedArray = resources.getStringArray(R.array.irs_rounds) + if (index in localizedArray.indices) { + rounds.value = localizedArray[index] + } + + } else { + + dateOfCase.value = getDateFromLong(System.currentTimeMillis()) + + } + + + setUpPage(list) + } + + override suspend fun handleListOnValueChanged(formId: Int, index: Int): Int { + /* return when (formId) { + + + + + }*/ + return -1 + } + + override fun mapValues(cacheModel: FormDataModel, pageNumber: Int) { + (cacheModel as IRSRoundScreening).let { form -> + form.rounds = rounds.getPosition() + form.date = getLongFromDate(dateOfCase.value) + } + } + + fun getIndexOfDate(): Int { + return getIndexById(dateOfCase.id) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/IconDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/IconDataset.kt index d25deee75..48603527e 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/configuration/IconDataset.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/IconDataset.kt @@ -2,16 +2,25 @@ package org.piramalswasthya.sakhi.configuration import android.content.res.Resources import dagger.hilt.android.scopes.ActivityRetainedScoped +import org.piramalswasthya.sakhi.BuildConfig import org.piramalswasthya.sakhi.R import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao import org.piramalswasthya.sakhi.model.Icon +import org.piramalswasthya.sakhi.repositories.AdolescentHealthRepo import org.piramalswasthya.sakhi.repositories.RecordsRepo +import org.piramalswasthya.sakhi.ui.asha_supervisor.supervisor.SupervisorFragmentDirections +import org.piramalswasthya.sakhi.ui.getTitleRes import org.piramalswasthya.sakhi.ui.home_activity.child_care.ChildCareFragmentDirections import org.piramalswasthya.sakhi.ui.home_activity.communicable_diseases.CdFragmentDirections +import org.piramalswasthya.sakhi.ui.home_activity.death_reports.DeathReportsFragmentDirections +import org.piramalswasthya.sakhi.ui.home_activity.disease_control.DiseaseControlFragmentDirections +import org.piramalswasthya.sakhi.ui.home_activity.disease_control.leprosy.LeprosyFragmentDirections +import org.piramalswasthya.sakhi.ui.home_activity.disease_control.malaria.form.MalariaIconsFragmentDirections import org.piramalswasthya.sakhi.ui.home_activity.eligible_couple.EligibleCoupleFragmentDirections import org.piramalswasthya.sakhi.ui.home_activity.home.HomeFragmentDirections import org.piramalswasthya.sakhi.ui.home_activity.hrp_cases.HrpCasesFragmentDirections import org.piramalswasthya.sakhi.ui.home_activity.immunization_due.ImmunizationDueTypeFragmentDirections +import org.piramalswasthya.sakhi.ui.home_activity.lms.LmsFragmentDirections import org.piramalswasthya.sakhi.ui.home_activity.maternal_health.MotherCareFragmentDirections import org.piramalswasthya.sakhi.ui.home_activity.non_communicable_diseases.NcdFragmentDirections import org.piramalswasthya.sakhi.ui.home_activity.village_level_forms.VillageLevelFormsFragmentDirections @@ -21,18 +30,31 @@ import javax.inject.Inject @ActivityRetainedScoped class IconDataset @Inject constructor( private val recordsRepo: RecordsRepo, - private val preferenceDao: PreferenceDao + private val preferenceDao: PreferenceDao, + private val adolescentHealthRepo: AdolescentHealthRepo ) { enum class Modules { ALL, HRP + + } + + enum class Disease { + MALARIA, KALA_AZAR, AES_JE, FILARIA, LEPROSY, DEWARMING } fun getHomeIconDataset(resources: Resources): List { val showAll = preferenceDao.isDevModeEnabled + val vlfTitle = resources.getString(R.string.icon_title_vlf) + Timber.d("currently : $showAll") - val showModules = Modules.ALL + lateinit var showModules:Modules + if (BuildConfig.FLAVOR.equals("xushrukha", true)) { + showModules = Modules.HRP + }else{ + showModules = Modules.ALL + } return when (showModules) { Modules.ALL -> listOf( Icon( @@ -45,7 +67,7 @@ class IconDataset @Inject constructor( R.drawable.ic__ben, resources.getString(R.string.icon_title_ben), recordsRepo.allBenListCount, - HomeFragmentDirections.actionNavHomeToAllBenFragment(), + HomeFragmentDirections.actionNavHomeToAllBenFragment(0), ), Icon( R.drawable.ic__eligible_couple, @@ -67,9 +89,9 @@ class IconDataset @Inject constructor( ), Icon( R.drawable.ic__ncd, - resources.getString(R.string.icon_title_ncd), + resources.getString(R.string.icon_title_disease), null, - HomeFragmentDirections.actionNavHomeToNcdFragment(), + HomeFragmentDirections.actionHomeFragmentToDiseaseControlFragment() ), Icon( R.drawable.ic__ncd, @@ -78,7 +100,7 @@ class IconDataset @Inject constructor( HomeFragmentDirections.actionHomeFragmentToCdFragment() ), Icon( - R.drawable.ic__immunization, + R.drawable.ic_vaccines, resources.getString(R.string.icon_title_imm), null, HomeFragmentDirections.actionNavHomeToImmunizationDueFragment(), @@ -106,11 +128,12 @@ class IconDataset @Inject constructor( ), Icon( R.drawable.ic__village_level_form, - resources.getString(R.string.icon_title_vlf), + vlfTitle, null, HomeFragmentDirections.actionNavHomeToVillageLevelFormsFragment() ), - ) + + ) Modules.HRP -> listOf( Icon( @@ -123,7 +146,7 @@ class IconDataset @Inject constructor( R.drawable.ic__ben, resources.getString(R.string.icon_title_ben), recordsRepo.allBenListCount, - HomeFragmentDirections.actionNavHomeToAllBenFragment(), + HomeFragmentDirections.actionNavHomeToAllBenFragment(0), ), Icon( R.drawable.ic__eligible_couple, @@ -147,6 +170,7 @@ class IconDataset @Inject constructor( ) ) + }.apply { forEachIndexed { index, icon -> icon.colorPrimary = index % 2 == 0 @@ -190,6 +214,79 @@ class IconDataset @Inject constructor( ), ) + fun getSupervisorIconsDataset(resources: Resources) = listOf( + Icon( + R.drawable.ic__hh, + resources.getString(R.string.sup_households), + recordsRepo.hhListCount, + SupervisorFragmentDirections.actionNavSupervisorToAllHouseholdFragments() + ), + Icon( + R.drawable.ic__ben, + resources.getString(R.string.sup_beneficiaries), + recordsRepo.allBenListCount, + SupervisorFragmentDirections.actionNavSupervisorToAllBenFragments(0) + ), + Icon( + R.drawable.ic__eligible_couple, + resources.getString(R.string.sup_eligible_couples), + recordsRepo.eligibleCoupleTrackingListCount, + SupervisorFragmentDirections.actionNavSupervisorToEligibleCoupleTrackingListFragments() + ), + Icon( + R.drawable.ic__maternal_health, + resources.getString(R.string.sup_pregnant_women), + recordsRepo.getPregnantWomenListCount(), + SupervisorFragmentDirections.actionNavSupervisorToPwRegistrationFragments() + ), + Icon( + R.drawable.ic__anc_visit, + resources.getString(R.string.sup_anc_visits), + recordsRepo.getRegisteredPregnantWomanListCount(), + SupervisorFragmentDirections.actionNavSupervisorToPwAncVisitsFragments() + ), + Icon( + R.drawable.ic__hrp, + resources.getString(R.string.sup_hrp_woman), + recordsRepo.hrpPregnantWomenListCount, + SupervisorFragmentDirections.actionNavSupervisorToPregnantListFragments() + ), + Icon( + R.drawable.ic__delivery_outcome, + resources.getString(R.string.sup_deliveries), + recordsRepo.getDeliveredWomenListCount(), + SupervisorFragmentDirections.actionNavSupervisorToDeliveryOutcomeListFragments() + ), + Icon( + R.drawable.ic__immunization, + resources.getString(R.string.sup_routine_immunization), + recordsRepo.childrenImmunizationListCount, + SupervisorFragmentDirections.actionNavSupervisorToChildImmunizationListFragments() + ), + Icon( + R.drawable.ic__ncd_list, + resources.getString(R.string.sup_ncd_screened), + recordsRepo.ncdListCount, + SupervisorFragmentDirections.actionNavSupervisorToNcdListFragments() + ), + Icon( + R.drawable.ic__ncd_priority, + resources.getString(R.string.sup_ncd_priority), + recordsRepo.getNcdPriorityListCount, + SupervisorFragmentDirections.actionNavSupervisorToNcdPriorityListFragments() + ), + Icon( + R.drawable.ic__death, + resources.getString(R.string.sup_tb_cases), + recordsRepo.tbSuspectedListCount, + SupervisorFragmentDirections.actionNavSupervisorToTBSuspectedListFragments() + ) + ).apply { + forEachIndexed { index, icon -> + icon.colorPrimary = index % 3 == 0 + } + } + fun getHRPPregnantWomenDataset(resources: Resources) = listOf( Icon( R.drawable.ic__assess_high_risk, @@ -205,6 +302,118 @@ class IconDataset @Inject constructor( ) ) + fun getVLFDataset(resources: Resources): List { + + val phcReviewIcon = Icon( + R.drawable.phc_meeting_s2, + if (BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true)) + resources.getString(R.string.cluster_review) + else + resources.getString(R.string.phc_review), + null, + VillageLevelFormsFragmentDirections + .actionVillageLevelFormsFragmentToPHCReviewListFragement() + ) + + val list = mutableListOf( + Icon( + R.drawable.ic__vhnd_s1, + resources.getString(R.string.vhnd), + null, + VillageLevelFormsFragmentDirections + .actionVillageLevelFormsFragmentToVHNDListFragement() + ), + Icon( + R.drawable.ic__vhnsc, + resources.getString(R.string.vnhc), + null, + VillageLevelFormsFragmentDirections + .actionVillageLevelFormsFragmentToVHNCListFragement() + ), + phcReviewIcon, + Icon( + R.drawable.ic__ahd, + resources.getString(R.string.ahd), + null, + VillageLevelFormsFragmentDirections + .actionVillageLevelFormsFragmentToAHDListFragment() + ) + ) + + if (!BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true)) { + list.add( + Icon( + R.drawable.ic__sass_bahu_sammelan, + resources.getString(R.string.saas_samelan), + null, + VillageLevelFormsFragmentDirections + .actionVillageLevelFormsFragmentToSaasBahuListFragment() + ) + ) + } + + + list.addAll( + listOf( + Icon( + R.drawable.ic__national_deworming_s1, + resources.getString(R.string.national_deworming_day), + null, + VillageLevelFormsFragmentDirections + .actionVillageLevelFormsFragmentToDewormingListFragment() + ), + Icon( + R.drawable.ic__maa_meeting_1, + resources.getString(R.string.maa_meeting), + null, + VillageLevelFormsFragmentDirections + .actionVillageLevelFormsFragmentToAllMaaMeetingFragment() + ), + Icon( + R.drawable.ic__pulse_polio, + resources.getString(R.string.pulse_polio_campaign), + null, + VillageLevelFormsFragmentDirections + .actionVillageLevelFormsFragmentToPulsePolioCampaignListFragment() + ), + Icon( + R.drawable.ic__ors, + resources.getString(R.string.ors_distribution_campaign), + null, + VillageLevelFormsFragmentDirections + .actionVillageLevelFormsFragmentToORSCampaignListFragment() + ), + Icon( + R.drawable.filaria, + resources.getString(R.string.mda_title), + null, + VillageLevelFormsFragmentDirections + .actionVillageLevelFormsFragmentToFilariaMdaCampaignHistoryFragment() + ) + ) + ) + + if (!BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true)) { + list.add( + Icon( + R.drawable.ic__u_win, + resources.getString(R.string.u_win_session), + null, + VillageLevelFormsFragmentDirections + .actionVillageLevelFormsFragmentToUwinListFragment() + ) + ) + } + + return list.apply { + forEachIndexed { index, icon -> + icon.colorPrimary = index % 2 == 0 + } + } + } + + + fun getHRPNonPregnantWomenDataset(resources: Resources) = listOf( Icon( R.drawable.ic__assess_high_risk, @@ -236,6 +445,30 @@ class IconDataset @Inject constructor( resources.getString(R.string.icon_title_acc), recordsRepo.adolescentListCount, ChildCareFragmentDirections.actionChildCareFragmentToAdolescentListFragment() + ), + Icon( + R.drawable.ic__adolescent, + resources.getString(R.string.children_under_five_years), + recordsRepo.childFilteredListCount, + ChildCareFragmentDirections.actionChildCareFragmentToChildrenUnderFiveYearListFragment() + ) + ).apply { + forEachIndexed { index, icon -> + icon.colorPrimary = index % 2 == 0 + } + } + + fun getLmsDataset(resources: Resources) = listOf( + Icon( + R.drawable.ic_guide_icon, + resources.getString(R.string.icon_title_user_guid), + null, + EligibleCoupleFragmentDirections.actionEligibleCoupleFragmentToEligibleCoupleListFragment() + ), Icon( + R.drawable.ic_video_icon, + resources.getString(R.string.icon_title_video_tutorial), + null, + LmsFragmentDirections.actionLmsFragmentToVideoTutorialFragmet() ) ).apply { forEachIndexed { index, icon -> @@ -261,6 +494,117 @@ class IconDataset @Inject constructor( } } + + fun getLeprosyDataset(resources: Resources) = listOf( + Icon( + R.drawable.leprocy, + resources.getString(R.string.leprosy_screening), + recordsRepo.tbScreeningListCount, + LeprosyFragmentDirections.actionLeprosyFragmentToAllHouseHoldDiseaseControlFragment( + resources.getString(Disease.LEPROSY.getTitleRes()) + ) + ), + Icon( + R.drawable.leprocy, + resources.getString(R.string.leprosy_suspected), + recordsRepo.leprosySuspectedListCount, + LeprosyFragmentDirections.actionLeprosyFragmenToLeprosySuspectedListFragment() + ), + Icon( + R.drawable.leprocy, + resources.getString(R.string.leprosy_confirmed), + recordsRepo.leprosyConfirmedCasesListCount, + LeprosyFragmentDirections.actionLeprosyDragmentToLeprosyConfirmedListFragment() + ), + + ) + + + fun getDiseaseControlDataset(resources: Resources) = listOf( + Icon( + R.drawable.ic__ncd, + resources.getString(R.string.icon_title_ncd), + null, + DiseaseControlFragmentDirections.actionDiseaseControlFragmentToNcdFragment(), + + ), + Icon( + R.drawable.maleria, + resources.getString(R.string.icon_title_maleria), + recordsRepo.tbScreeningListCount, + DiseaseControlFragmentDirections.actionDiseaseControlFragmentToMalariaIconsFragment( + + ) + ), Icon( + R.drawable.kala, + resources.getString(R.string.icon_title_ka), + recordsRepo.tbScreeningListCount, + DiseaseControlFragmentDirections.actionDiseaseControlFragmentToAllHouseHoldDiseaseControlFragment( + resources.getString(Disease.KALA_AZAR.getTitleRes())) + ), + + Icon( + R.drawable.aes, + resources.getString(R.string.icon_title_aes), + recordsRepo.tbScreeningListCount, + DiseaseControlFragmentDirections.actionDiseaseControlFragmentToAllHouseHoldDiseaseControlFragment( + resources.getString(Disease.AES_JE.getTitleRes())) + ), + Icon( + R.drawable.filaria, + resources.getString(R.string.icon_title_filaria), + recordsRepo.tbScreeningListCount, + DiseaseControlFragmentDirections.actionDiseaseControlFragmentToAllHouseHoldDiseaseControlFragment( + resources.getString(Disease.FILARIA.getTitleRes()) + ) + ), + Icon( + R.drawable.leprocy, + resources.getString(R.string.icon_title_leprosy), + recordsRepo.tbScreeningListCount, + DiseaseControlFragmentDirections.actionDiseaseControlFragmentToLeprosyFragment() + ), + /*Icon( + R.drawable.ic__eligible_couple, + resources.getString(R.string.icon_title_dearming), + recordsRepo.eligibleCoupleTrackingListCount, + DiseaseControlFragmentDirections.actionDiseaseControlFragmentToAllHouseHoldDiseaseControlFragment( + Disease.DEWARMING.toString() + ) + )*/ + ).apply { + forEachIndexed { index, icon -> + icon.colorPrimary = index % 2 == 0 + } + } + + fun getDeathReportDataset(resources: Resources) = listOf( + Icon( + R.drawable.ic__general_death, + resources.getString(R.string.general_deaths), + recordsRepo.getGeneralDeathCount(), + DeathReportsFragmentDirections.actionDeathReportsFragmentToGdrListFragment() + ), + Icon( + R.drawable.maternal_death_s1, + resources.getString(R.string.maternal_deaths), + recordsRepo.getMaternalDeathCount(), + DeathReportsFragmentDirections.actionDeathReportsFragmentToMdsrListFragment() + ), + Icon( + R.drawable.non_maternal_death_s1, + resources.getString(R.string.non_maternal_deaths), + recordsRepo.getNonMaternalDeathCount(), + DeathReportsFragmentDirections.actionDeathReportsFragmentToNmdsrListFragment() + ), + Icon( + R.drawable.ic__child_death, + resources.getString(R.string.child_deaths), + recordsRepo.getChildDeathCount(), + DeathReportsFragmentDirections.actionDeathReportsFragmentToCdrListFragment() + ) + ) + fun getMotherCareDataset(resources: Resources) = listOf( Icon( R.drawable.ic__pwr, @@ -287,7 +631,7 @@ class IconDataset @Inject constructor( MotherCareFragmentDirections.actionMotherCareFragmentToPncMotherListFragment() ), Icon( - R.drawable.ic__infant_registration, + R.drawable.ic__newborn, resources.getString(R.string.icon_title_pmir), recordsRepo.getInfantRegisterCount(), MotherCareFragmentDirections.actionMotherCareFragmentToInfantRegListFragment() @@ -298,6 +642,24 @@ class IconDataset @Inject constructor( recordsRepo.getRegisteredInfantsCount(), MotherCareFragmentDirections.actionMotherCareFragmentToChildRegListFragment() ), + Icon( + R.drawable.ic__abortion_1, + resources.getString(R.string.icon_title_abortion), + recordsRepo.getAbortionPregnantWomanCount(), + MotherCareFragmentDirections.actionMotherCareFragmentToAbortionListFragment() + ), + Icon( + R.drawable.ic__pmsma, + resources.getString(R.string.icon_title_pmsma), + recordsRepo.getHighRiskWomenCount(), + MotherCareFragmentDirections.actionMotherCareFragmentToPmsmaHighRiskListFragment() + ), + Icon( + R.drawable.ic__hwc_referal_1, + resources.getString(R.string.hwc_referred_list), + recordsRepo.getHwcReferedListCount, + MotherCareFragmentDirections.actionMotherCareFragmentToHwcReferredListFragment() + ), ).apply { forEachIndexed { index, icon -> icon.colorPrimary = index % 2 == 0 @@ -305,12 +667,12 @@ class IconDataset @Inject constructor( } fun getNCDDataset(resources: Resources) = listOf( - Icon( - R.drawable.ic__ncd_list, - resources.getString(R.string.icon_title_ncd_list), - recordsRepo.ncdListCount, - NcdFragmentDirections.actionNcdFragmentToNcdListFragment() - ), +// Icon( +// R.drawable.ic__ncd_list, +// resources.getString(R.string.icon_title_ncd_list), +// recordsRepo.ncdListCount, +// NcdFragmentDirections.actionNcdFragmentToNcdListFragment() +// ), Icon( R.drawable.ic__ncd_eligibility, resources.getString(R.string.icon_title_ncd_eligible_list), @@ -329,21 +691,27 @@ class IconDataset @Inject constructor( recordsRepo.getNcdNonEligibleListCount, NcdFragmentDirections.actionNcdFragmentToNcdNonEligibleListFragment() ), + Icon( + R.drawable.ic_ncd_noneligible, + resources.getString(R.string.ncd_refer_list), + recordsRepo.getNcdrefferedListCount, + NcdFragmentDirections.actionNcdFragmentToNcdReferredListFragment() + ), ).apply { forEachIndexed { index, icon -> icon.colorPrimary = index % 2 == 0 } } - fun getImmunizationDataset() = listOf( + fun getImmunizationDataset(resources : Resources) = listOf( Icon( - R.drawable.ic__immunization, - "Child Immunization", + R.drawable.ic_vaccines, + resources.getString(R.string.child_immunization), recordsRepo.childrenImmunizationListCount, ImmunizationDueTypeFragmentDirections.actionImmunizationDueTypeFragmentToChildImmunizationListFragment() ), - ).apply { + ).apply { forEachIndexed { index, icon -> icon.colorPrimary = index % 2 == 0 } @@ -374,6 +742,30 @@ class IconDataset @Inject constructor( recordsRepo.tbSuspectedListCount, CdFragmentDirections.actionCdFragmentToTBSuspectedListFragment() + ), Icon( + icon = R.drawable.ic__death, + title = resources.getString(R.string.icon_title_ncd_tb_confirmed), + recordsRepo.tbConfirmedListCount, + CdFragmentDirections.actionCdFragmentToTBConfirmedListFragment() + ) + ).apply { + forEachIndexed { index, icon -> + icon.colorPrimary = index % 2 == 0 + } + } + + fun getMalariaDataset(resources: Resources) = listOf( + Icon( + R.drawable.malaria_list, + resources.getString(R.string.icon_title_maleria), + recordsRepo.tbScreeningListCount, + MalariaIconsFragmentDirections.actionMalariaIconsFragmentToAllHouseHoldDiseaseControlFragment(resources.getString(Disease.MALARIA.getTitleRes())) + ), Icon( + R.drawable.confirmed, + resources.getString(R.string.icon_title_malaria_confirmed), + recordsRepo.malariaConfirmedCasesListCount, + MalariaIconsFragmentDirections.actionMalariaIconsFragmentToConfirmedMalariaLIstFragment() + ) ).apply { forEachIndexed { index, icon -> @@ -381,4 +773,4 @@ class IconDataset @Inject constructor( } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/ImmunizationDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/ImmunizationDataset.kt index 8fd36623e..3c030d4a2 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/configuration/ImmunizationDataset.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/ImmunizationDataset.kt @@ -1,6 +1,8 @@ package org.piramalswasthya.sakhi.configuration import android.content.Context +import android.net.Uri +import org.piramalswasthya.sakhi.BuildConfig import org.piramalswasthya.sakhi.R import org.piramalswasthya.sakhi.helpers.Languages import org.piramalswasthya.sakhi.model.BenRegCache @@ -13,7 +15,8 @@ class ImmunizationDataset(context: Context, language: Languages) : Dataset(conte private var vaccineId: Int = 0 - private val name = FormElement( + + /*private val name = FormElement( id = 100, inputType = InputType.TEXT_VIEW, title = resources.getString(R.string.name_ben), @@ -32,7 +35,7 @@ class ImmunizationDataset(context: Context, language: Languages) : Dataset(conte inputType = InputType.TEXT_VIEW, title = resources.getString(R.string.date_of_birth), required = false - ) + )*/ // private val dateOfPrevVaccination = FormElement( // id = 103, @@ -69,7 +72,7 @@ class ImmunizationDataset(context: Context, language: Languages) : Dataset(conte inputType = InputType.DATE_PICKER, title = resources.getString(R.string.date_of_vaccination), max = System.currentTimeMillis(), - required = false + required = true ) private val vaccinatedPlace = FormElement( id = 109, @@ -88,36 +91,77 @@ class ImmunizationDataset(context: Context, language: Languages) : Dataset(conte required = false ) + private val doseName = FormElement( + id = 113, + inputType = InputType.TEXT_VIEW, + title = context.getString(R.string.dose_name), + required = false + ) + + private val vaccinationDueDate = FormElement( + id = 114, + inputType = InputType.TEXT_VIEW, + title = context.getString(R.string.vaccination_due_date), + required = false + ) + + val mcpCard1 = FormElement( + id = 111, + inputType = InputType.FILE_UPLOAD, + required = false, + title = context.getString(R.string.mcp_card_1) + ) + val mcpCard2 = FormElement( + id = 112, + inputType = InputType.FILE_UPLOAD, + required = false, + title = context.getString(R.string.mcp_card_2) + ) + + suspend fun setFirstPage(ben: BenRegCache, vaccine: Vaccine, imm: ImmunizationCache?) { - val list = listOf( - name, - motherName, - dateOfBirth, + val list = mutableListOf( +// name, +// motherName, +// dateOfBirth, +// vaccineName, +// doseNumber, +// expectedDate, vaccineName, - doseNumber, - expectedDate, + vaccinationDueDate, dateOfVaccination, vaccinatedPlace, - vaccinatedBy + vaccinatedBy, + mcpCard1, + mcpCard2 ) + if (BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true)) { + list.remove(mcpCard1) + list.remove(mcpCard2) + } vaccineId = vaccine.vaccineId - name.value = ben.firstName ?: "Baby of ${ben.motherName}" - motherName.value = ben.motherName - dateOfBirth.value = getDateFromLong(ben.dob) +// name.value = ben.firstName ?: "Baby of ${ben.motherName}" +// motherName.value = ben.motherName +// dateOfBirth.value = getDateFromLong(ben.dob) +// doseName.value = vaccine.immunizationService.name vaccineName.value = vaccine.vaccineName.dropLastWhile { it.isDigit() } doseNumber.value = vaccine.vaccineName.takeLastWhile { it.isDigit() } + vaccinationDueDate.value = getDateFromLong(ben.dob + vaccine.maxAllowedAgeInMillis) expectedDate.value = - getDateFromLong(ben.dob + vaccine.minAllowedAgeInMillis + vaccine.overdueDurationSinceMinInMillis) + getDateFromLong(ben.dob + vaccine.maxAllowedAgeInMillis) dateOfVaccination.value = getDateFromLong(System.currentTimeMillis()) dateOfVaccination.min = ben.dob + vaccine.minAllowedAgeInMillis if (System.currentTimeMillis() > ben.dob + vaccine.maxAllowedAgeInMillis) { dateOfVaccination.max = ben.dob + vaccine.maxAllowedAgeInMillis } + imm?.let { saved -> dateOfVaccination.value = saved.date?.let { getDateFromLong(it) } vaccinatedPlace.value = getLocalValueInArray(vaccinatedPlace.arrayId, saved.place) vaccinatedBy.value = getLocalValueInArray(vaccinatedBy.arrayId, saved.byWho) + mcpCard1.value = imm.mcpCardSummary1 + mcpCard2.value = imm.mcpCardSummary2 } setUpPage(list) } @@ -128,15 +172,34 @@ class ImmunizationDataset(context: Context, language: Languages) : Dataset(conte (cacheModel as ImmunizationCache).let { it.date = dateOfVaccination.value?.let { getLongFromDate(it) } // it.placeId= vaccinatedPlace.getPosition() - it.vaccineId = vaccineId + // it.vaccineId = vaccineId it.place = vaccinatedPlace.getEnglishStringFromPosition(vaccinatedPlace.getPosition()) ?: "" // it.byWhoId= vaccinatedBy.getPosition() it.byWho = vaccinatedBy.getEnglishStringFromPosition(vaccinatedBy.getPosition()) ?: "" - + it.mcpCardSummary1 = mcpCard1.value?.takeIf { it.isNotEmpty() } + it.mcpCardSummary2 = mcpCard2.value?.takeIf { it.isNotEmpty() } } } + + fun getIndexMCPCard1() = getIndexById(mcpCard1.id) + fun getIndexMCPCard2() = getIndexById(mcpCard2.id) + + fun setImageUriToFormElement(lastImageFormId: Int, dpUri: Uri) { + when (lastImageFormId) { + mcpCard1.id -> { + mcpCard1.value = dpUri.toString() + mcpCard1.errorText = null + } + + mcpCard2.id -> { + mcpCard2.value = dpUri.toString() + mcpCard2.errorText = null + } + } + } + } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/InfantRegistrationDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/InfantRegistrationDataset.kt index f762e0366..d3c14b1e5 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/configuration/InfantRegistrationDataset.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/InfantRegistrationDataset.kt @@ -1,6 +1,8 @@ package org.piramalswasthya.sakhi.configuration import android.content.Context +import android.net.Uri +import org.piramalswasthya.sakhi.BuildConfig import org.piramalswasthya.sakhi.R import org.piramalswasthya.sakhi.helpers.Languages import org.piramalswasthya.sakhi.helpers.getWeeksOfPregnancy @@ -11,7 +13,6 @@ import org.piramalswasthya.sakhi.model.Gender import org.piramalswasthya.sakhi.model.InfantRegCache import org.piramalswasthya.sakhi.model.InputType import org.piramalswasthya.sakhi.model.PregnantWomanRegistrationCache -import java.util.concurrent.TimeUnit class InfantRegistrationDataset( context: Context, currentLanguage: Languages @@ -20,7 +21,7 @@ class InfantRegistrationDataset( private var babyName = FormElement( id = 1, inputType = InputType.TEXT_VIEW, - title = "Name of Baby", + title = resources.getString(R.string.ir_baby_name), required = false, hasDependants = false ) @@ -28,8 +29,9 @@ class InfantRegistrationDataset( private var infantTerm = FormElement( id = 2, inputType = InputType.TEXT_VIEW, - title = "Infant Term", - entries = arrayOf("Full Term", "Pre Term"), + title = resources.getString(R.string.ir_infant_term), + arrayId = R.array.ir_infant_term, + entries = resources.getStringArray(R.array.ir_infant_term), required = false, hasDependants = false ) @@ -37,8 +39,9 @@ class InfantRegistrationDataset( private var corticosteroidGiven = FormElement( id = 3, inputType = InputType.RADIO, - title = "Was Corticosteroid Inj. given?", - entries = arrayOf("Yes", "No", "Don't Know"), + title = resources.getString(R.string.ir_corticosteroid_given), + arrayId = R.array.ir_confirmation_array3, + entries = resources.getStringArray(R.array.ir_confirmation_array3), required = false, hasDependants = false ) @@ -46,7 +49,7 @@ class InfantRegistrationDataset( private var gender = FormElement( id = 4, inputType = InputType.RADIO, - title = "Sex of Infant", + title = resources.getString(R.string.ir_gender), entries = resources.getStringArray(R.array.ecr_gender_array), required = true, hasDependants = true, @@ -55,8 +58,8 @@ class InfantRegistrationDataset( private var babyCriedAtBirth = FormElement( id = 5, inputType = InputType.RADIO, - title = "Baby Cried Immediately at Birth", - entries = arrayOf("Yes", "No"), + title = resources.getString(R.string.ir_baby_cried_at_birth), + entries = resources.getStringArray(R.array.ir_confirmation_array1), required = false, hasDependants = true ) @@ -64,8 +67,8 @@ class InfantRegistrationDataset( private var resuscitation = FormElement( id = 6, inputType = InputType.RADIO, - title = "If No, Resuscitation Done", - entries = arrayOf("Yes", "No"), + title = resources.getString(R.string.ir_resuscitation), + entries = resources.getStringArray(R.array.ir_confirmation_array1), required = true, hasDependants = false ) @@ -73,8 +76,9 @@ class InfantRegistrationDataset( private var referred = FormElement( id = 7, inputType = InputType.RADIO, - title = "Referred to higher facility for further management", - entries = arrayOf("Yes", "No", "NA"), + title = resources.getString(R.string.ir_referred), + arrayId = R.array.ir_confirmation_array2, + entries = resources.getStringArray(R.array.ir_confirmation_array2), required = false, hasDependants = false ) @@ -82,28 +86,27 @@ class InfantRegistrationDataset( private var hadBirthDefect = FormElement( id = 8, inputType = InputType.RADIO, - title = "Any birth defect seen in at birth?", - entries = arrayOf("Yes", "No", "NA"), + title = resources.getString(R.string.ir_had_birth_defect), + arrayId = R.array.ir_confirmation_array2, + entries = resources.getStringArray(R.array.ir_confirmation_array2), required = false, hasDependants = true ) private var birthDefect = FormElement( - id = 9, inputType = InputType.DROPDOWN, title = "Defect seen at birth", entries = arrayOf( - "Cleft Lip / Cleft Palate", - "Club Foot", - "Down's Syndrome", - "Hydrocephalus", - "Imperforate Anus", - "Neural Tube Defect (Spinal Bifida)", - "Other" - ), required = false, hasDependants = true + id = 9, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.ir_birth_defect), + arrayId = R.array.ir_birth_defect_array, + entries = resources.getStringArray(R.array.ir_birth_defect_array), + required = false, + hasDependants = true ) private var otherDefect = FormElement( id = 10, inputType = InputType.EDIT_TEXT, - title = "Other defect seen at Birth", + title = resources.getString(R.string.ir_other_defect), required = false, hasDependants = false, ) @@ -111,63 +114,103 @@ class InfantRegistrationDataset( private var weight = FormElement( id = 11, inputType = InputType.EDIT_TEXT, - title = "Weight at Birth(kg)", - required = false, + title = resources.getString(R.string.str_weight_at_birth_gram), + required = true, hasDependants = false, - minDecimal = 0.5, - maxDecimal = 7.0, - etInputType = android.text.InputType.TYPE_NUMBER_FLAG_DECIMAL, + etMaxLength = 4, + min = 500, + max = 6000, + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, ) private var breastFeedingStarted = FormElement( id = 12, inputType = InputType.RADIO, - title = "Breast feeding started within one hour of birth", - entries = arrayOf("Yes", "No"), - required = false, + title = resources.getString(R.string.ir_breast_feeding_started), + entries = resources.getStringArray(R.array.ir_confirmation_array1), + required = true, hasDependants = false, ) - - private val opv0Dose = FormElement( + private var isSncu = FormElement( id = 13, - inputType = InputType.DATE_PICKER, - title = "OPV0 Dose", - arrayId = -1, + inputType = InputType.RADIO, + title = resources.getString(R.string.is_baby_discharge_from_sncu), + arrayId = R.array.do_is_jsy_beneficiary_array, + entries = resources.getStringArray(R.array.do_is_jsy_beneficiary_array), required = false, - max = System.currentTimeMillis(), hasDependants = true ) - private val bcgDose = FormElement( - id = 14, - inputType = InputType.DATE_PICKER, - title = "BCG Dose", - arrayId = -1, - required = false, - max = System.currentTimeMillis(), - hasDependants = true + + private val deliveryDischargeSummary1 = FormElement( + id = 58, + inputType = InputType.FILE_UPLOAD, + title = resources.getString(R.string.delivery_discharge_summary_1), + required = false + ) - private val hepBDose = FormElement( - id = 15, - inputType = InputType.DATE_PICKER, - title = "HEP B-0 Dose", - arrayId = -1, - required = false, - max = System.currentTimeMillis(), - hasDependants = true + private val deliveryDischargeSummary2 = FormElement( + id =59, + inputType = InputType.FILE_UPLOAD, + title = resources.getString(R.string.delivery_discharge_summary_2), + required = false ) - private val vitkDose = FormElement( - id = 16, - inputType = InputType.DATE_PICKER, - title = "VITK Dose", - arrayId = -1, - required = false, - max = System.currentTimeMillis(), - hasDependants = true + private val deliveryDischargeSummary3 = FormElement( + id =60, + inputType = InputType.FILE_UPLOAD, + title = resources.getString(R.string.delivery_discharge_summary_3), + required = false + ) + + private val deliveryDischargeSummary4 = FormElement( + id =61, + inputType = InputType.FILE_UPLOAD, + title = resources.getString(R.string.delivery_discharge_summary_4), + required = false ) +// private val opv0Dose = FormElement( +// id = 13, +// inputType = InputType.DATE_PICKER, +// title = "OPV0 Dose", +// arrayId = -1, +// required = false, +// max = System.currentTimeMillis(), +// hasDependants = true +// ) + +// private val bcgDose = FormElement( +// id = 14, +// inputType = InputType.DATE_PICKER, +// title = "BCG Dose", +// arrayId = -1, +// required = false, +// max = System.currentTimeMillis(), +// hasDependants = true +// ) + +// private val hepBDose = FormElement( +// id = 15, +// inputType = InputType.DATE_PICKER, +// title = "HEP B-0 Dose", +// arrayId = -1, +// required = false, +// max = System.currentTimeMillis(), +// hasDependants = true +// ) + +// private val vitkDose = FormElement( +// id = 16, +// inputType = InputType.DATE_PICKER, +// title = "VITK Dose", +// arrayId = -1, +// required = false, +// max = System.currentTimeMillis(), +// hasDependants = true +// ) + suspend fun setUpPage( ben: BenRegCache?, @@ -183,10 +226,10 @@ class InfantRegistrationDataset( hadBirthDefect, // birthDefect, // otherDefect, - weight, breastFeedingStarted, opv0Dose, bcgDose, hepBDose, vitkDose + weight, breastFeedingStarted,isSncu //opv0Dose, bcgDose, hepBDose, vitkDose ) if (deliveryOutcomeCache != null) { - opv0Dose.min = deliveryOutcomeCache.dateOfDelivery + /* opv0Dose.min = deliveryOutcomeCache.dateOfDelivery bcgDose.min = deliveryOutcomeCache.dateOfDelivery hepBDose.min = deliveryOutcomeCache.dateOfDelivery vitkDose.min = deliveryOutcomeCache.dateOfDelivery @@ -205,7 +248,7 @@ class InfantRegistrationDataset( vitkDose.max = it + TimeUnit.DAYS.toMillis(1) } } - } + }*/ } if (pwrCache != null && deliveryOutcomeCache != null) { val weeksOfPregnancy = deliveryOutcomeCache.dateOfDelivery?.let { @@ -240,27 +283,45 @@ class InfantRegistrationDataset( otherDefect, weight, breastFeedingStarted, - opv0Dose, + isSncu + /* opv0Dose, bcgDose, hepBDose, - vitkDose + vitkDose*/ ) babyName.value = saved.babyName - infantTerm.value = saved.infantTerm - corticosteroidGiven.value = saved.corticosteroidGiven + infantTerm.value = getLocalValueInArray(R.array.ir_infant_term, saved.infantTerm) + corticosteroidGiven.value = getLocalValueInArray(R.array.ir_confirmation_array3, saved.corticosteroidGiven) gender.value = saved.gender?.let { gender.entries?.get(it.ordinal) } - babyCriedAtBirth.value = if (saved.babyCriedAtBirth == true) "Yes" else "No" - resuscitation.value = if (saved.resuscitation == true) "Yes" else "No" - referred.value = saved.referred - hadBirthDefect.value = saved.hadBirthDefect - birthDefect.value = saved.birthDefect + babyCriedAtBirth.value = if (saved.babyCriedAtBirth == true) babyCriedAtBirth.entries!![0] else babyCriedAtBirth.entries!![1] + resuscitation.value = if (saved.resuscitation == true) resuscitation.entries!![0] else resuscitation.entries!![1] + referred.value = getLocalValueInArray(R.array.ir_confirmation_array2, saved.referred) + hadBirthDefect.value = getLocalValueInArray(R.array.ir_confirmation_array2, saved.hadBirthDefect) + birthDefect.value = getLocalValueInArray(R.array.ir_birth_defect_array, saved.birthDefect) otherDefect.value = saved.otherDefect weight.value = saved.weight.toString() - breastFeedingStarted.value = if (saved.breastFeedingStarted == true) "Yes" else "No" - opv0Dose.value = saved.opv0Dose?.let { getDateFromLong(it) } - bcgDose.value = saved.bcgDose?.let { getDateFromLong(it) } - hepBDose.value = saved.hepBDose?.let { getDateFromLong(it) } - vitkDose.value = saved.vitkDose?.let { getDateFromLong(it) } + breastFeedingStarted.value = if (saved.breastFeedingStarted == true) breastFeedingStarted.entries!![0] else breastFeedingStarted.entries!![1] + isSncu.value = getLocalValueInArray(R.array.do_is_jsy_beneficiary_array, saved.isSNCU) + if (saved.isSNCU=="Yes") + { + deliveryDischargeSummary1.value = saved.deliveryDischargeSummary1 + deliveryDischargeSummary2.value = saved.deliveryDischargeSummary2 + deliveryDischargeSummary3.value = saved.deliveryDischargeSummary3 + deliveryDischargeSummary4.value = saved.deliveryDischargeSummary4 + if (!BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true)) { + list.add(list.indexOf(isSncu) + 1, deliveryDischargeSummary1) + list.add(list.indexOf(deliveryDischargeSummary1) + 1, deliveryDischargeSummary2) + list.add(list.indexOf(deliveryDischargeSummary2) + 1, deliveryDischargeSummary3) + list.add(list.indexOf(deliveryDischargeSummary3) + 1, deliveryDischargeSummary4) + } + + + } + +// opv0Dose.value = saved.opv0Dose?.let { getDateFromLong(it) } +// bcgDose.value = saved.bcgDose?.let { getDateFromLong(it) } +// hepBDose.value = saved.hepBDose?.let { getDateFromLong(it) } +// vitkDose.value = saved.vitkDose?.let { getDateFromLong(it) } } setUpPage(list) @@ -287,6 +348,25 @@ class InfantRegistrationDataset( ) } + isSncu.id -> { + val isYes = isSncu.value == isSncu.entries!![0] + if(isYes){ + triggerDependants( + source = isSncu, + addItems = listOf(deliveryDischargeSummary1,deliveryDischargeSummary2,deliveryDischargeSummary3,deliveryDischargeSummary4), + removeItems = emptyList() + ) + } + else{ + triggerDependants( + source = isSncu, + addItems = emptyList(), + removeItems =listOf(deliveryDischargeSummary1,deliveryDischargeSummary2,deliveryDischargeSummary3,deliveryDischargeSummary4), + ) + } + + } + birthDefect.id -> { triggerDependants( source = birthDefect, @@ -301,9 +381,7 @@ class InfantRegistrationDataset( } weight.id -> { - validateDoubleUpto1DecimalPlaces(weight) - if (weight.errorText == null) validateDoubleMinMax(weight) - -1 + validateWeightOnEditText(weight) } @@ -314,23 +392,60 @@ class InfantRegistrationDataset( override fun mapValues(cacheModel: FormDataModel, pageNumber: Int) { (cacheModel as InfantRegCache).let { form -> form.babyName = babyName.value - form.infantTerm = infantTerm.value - form.corticosteroidGiven = corticosteroidGiven.value + form.infantTerm = getEnglishValueInArray(R.array.ir_infant_term, infantTerm.value) + form.corticosteroidGiven = getEnglishValueInArray(R.array.ir_confirmation_array3, corticosteroidGiven.value) form.gender = gender.value?.let { Gender.values()[gender.getPosition() - 1] } - form.babyCriedAtBirth = babyCriedAtBirth.value == "Yes" - form.resuscitation = resuscitation.value == "Yes" - form.referred = referred.value - form.hadBirthDefect = hadBirthDefect.value - form.birthDefect = birthDefect.value + form.babyCriedAtBirth = babyCriedAtBirth.value == babyCriedAtBirth.entries!![0] + form.resuscitation = resuscitation.value == resuscitation.entries!![0] + form.referred = getEnglishValueInArray(R.array.ir_confirmation_array2, referred.value) + + form.isSNCU = getEnglishValueInArray(R.array.do_is_jsy_beneficiary_array, isSncu.value) ?: "No" + form.deliveryDischargeSummary1 = deliveryDischargeSummary1.value?.takeIf { it.isNotEmpty() } + form.deliveryDischargeSummary2 = deliveryDischargeSummary2.value?.takeIf { it.isNotEmpty() } + form.deliveryDischargeSummary3 = deliveryDischargeSummary3.value?.takeIf { it.isNotEmpty() } + form.deliveryDischargeSummary4 = deliveryDischargeSummary4.value?.takeIf { it.isNotEmpty() } + + + form.hadBirthDefect = getEnglishValueInArray(R.array.ir_confirmation_array2, hadBirthDefect.value) + form.birthDefect = getEnglishValueInArray(R.array.ir_birth_defect_array, birthDefect.value) form.otherDefect = otherDefect.value form.weight = weight.value?.toDouble() - form.breastFeedingStarted = breastFeedingStarted.value == "Yes" - form.opv0Dose = getLongFromDate(opv0Dose.value) + form.breastFeedingStarted = breastFeedingStarted.value == breastFeedingStarted.entries!![0] + /* form.opv0Dose = getLongFromDate(opv0Dose.value) form.bcgDose = getLongFromDate(bcgDose.value) form.hepBDose = getLongFromDate(hepBDose.value) - form.vitkDose = getLongFromDate(vitkDose.value) + form.vitkDose = getLongFromDate(vitkDose.value)*/ + } + } + + + fun getIndexDeliveryDischargeSummary1 () = getIndexById(deliveryDischargeSummary1.id) + fun getIndexDeliveryDischargeSummary2 () = getIndexById(deliveryDischargeSummary2.id) + fun getIndexDeliveryDischargeSummary3 () = getIndexById(deliveryDischargeSummary3.id) + fun getIndexDeliveryDischargeSummary4 () = getIndexById(deliveryDischargeSummary4.id) + + + fun setImageUriToFormElement(lastImageFormId: Int, dpUri: Uri) { + when (lastImageFormId) { + 58 -> { + deliveryDischargeSummary1.value = dpUri.toString() + deliveryDischargeSummary1.errorText = null + } + 59 -> { + deliveryDischargeSummary2.value = dpUri.toString() + deliveryDischargeSummary2.errorText = null + } + 60 -> { + deliveryDischargeSummary3.value = dpUri.toString() + deliveryDischargeSummary3.errorText = null + } + 61 -> { + deliveryDischargeSummary4.value = dpUri.toString() + deliveryDischargeSummary4.errorText = null + } + } } } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/KalaAzarFormDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/KalaAzarFormDataset.kt new file mode 100644 index 000000000..5e3b2380e --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/KalaAzarFormDataset.kt @@ -0,0 +1,414 @@ +package org.piramalswasthya.sakhi.configuration + +import android.content.Context +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.helpers.Languages +import org.piramalswasthya.sakhi.model.BenRegCache +import org.piramalswasthya.sakhi.model.FormElement +import org.piramalswasthya.sakhi.model.InputType +import org.piramalswasthya.sakhi.model.KalaAzarScreeningCache + +class KalaAzarFormDataset( + context: Context, currentLanguage: Languages +) : Dataset(context, currentLanguage) { + + private val dateOfCase = FormElement( + id = 1, + inputType = InputType.DATE_PICKER, + title = resources.getString(R.string.visit_date), + arrayId = -1, + required = true, + min = System.currentTimeMillis() - (90L * 24 * 60 * 60 * 1000), + max = System.currentTimeMillis(), + hasDependants = false + + ) + private val beneficiaryStatus = FormElement( + id = 2, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.beneficiary_status), + arrayId = R.array.benificary_case_status_kalaazar, + entries = resources.getStringArray(R.array.benificary_case_status_kalaazar), + required = true, + hasDependants = true + + ) + private val dateOfDeath = FormElement( + id = 3, + inputType = InputType.DATE_PICKER, + title = resources.getString(R.string.death_date), + arrayId = -1, + required = true, + min = System.currentTimeMillis() - (90L * 24 * 60 * 60 * 1000), + max = System.currentTimeMillis(), + hasDependants = true + + ) + + private val placeOfDeath = FormElement( + id = 4, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.place_of_Death), + arrayId = R.array.death_place, + entries = resources.getStringArray(R.array.death_place), + required = true, + hasDependants = true + + ) + + private var otherPlaceOfDeath = FormElement( + id = 5, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.other_place), + required = true, + hasDependants = false + ) + + private val reasonOfDeath = FormElement( + id = 6, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.reason_of_Death), + arrayId = R.array.reason_death, + entries = resources.getStringArray(R.array.reason_death), + required = true, + hasDependants = true + + ) + private var otherReasonOfDeath = FormElement( + id = 7, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.other_reason), + required = true, + hasDependants = false + ) + + + private val caseStatus = FormElement( + id = 8, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.case_status), + arrayId = R.array.dc_case_status, + entries = resources.getStringArray(R.array.dc_case_status), + required = false, + hasDependants = true + + ) + private var rapidDiagnostic = FormElement( + id = 9, + inputType = InputType.RADIO, + title = resources.getString(R.string.rapid_diagnostic_kala), + arrayId = R.array.positive_negative, + entries = resources.getStringArray(R.array.positive_negative), + required = false, + hasDependants = true + ) + + private val dateOfTest = FormElement( + id = 10, + inputType = InputType.DATE_PICKER, + title = resources.getString(R.string.test_up_date), + arrayId = -1, + required = true, + max = System.currentTimeMillis(), + min = System.currentTimeMillis() - (90L * 24 * 60 * 60 * 1000), + hasDependants = true + + ) + private var followUpPoint = FormElement( + id = 11, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.follow_up), + arrayId = R.array.follow_up_array, + entries = resources.getStringArray(R.array.follow_up_array), + required = false, + hasDependants = true + ) + + private var referredTo = FormElement( + id = 12, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.refer_to), + arrayId = R.array.dc_refer, + entries = resources.getStringArray(R.array.dc_refer), + required = false, + hasDependants = true + ) + private var other = FormElement( + id = 13, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.other), + required = true, + hasDependants = false + ) + + + + + + + suspend fun setUpPage(ben: BenRegCache?, saved: KalaAzarScreeningCache?) { + val list = mutableListOf( + dateOfCase, + beneficiaryStatus, + ) + if (saved == null) { + dateOfCase.value = getDateFromLong(System.currentTimeMillis()) + beneficiaryStatus.value = resources.getStringArray(R.array.benificary_case_status_kalaazar)[0] + caseStatus.value = resources.getStringArray(R.array.dc_case_status)[0] + } else { + + dateOfCase.value = getDateFromLong(saved.visitDate) + + rapidDiagnostic.value = getLocalValueInArray(R.array.positive_negative, saved.rapidDiagnosticTest) + ?: resources.getStringArray(R.array.positive_negative)[2] + + val caseStatusArray = resources.getStringArray(R.array.dc_case_status) + if (saved.kalaAzarCaseStatus == "Suspected") { + caseStatus.entries = caseStatusArray + .filter { it == caseStatusArray[1] || it == caseStatusArray[2] } + .toTypedArray() + } else if (saved.kalaAzarCaseStatus == "Confirmed") { + caseStatus.entries = caseStatusArray + .filter { it == caseStatusArray[3] } + .toTypedArray() + } else { + caseStatus.entries = caseStatusArray + } + if (saved.kalaAzarCaseStatus != null) { + caseStatus.value = + getLocalValueInArray(R.array.dc_case_status, saved.kalaAzarCaseStatus) + } + referredTo.value = + getLocalValueInArray(referredTo.arrayId, saved.referToName) + if (referredTo.value == referredTo.entries!!.last()) { + referredTo.value = saved.referToName + list.add(list.indexOf(referredTo) + 1, other) + } else { + referredTo.value = + getLocalValueInArray(referredTo.arrayId, saved.referToName) + } + beneficiaryStatus.value = + getLocalValueInArray(beneficiaryStatus.arrayId, saved.beneficiaryStatus) + other.value = saved.otherReferredFacility + if (beneficiaryStatus.value == beneficiaryStatus.entries!![beneficiaryStatus.entries!!.size - 2]) { + list.add(list.indexOf(beneficiaryStatus) + 1, dateOfDeath) + list.add(list.indexOf(beneficiaryStatus) + 2, placeOfDeath) + list.add(list.indexOf(beneficiaryStatus) + 3, reasonOfDeath) + dateOfDeath.value = + getDateFromLong(saved.dateOfDeath) + placeOfDeath.value = + getLocalValueInArray(placeOfDeath.arrayId, saved.placeOfDeath) + if (placeOfDeath.value == placeOfDeath.entries!!.last()) { + list.add(list.indexOf(placeOfDeath) + 1, otherPlaceOfDeath) + otherPlaceOfDeath.value = saved.otherPlaceOfDeath + } + + reasonOfDeath.value = + getLocalValueInArray(reasonOfDeath.arrayId, saved.reasonForDeath) + if (reasonOfDeath.value == reasonOfDeath.entries!!.last()) { + list.add(list.indexOf(reasonOfDeath) + 1, otherReasonOfDeath) + otherReasonOfDeath.value = saved.otherReasonForDeath + } + } else { + list.add(list.indexOf(beneficiaryStatus) + 1, caseStatus) + list.add(list.indexOf(beneficiaryStatus) + 2, rapidDiagnostic) + list.add(list.indexOf(beneficiaryStatus) + 3, followUpPoint) + list.add(list.indexOf(beneficiaryStatus) + 4, referredTo) + if (rapidDiagnostic.value != resources.getStringArray(R.array.positive_negative)[2]) { + list.add(list.indexOf(rapidDiagnostic) + 1, dateOfTest) + dateOfTest.value = getDateFromLong(saved.dateOfRdt) + } + + + + } + referredTo.value = + getLocalValueInArray(referredTo.arrayId, saved.referToName) + if (referredTo.value == referredTo.entries!!.last()) { + referredTo.value = saved.referToName + list.add(list.indexOf(referredTo) + 1, other) + } else { + referredTo.value = + getLocalValueInArray(referredTo.arrayId, saved.referToName) + } + + other.value = saved.otherReferredFacility + } + + + setUpPage(list) + + } + + override suspend fun handleListOnValueChanged(formId: Int, index: Int): Int { + return when (formId) { + beneficiaryStatus.id -> { + if (beneficiaryStatus.value == beneficiaryStatus.entries!![beneficiaryStatus.entries!!.size - 2] + ) { + triggerDependants( + source = beneficiaryStatus, + addItems = listOf(dateOfDeath, placeOfDeath, reasonOfDeath), + removeItems = listOf( + caseStatus, rapidDiagnostic,followUpPoint, referredTo, + ) + ) + } else { + triggerDependants( + source = beneficiaryStatus, + addItems = listOf( + caseStatus, rapidDiagnostic, referredTo + ), + removeItems = listOf(dateOfDeath, placeOfDeath, reasonOfDeath,otherPlaceOfDeath,otherReasonOfDeath) + ) + } + 0 + } + + other.id -> { + validateEmptyOnEditText(other) + } + + referredTo.id -> { + if (referredTo.value == referredTo.entries!!.last()) { + triggerDependants( + source = referredTo, + addItems = listOf(other), + removeItems = listOf() + ) + } else { + triggerDependants( + source = referredTo, + addItems = listOf(), + removeItems = listOf(other) + ) + } + 0 + } + + placeOfDeath.id -> { + if (placeOfDeath.value == placeOfDeath.entries!!.last()) { + triggerDependants( + source = placeOfDeath, + addItems = listOf(otherPlaceOfDeath), + removeItems = listOf() + ) + } else { + triggerDependants( + source = placeOfDeath, + addItems = listOf(), + removeItems = listOf(otherPlaceOfDeath) + ) + } + 0 + } + + otherPlaceOfDeath.id -> { + validateEmptyOnEditText(otherPlaceOfDeath) + } + + rapidDiagnostic.id -> { + if (rapidDiagnostic.value == resources.getStringArray(R.array.positive_negative)[2]) { + triggerDependants( + source = rapidDiagnostic, + addItems = listOf(), + removeItems = listOf(dateOfTest) + ) + caseStatus.value = + getLocalValueInArray( + caseStatus.arrayId, + resources.getStringArray(R.array.dc_case_status)[0] + ) + } else if (rapidDiagnostic.value == resources.getStringArray(R.array.positive_negative)[1]) { + triggerDependants( + source = rapidDiagnostic, + addItems = listOf(dateOfTest), + removeItems = listOf() + ) + caseStatus.value = + getLocalValueInArray( + caseStatus.arrayId, + resources.getStringArray(R.array.dc_case_status)[1] + ) + } else { + triggerDependants( + source = rapidDiagnostic, + addItems = listOf(dateOfTest), + removeItems = listOf() + ) + caseStatus.value = + getLocalValueInArray( + caseStatus.arrayId, + resources.getStringArray(R.array.dc_case_status)[0] + ) + } + 0 + } + + + reasonOfDeath.id -> { + if (reasonOfDeath.value == reasonOfDeath.entries!!.last()) { + triggerDependants( + source = reasonOfDeath, + addItems = listOf(otherReasonOfDeath), + removeItems = listOf() + ) + } else { + triggerDependants( + source = reasonOfDeath, + addItems = listOf(), + removeItems = listOf(otherReasonOfDeath) + ) + } + 0 + } + otherReasonOfDeath.id -> { + validateEmptyOnEditText(otherReasonOfDeath) + } + + else -> { + 0 + } + } + } + + override fun mapValues(cacheModel: FormDataModel, pageNumber: Int) { + (cacheModel as KalaAzarScreeningCache).let { form -> + form.visitDate = getLongFromDate(dateOfCase.value) + form.referToName = getEnglishValueInArray(R.array.dc_refer, referredTo.value) + form.referredTo = referredTo.getPosition() + form.beneficiaryStatus = getEnglishValueInArray(R.array.benificary_case_status_kalaazar, beneficiaryStatus.value) + form.beneficiaryStatusId = beneficiaryStatus.getPosition() + form.reasonForDeath = getEnglishValueInArray(R.array.reason_death, reasonOfDeath.value) + form.kalaAzarCaseStatus = getEnglishValueInArray(R.array.dc_case_status, caseStatus.value) + form.otherPlaceOfDeath = otherPlaceOfDeath.value + form.otherReasonForDeath = otherReasonOfDeath.value + form.dateOfDeath = getLongFromDate(dateOfDeath.value) + form.dateOfRdt = getLongFromDate(dateOfTest.value) + form.placeOfDeath = getEnglishValueInArray(R.array.death_place, placeOfDeath.value) + form.otherReferredFacility = other.value + form.rapidDiagnosticTest = getEnglishValueInArray(R.array.positive_negative, rapidDiagnostic.value) + form.diseaseTypeID = 2 + form.createdDate = getLongFromDate(dateOfCase.value) + form.followUpPoint = followUpPoint.value?.toIntOrNull() ?: 0 + + } + } + + + + + fun updateBen(benRegCache: BenRegCache) { + benRegCache.genDetails?.let { + it.reproductiveStatus = + englishResources.getStringArray(R.array.nbr_reproductive_status_array2)[1] + it.reproductiveStatusId = 2 + } + if (benRegCache.processed != "N") benRegCache.processed = "U" + } + + + + fun getIndexOfDate(): Int { + return getIndexById(dateOfCase.id) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/LeprosyConfirmedDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/LeprosyConfirmedDataset.kt new file mode 100644 index 000000000..eea646909 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/LeprosyConfirmedDataset.kt @@ -0,0 +1,694 @@ +package org.piramalswasthya.sakhi.configuration + +import android.content.Context +import android.widget.Toast +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.helpers.Languages +import org.piramalswasthya.sakhi.model.BenRegCache +import org.piramalswasthya.sakhi.model.FormElement +import org.piramalswasthya.sakhi.model.InputType +import org.piramalswasthya.sakhi.model.LeprosyFollowUpCache +import org.piramalswasthya.sakhi.model.LeprosyScreeningCache +import java.util.Calendar + +class LeprosyConfirmedDataset( + context: Context, currentLanguage: Languages +) : Dataset(context, currentLanguage) { + + private var homeVisitDateLong: Long = System.currentTimeMillis() + private var treatmentStartDateLong: Long = 0L + private var lastFollowUpDateLong: Long = 0L + private var isNewFollowUp: Boolean = false + private var nextFollowUpMonthStart: Long = 0L + private var nextFollowUpMonthEnd: Long = 0L + private var context = context + + private val dateOfCase = FormElement( + id = 1, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.home_visit_date), + arrayId = -1, + required = false, + max = System.currentTimeMillis(), + hasDependants = true, + isEnabled = false + ) + + private val leprosyStatus = FormElement( + id = 8, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.str_leprosy_status), + arrayId = R.array.leprosy_status, + entries = resources.getStringArray(R.array.leprosy_status), + required = false, + hasDependants = true, + isEnabled = false + ) + + private var referredTo = FormElement( + id = 9, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.refer_to), + arrayId = R.array.dc_refer, + entries = resources.getStringArray(R.array.dc_refer), + required = false, + hasDependants = true, + isEnabled = false + ) + private var other = FormElement( + id = 10, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.other), + required = false, + hasDependants = false + ) + + private val followUpdate = FormElement( + id = 12, + inputType = InputType.DATE_PICKER, + title = resources.getString(R.string.follow_up_date), + arrayId = -1, + required = true, + max = System.currentTimeMillis(), + min = System.currentTimeMillis() - (90L * 24 * 60 * 60 * 1000), + hasDependants = true + ) + + private val remarks = FormElement( + id = 13, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.pnc_remarks), + required = false, + isEnabled = false + ) + + private val leprosySymptoms = FormElement( + id = 14, + inputType = InputType.RADIO, + arrayId = R.array.yes_no, + title = resources.getString(R.string.any_leprosy_symptoms_present), + entries = resources.getStringArray(R.array.yes_no), + hasDependants = true, + required = false, + isEnabled = false + ) + + private val visitLabel = FormElement( + id = 15, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.visits), + required = false, + isEnabled = false + ) + + private val typeOfLeprosy = FormElement( + id = 16, + inputType = InputType.RADIO, + isEnabled = false, + required = false, + title = resources.getString(R.string.type_of_leprocy), + arrayId = R.array.type_of_leprocy, + entries = resources.getStringArray(R.array.type_of_leprocy), + hasDependants = true + ) + + private val treatmentStartDate = FormElement( + id = 17, + inputType = InputType.DATE_PICKER, + title = resources.getString(R.string.treatment_start_date), + required = true, + isEnabled = true, + hasDependants = true, + max = System.currentTimeMillis(), + ) + + private val mdt_blister_pack_recived = FormElement( + id = 18, + inputType = InputType.RADIO, + title = resources.getString(R.string.mdt_blister_pack_received), + arrayId = R.array.yes_no, + entries = resources.getStringArray(R.array.yes_no), + required = false, + isEnabled = true, + ) + + private val treatmentStatus = FormElement( + id = 19, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.treatment_status), + arrayId = R.array.leprosy_treatment_status_before_time, + entries = resources.getStringArray(R.array.leprosy_treatment_status_before_time), + required = true, + isEnabled = true, + ) + + private val treatmentEndDate = FormElement( + id = 20, + inputType = InputType.DATE_PICKER, + title = resources.getString(R.string.actual_treatment_completion_date), + required = true, + isEnabled = true, + max = System.currentTimeMillis(), + ) + + private val treatmentEndDateEstimation = FormElement( + id = 21, + inputType = InputType.DATE_PICKER, + title = resources.getString(R.string.expected_treatment_completion_date), + required = false, + isEnabled = false, + hasDependants = true, + max = System.currentTimeMillis(), + ) + + + suspend fun setUpPage( + ben: BenRegCache?, + saved: LeprosyScreeningCache?, + followUp: LeprosyFollowUpCache? + ) { + val list = mutableListOf( + dateOfCase, + leprosySymptoms, + visitLabel, + leprosyStatus, + referredTo, + typeOfLeprosy, + treatmentStartDate, + treatmentEndDateEstimation, + followUpdate, + mdt_blister_pack_recived, + + ) + + homeVisitDateLong = saved?.homeVisitDate ?: System.currentTimeMillis() + treatmentStartDateLong = + saved?.treatmentStartDate?.takeIf { it > 0L } ?: followUp?.treatmentStartDate ?: 0L + lastFollowUpDateLong = followUp?.followUpDate ?: 0L + // isNewFollowUp = followUp == null + + if (saved == null) { + dateOfCase.value = getDateFromLong(System.currentTimeMillis()) + leprosyStatus.value = resources.getStringArray(R.array.leprosy_status)[0] + visitLabel.value = resources.getString(R.string.visit_number_label, saved?.currentVisitNumber ?: 1) + leprosySymptoms.value = resources.getStringArray(R.array.yes_no)[1] + + updateDateConstraints() + checkFollowUpDateAvailability() + calculateAndSetExpectedCompletionDate() + + } else { + dateOfCase.value = getDateFromLong(saved.homeVisitDate) + leprosySymptoms.value = + getLocalValueInArray(leprosySymptoms.arrayId, saved.leprosySymptoms) + typeOfLeprosy.value = getLocalValueInArray(typeOfLeprosy.arrayId, saved.typeOfLeprosy) + treatmentStartDate.value = getDateFromLong(treatmentStartDateLong) + val symptomsPosition = saved.leprosySymptomsPosition ?: 1 + leprosySymptoms.value = + resources.getStringArray(R.array.yes_no).getOrNull(symptomsPosition) + ?: resources.getStringArray(R.array.yes_no)[1] + val visit_value = saved.visitLabel + val visitNum = visit_value?.substringAfterLast("-")?.toIntOrNull() ?: 1 + visitLabel.value = resources.getString(R.string.visit_number_label, visitNum) + leprosyStatus.value = getLocalValueInArray(leprosyStatus.arrayId, saved.leprosyStatus) + + if (leprosyStatus.value == leprosyStatus.entries!!.last()) { + leprosyStatus.value = saved.leprosyStatus + list.add(list.indexOf(leprosyStatus) + 1, other) + } else { + leprosyStatus.value = + getLocalValueInArray(leprosyStatus.arrayId, saved.leprosyStatus) + } + + remarks.value = saved.remarks + referredTo.value = getLocalValueInArray(referredTo.arrayId, saved.referToName) + if (referredTo.value == referredTo.entries!!.last()) { + referredTo.value = saved.referToName + list.add(list.indexOf(referredTo) + 1, other) + } else { + referredTo.value = getLocalValueInArray(referredTo.arrayId, saved.referToName) + } + treatmentStartDate.value = getDateFromLong(treatmentStartDateLong) + + other.value = saved.otherReferredTo + + updateDateConstraints() + checkFollowUpDateAvailability() + calculateAndSetExpectedCompletionDate() + + + } + + if (followUp == null) { + visitLabel.value = resources.getString(R.string.visit_number_label, saved?.currentVisitNumber ?: 1) + // followUpdate.value = getDateFromLong(System.currentTimeMillis()) + isNewFollowUp = true + } else { + isNewFollowUp = false + visitLabel.value = resources.getString(R.string.visit_number_label, saved?.currentVisitNumber ?: 1) + // followUpdate.value = getDateFromLong(followUp.followUpDate) + mdt_blister_pack_recived.value = getLocalValueInArray( + mdt_blister_pack_recived.arrayId, + followUp.mdtBlisterPackReceived + ) + treatmentStatus.value = getLocalValueInArray( + treatmentStatus.arrayId, + followUp.treatmentStatus + ) + + if (followUp.treatmentCompleteDate > 0) { + treatmentEndDate.value = getDateFromLong(followUp.treatmentCompleteDate) + list.add(list.indexOf(treatmentStatus) + 1, treatmentEndDate) + } + + remarks.value = followUp.remarks + + updateDateConstraints() + checkFollowUpDateAvailability() + calculateAndSetExpectedCompletionDate() + } + + setUpPage(list) + } + + suspend fun setUpPage(ben: BenRegCache?, followUp: LeprosyFollowUpCache?) { + val list = mutableListOf( + dateOfCase, + leprosySymptoms, + visitLabel, + leprosyStatus, + referredTo, + typeOfLeprosy, + treatmentStartDate, + followUpdate, + mdt_blister_pack_recived, + treatmentStatus, + ) + + dateOfCase.isEnabled = false + leprosyStatus.isEnabled = false + visitLabel.isEnabled = false + leprosySymptoms.isEnabled = false + referredTo.isEnabled = false + typeOfLeprosy.isEnabled = false + treatmentStartDate.isEnabled = false + followUpdate.isEnabled = false + mdt_blister_pack_recived.isEnabled = false + treatmentStatus.isEnabled = false + + homeVisitDateLong = followUp?.homeVisitDate ?: System.currentTimeMillis() + treatmentStartDateLong = followUp?.treatmentStartDate ?: 0L + lastFollowUpDateLong = followUp?.followUpDate ?: 0L + + treatmentStatus.arrayId = R.array.leprosy_treatment_status + treatmentStatus.entries = resources.getStringArray(R.array.leprosy_treatment_status) + if (followUp == null) { + dateOfCase.value = getDateFromLong(System.currentTimeMillis()) + leprosyStatus.value = resources.getStringArray(R.array.leprosy_status)[0] + visitLabel.value = resources.getString(R.string.visit_number_label, 1) + leprosySymptoms.value = resources.getStringArray(R.array.yes_no)[1] + isNewFollowUp = true + updateDateConstraints() + } else { + isNewFollowUp = false + dateOfCase.value = getDateFromLong(followUp.homeVisitDate) + leprosySymptoms.value = + getLocalValueInArray(leprosySymptoms.arrayId, followUp.leprosySymptoms) + typeOfLeprosy.value = + getLocalValueInArray(typeOfLeprosy.arrayId, followUp.typeOfLeprosy) + treatmentStartDate.value = getDateFromLong(followUp.treatmentStartDate) + val symptomsPosition = followUp.leprosySymptomsPosition ?: 1 + leprosySymptoms.value = + resources.getStringArray(R.array.yes_no).getOrNull(symptomsPosition) + ?: resources.getStringArray(R.array.yes_no)[1] + val visit_value = followUp.visitLabel + val visitNum = visit_value?.substringAfterLast("-")?.toIntOrNull() ?: 1 + visitLabel.value = resources.getString(R.string.visit_number_label, visitNum) + leprosyStatus.value = + getLocalValueInArray(leprosyStatus.arrayId, followUp.leprosyStatus) + + if (leprosyStatus.value == leprosyStatus.entries!!.last()) { + leprosyStatus.value = followUp.leprosyStatus + list.add(list.indexOf(leprosyStatus) + 1, other) + } else { + leprosyStatus.value = + getLocalValueInArray(leprosyStatus.arrayId, followUp.leprosyStatus) + } + + remarks.value = followUp.remarks + referredTo.value = getLocalValueInArray(referredTo.arrayId, followUp.referToName) + if (referredTo.value == referredTo.entries!!.last()) { + referredTo.value = followUp.referToName + list.add(list.indexOf(referredTo) + 1, other) + } else { + referredTo.value = getLocalValueInArray(referredTo.arrayId, followUp.referToName) + } + treatmentStartDate.value = getDateFromLong(followUp.treatmentStartDate) + followUpdate.value = getDateFromLong(followUp.followUpDate) + mdt_blister_pack_recived.value = getLocalValueInArray( + mdt_blister_pack_recived.arrayId, + followUp.mdtBlisterPackReceived + ) + + treatmentStatus.value = getLocalValueInArray( + treatmentStatus.arrayId, + followUp.treatmentStatus + ) + + if (followUp.treatmentCompleteDate > 0) { + treatmentEndDate.value = getDateFromLong(followUp.treatmentCompleteDate) + list.add(list.indexOf(treatmentStatus) + 1, treatmentEndDate) + } + + updateDateConstraints() + } + + setUpPage(list) + } + + override suspend fun handleListOnValueChanged(formId: Int, index: Int): Int { + when (formId) { + treatmentStatus.id -> { + + if (treatmentStatus.value == treatmentStatus.entries!!.last()) { + triggerDependants( + source = treatmentStatus, + addItems = listOf(treatmentEndDate), + removeItems = listOf() + ) + } else { + triggerDependants( + source = treatmentStatus, + addItems = listOf(), + removeItems = listOf(treatmentEndDate) + ) + } + } + + treatmentStartDate.id -> { + treatmentStartDateLong = getLongFromDate(treatmentStartDate.value) + calculateAndSetExpectedCompletionDate() + followUpdate.min = treatmentStartDateLong + followUpdate.isEnabled = true + + } + + followUpdate.id -> { + // lastFollowUpDateLong = getLongFromDate(followUpdate.value) + updateTreatmentStatusDropdown() + triggerDependants( + source = followUpdate, + addItems = listOf(treatmentStatus), + removeItems = listOf() + ) + + + } + + } + return 0 + } + + override fun mapValues(cacheModel: FormDataModel, pageNumber: Int) { + (cacheModel as LeprosyFollowUpCache).let { followUp -> + followUp.followUpDate = getLongFromDate(followUpdate.value) + followUp.mdtBlisterPackReceived = getEnglishValueInArray(R.array.yes_no, mdt_blister_pack_recived.value) + followUp.treatmentStatus = getEnglishValueInArray(treatmentStatus.arrayId, treatmentStatus.value) + followUp.treatmentStartDate = getLongFromDate(treatmentStartDate.value) + + if (treatmentStatus.value == treatmentStatus.entries?.last()) { + followUp.treatmentCompleteDate = getLongFromDate(treatmentEndDate.value) + } else { + followUp.treatmentCompleteDate = 0 + } + + followUp.remarks = remarks.value + } + } + + + private fun calculateAndSetExpectedCompletionDate() { + if (treatmentStartDateLong == 0L) { + treatmentEndDateEstimation.value = "" + return + } + + val typeOfLeprosyValue = typeOfLeprosy.value + if (typeOfLeprosyValue.isNullOrBlank()) { + treatmentEndDateEstimation.value = "" + return + } + + val leprosyTypes = resources.getStringArray(R.array.type_of_leprocy) + val index = leprosyTypes.indexOf(typeOfLeprosyValue) + + val monthsToAdd = when (index) { + 0 -> 12 + 1 -> 6 + else -> { + treatmentEndDateEstimation.value = "" + return + } + } + + val calendar = Calendar.getInstance() + calendar.timeInMillis = treatmentStartDateLong + calendar.add(Calendar.MONTH, monthsToAdd) + + treatmentEndDateEstimation.value = getDateFromLong(calendar.timeInMillis) + } + + fun getIndexOfDate(): Int { + return getIndexById(followUpdate.id) + } + + fun updateBen(benRegCache: BenRegCache) { + benRegCache.genDetails?.let { + it.reproductiveStatus = + englishResources.getStringArray(R.array.nbr_reproductive_status_array2)[1] + it.reproductiveStatusId = 2 + } + if (benRegCache.processed != "N") benRegCache.processed = "U" + } + + private fun getFirstDayOfNextMonth(date: Long): Long { + val calendar = Calendar.getInstance() + calendar.timeInMillis = date + calendar.set(Calendar.DAY_OF_MONTH, 1) + calendar.add(Calendar.MONTH, 1) + calendar.set(Calendar.HOUR_OF_DAY, 0) + calendar.set(Calendar.MINUTE, 0) + calendar.set(Calendar.SECOND, 0) + calendar.set(Calendar.MILLISECOND, 0) + return calendar.timeInMillis + } + + private fun getLastDayOfNextMonth(date: Long): Long { + val calendar = Calendar.getInstance() + calendar.timeInMillis = date + calendar.set(Calendar.DAY_OF_MONTH, 1) + calendar.add(Calendar.MONTH, 2) + calendar.add(Calendar.DAY_OF_MONTH, -1) + calendar.set(Calendar.HOUR_OF_DAY, 23) + calendar.set(Calendar.MINUTE, 59) + calendar.set(Calendar.SECOND, 59) + calendar.set(Calendar.MILLISECOND, 999) + return calendar.timeInMillis + } + + private fun isSameMonth(date1: Long, date2: Long): Boolean { + val cal1 = Calendar.getInstance() + val cal2 = Calendar.getInstance() + cal1.timeInMillis = date1 + cal2.timeInMillis = date2 + return cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) && + cal1.get(Calendar.MONTH) == cal2.get(Calendar.MONTH) + } + + private fun isSameOrAfterMonth(date1: Long, date2: Long): Boolean { + val cal1 = Calendar.getInstance() + val cal2 = Calendar.getInstance() + + cal1.timeInMillis = date1 + cal2.timeInMillis = date2 + + val year1 = cal1.get(Calendar.YEAR) + val year2 = cal2.get(Calendar.YEAR) + + val month1 = cal1.get(Calendar.MONTH) + val month2 = cal2.get(Calendar.MONTH) + + return when { + year1 > year2 -> true + year1 == year2 && month1 >= month2 -> true + else -> false + } + } + + private fun getFirstDayOfCurrentMonth(): Long { + val calendar = Calendar.getInstance() + calendar.set(Calendar.DAY_OF_MONTH, 1) + calendar.set(Calendar.HOUR_OF_DAY, 0) + calendar.set(Calendar.MINUTE, 0) + calendar.set(Calendar.SECOND, 0) + calendar.set(Calendar.MILLISECOND, 0) + return calendar.timeInMillis + } + + private fun updateTreatmentStatusDropdown() { + val followUpDateLong = getLongFromDate(followUpdate.value) + val expectedEndDateLong = getLongFromDate(treatmentEndDateEstimation.value) + + if (followUpDateLong > 0 && expectedEndDateLong > 0 && + isSameOrAfterMonth(followUpDateLong, expectedEndDateLong) + ) { + treatmentStatus.arrayId = R.array.leprosy_treatment_status + treatmentStatus.entries = + resources.getStringArray(R.array.leprosy_treatment_status) + } else { + treatmentStatus.arrayId = R.array.leprosy_treatment_status_before_time + treatmentStatus.entries = + resources.getStringArray(R.array.leprosy_treatment_status_before_time) + } + + if (!treatmentStatus.entries!!.contains(treatmentStatus.value)) { + treatmentStatus.value = "" + } + } + + + private fun checkFollowUpDateAvailability() { + val currentTime = System.currentTimeMillis() + if (treatmentStartDateLong == 0L) { + followUpdate.isEnabled = false + return + } + if (lastFollowUpDateLong == 0L) { + followUpdate.isEnabled = true + return + } + + nextFollowUpMonthStart = getFirstDayOfNextMonth(lastFollowUpDateLong) + nextFollowUpMonthEnd = getLastDayOfNextMonth(lastFollowUpDateLong) + + if (currentTime < nextFollowUpMonthStart) { + followUpdate.isEnabled = false + Toast.makeText( + context, + "Next follow-up will be available from ${getDateFromLong(nextFollowUpMonthStart)}", + Toast.LENGTH_LONG + ).show() + } else if (currentTime >= nextFollowUpMonthStart && currentTime <= nextFollowUpMonthEnd) { + followUpdate.isEnabled = true + // followUpdate.errorMessage = null + } else { + followUpdate.isEnabled = true + } + } + + fun validateFollowUpDate(selectedDate: Long): String? { + if (lastFollowUpDateLong == 0L) { + if (selectedDate < homeVisitDateLong) { + return "Follow-up date cannot be before home visit date" + } + if (selectedDate > System.currentTimeMillis()) { + return "Follow-up date cannot be in the future" + } + return null + } + + val nextMonthStart = getFirstDayOfNextMonth(lastFollowUpDateLong) + val currentTime = System.currentTimeMillis() + + /* if (selectedDate < nextMonthStart) { + return "Next follow-up must be in the next month. Please select a date from ${getDateFromLong(nextMonthStart)}" + }*/ + + if (selectedDate > currentTime) { + return "Follow-up date cannot be in the future" + } + + /* if (!isSameOrAfterMonth(selectedDate, nextMonthStart)) { + val cal = Calendar.getInstance() + cal.timeInMillis = nextMonthStart + val monthName = getMonthName(cal.get(Calendar.MONTH)) + return "Follow-up must be in $monthName. Please select a date between ${getDateFromLong(nextMonthStart)} and ${getDateFromLong(getLastDayOfNextMonth(lastFollowUpDateLong))}" + }*/ + + return null + } + + private fun getMonthName(month: Int): String { + val monthNames = arrayOf( + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December" + ) + return monthNames.getOrElse(month) { "Unknown" } + } + + + fun validateForm(): String? { + if (followUpdate.value!!.isBlank()) { + return "Follow-up date is required" + } + + val selectedDate = getLongFromDate(followUpdate.value) + val dateValidation = validateFollowUpDate(selectedDate) + if (dateValidation != null) { + return dateValidation + } + + if (treatmentStatus.value!!.isBlank()) { + return "Treatment status is required" + } + + if (treatmentStatus.value == treatmentStatus.entries?.last() && treatmentEndDate.value!!.isBlank()) { + return "Treatment end date is required when treatment status is completed" + } + + return null + } + + + private fun updateDateConstraints() { + treatmentStartDate.min = homeVisitDateLong + if (treatmentStartDateLong > 0) { + treatmentStartDate.isEnabled = false + treatmentStartDate.required = false + + } else { + followUpdate.isEnabled = false + return + } + + if (lastFollowUpDateLong > 0) { + val nextMonthStart = getFirstDayOfNextMonth(lastFollowUpDateLong) + followUpdate.min = nextMonthStart + followUpdate.max = System.currentTimeMillis() + } else { + followUpdate.min = treatmentStartDateLong + followUpdate.max = System.currentTimeMillis() + } + + val followUpDateLong = getLongFromDate(followUpdate.value) + treatmentEndDate.min = if (followUpDateLong > 0) followUpDateLong else followUpdate.min + } + + + fun getNextFollowUpAvailabilityMessage(): String? { + if (lastFollowUpDateLong == 0L) { + return null + } + + val nextMonthStart = getFirstDayOfNextMonth(lastFollowUpDateLong) + val currentTime = System.currentTimeMillis() + + if (currentTime < nextMonthStart) { + return "Next follow-up will be available from ${getDateFromLong(nextMonthStart)}" + } + + return null + } + + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/LeprosyFormDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/LeprosyFormDataset.kt new file mode 100644 index 000000000..861bd5f3c --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/LeprosyFormDataset.kt @@ -0,0 +1,721 @@ +package org.piramalswasthya.sakhi.configuration + +import android.content.Context +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.helpers.Languages +import org.piramalswasthya.sakhi.model.BenRegCache +import org.piramalswasthya.sakhi.model.FormElement +import org.piramalswasthya.sakhi.model.InputType +import org.piramalswasthya.sakhi.model.LeprosyScreeningCache +import java.util.Calendar + +class LeprosyFormDataset( + context: Context, currentLanguage: Languages +) : Dataset(context, currentLanguage) { + + private fun getOneYearBeforeCurrentDate(): Long { + val calendar = Calendar.getInstance() + calendar.add(Calendar.YEAR, -1) + calendar.set(Calendar.HOUR_OF_DAY, 0) + calendar.set(Calendar.MINUTE, 0) + calendar.set(Calendar.SECOND, 0) + calendar.set(Calendar.MILLISECOND, 0) + return calendar.timeInMillis + } + + private val title = FormElement( + id = 28, + inputType = InputType.CHECKBOXES, + title = resources.getString(R.string.leprosy_title), + arrayId = -1, + required = false, + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + ) + private val dateOfCase = FormElement( + id = 1, + inputType = InputType.DATE_PICKER, + title = resources.getString(R.string.home_visit_date_screening), + arrayId = -1, + required = true, + max = System.currentTimeMillis(), + min = getOneYearBeforeCurrentDate(), + hasDependants = true + + ) + private val beneficiaryStatus = FormElement( + id = 2, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.beneficiary_status), + arrayId = R.array.benificary_case_status_leprosy, + entries = resources.getStringArray(R.array.benificary_case_status_leprosy), + required = true, + hasDependants = true + + ) + private val dateOfDeath = FormElement( + id = 3, + inputType = InputType.DATE_PICKER, + title = resources.getString(R.string.death_date), + arrayId = -1, + required = true, + min = System.currentTimeMillis() - (90L * 24 * 60 * 60 * 1000), + max = System.currentTimeMillis(), + hasDependants = true + + ) + + private val placeOfDeath = FormElement( + id = 4, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.place_of_Death), + arrayId = R.array.death_place, + entries = resources.getStringArray(R.array.death_place), + required = true, + hasDependants = true + + ) + + private var otherPlaceOfDeath = FormElement( + id = 5, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.other_place), + required = true, + hasDependants = false + ) + + private val reasonOfDeath = FormElement( + id = 6, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.reason_of_Death), + arrayId = R.array.reason_death, + entries = resources.getStringArray(R.array.reason_death), + required = true, + hasDependants = true + + ) + private var otherReasonOfDeath = FormElement( + id = 7, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.other_reason), + required = true, + hasDependants = false + ) + private val leprosyStatus = FormElement( + id = 8, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.str_leprosy_status), + arrayId = R.array.leprosy_status, + entries = resources.getStringArray(R.array.leprosy_status), + required = false, + isEnabled = false, + hasDependants = true + + ) + + private var referredTo = FormElement( + id = 9, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.refer_to), + arrayId = R.array.dc_refer, + entries = resources.getStringArray(R.array.dc_refer), + required = false, + hasDependants = true + ) + private var other = FormElement( + id = 10, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.other), + required = true, + hasDependants = false + ) + private var typeOfLeprosy = FormElement( + id = 11, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.type_of_leprocy), + arrayId = R.array.type_of_leprocy, + entries = resources.getStringArray(R.array.type_of_leprocy), + required = false, + hasDependants = true + ) + + private val followUpdate = FormElement( + id = 12, + inputType = InputType.DATE_PICKER, + title = resources.getString(R.string.follow_up_date), + arrayId = -1, + required = false, + max = System.currentTimeMillis(), + min = System.currentTimeMillis() - (90L * 24 * 60 * 60 * 1000), + hasDependants = true + + ) + private val remarks = FormElement( + id = 13, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.pnc_remarks), + required = false, + + ) + + private val recurrentUlceration = FormElement( + id = 16, + inputType = InputType.RADIO, + arrayId = R.array.yes_no, + title = resources.getString(R.string.cbac_recurrent_ulceration), + entries = resources.getStringArray(R.array.yes_no), + hasDependants = true, + required = true, + isEnabled = true + ) + + private val cbacRecurrentTingling = FormElement( + id = 17, + inputType = InputType.RADIO, + arrayId = R.array.yes_no, + title = resources.getString(R.string.cbac_recurrent_tingling), + entries = resources.getStringArray(R.array.yes_no), + hasDependants = true, + required = true, + isEnabled = true + ) + private val cbacAnyHyperPigmented = FormElement( + id = 18, + inputType = InputType.RADIO, + arrayId = R.array.yes_no, + title = resources.getString(R.string.cbac_Any_hyper_pigmented), + entries = resources.getStringArray(R.array.yes_no), + hasDependants = true, + required = true, + isEnabled = true + ) + + private val cbacAnyThickendSkin = FormElement( + id = 19, + inputType = InputType.RADIO, + arrayId = R.array.yes_no, + title = resources.getString(R.string.cbac_any_thickend_skin), + entries = resources.getStringArray(R.array.yes_no), + hasDependants = true, + required = true, + isEnabled = true + ) + private val cbacAnyNodulesSkin = FormElement( + id = 20, + inputType = InputType.RADIO, + arrayId = R.array.yes_no, + title = resources.getString(R.string.cbac_any_nodules_skin), + entries = resources.getStringArray(R.array.yes_no), + hasDependants = true, + required = true, + isEnabled = true + ) + private val cbacRecurrentNumbness = FormElement( + id = 21, + inputType = InputType.RADIO, + arrayId = R.array.yes_no, + title = resources.getString(R.string.cbac_Recurrent_numbness), + entries = resources.getStringArray(R.array.yes_no), + hasDependants = true, + required = true, + isEnabled = true + ) + private val cbacClawingOfFingers = FormElement( + id = 22, + inputType = InputType.RADIO, + arrayId = R.array.yes_no, + title = resources.getString(R.string.cbac_Clawing_of_fingers), + entries = resources.getStringArray(R.array.yes_no), + hasDependants = true, + required = true, + isEnabled = true + ) + private val cbacTinglingOrNumbness = FormElement( + id = 23, + inputType = InputType.RADIO, + arrayId = R.array.yes_no, + title = resources.getString(R.string.cbac_Tingling_or_Numbness), + entries = resources.getStringArray(R.array.yes_no), + hasDependants = true, + required = true, + isEnabled = true + ) + private val cbacInabilityCloseEyelid = FormElement( + id = 24, + inputType = InputType.RADIO, + arrayId = R.array.yes_no, + title = resources.getString(R.string.cbac_Inability_close_eyelid), + entries = resources.getStringArray(R.array.yes_no), + hasDependants = true, + required = true, + isEnabled = true + ) + private val cbacDiffHoldingObjects = FormElement( + id = 25, + inputType = InputType.RADIO, + arrayId = R.array.yes_no, + title = resources.getString(R.string.cbac_diff_holding_objects), + entries = resources.getStringArray(R.array.yes_no), + hasDependants = true, + required = true, + isEnabled = true + ) + private val cbacWeeknessInFeet = FormElement( + id = 26, + inputType = InputType.RADIO, + arrayId = R.array.yes_no, + title = resources.getString(R.string.cbac_Weekness_in_feet), + entries = resources.getStringArray(R.array.yes_no), + hasDependants = true, + required = true, + isEnabled = true + ) + + private val leprosySymptoms = FormElement( + id = 14, + inputType = InputType.RADIO, + arrayId = R.array.yes_no, + title = resources.getString(R.string.any_leprosy_symptoms_present), + entries = resources.getStringArray(R.array.yes_no), + hasDependants = true, + required = true, + isEnabled = true + ) + + private val visitLabel = FormElement( + id = 15, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.visit_leprosy), + required = true, + isEnabled = false + ) + + suspend fun setUpPage(ben: BenRegCache?, saved: LeprosyScreeningCache?) { + val list = mutableListOf( + dateOfCase, + // beneficiaryStatus, + title, + recurrentUlceration, + cbacRecurrentTingling, + cbacAnyHyperPigmented, + cbacAnyThickendSkin, + cbacAnyNodulesSkin, + cbacRecurrentNumbness, + cbacClawingOfFingers, + cbacTinglingOrNumbness, + cbacInabilityCloseEyelid, + cbacDiffHoldingObjects, + cbacWeeknessInFeet, + leprosySymptoms, + visitLabel, + leprosyStatus, + + + ) + if (saved == null) { + dateOfCase.value = getDateFromLong(System.currentTimeMillis()) + leprosyStatus.value = resources.getStringArray(R.array.leprosy_status)[0] + visitLabel.value = resources.getString(R.string.visit_number_label, 1) + leprosySymptoms.value = resources.getStringArray(R.array.yes_no)[1] + } else { + dateOfCase.value = getDateFromLong(saved.homeVisitDate) + val symptomsPosition = saved.leprosySymptomsPosition ?: 1 + leprosySymptoms.value = resources.getStringArray(R.array.yes_no).getOrNull(symptomsPosition) + ?: resources.getStringArray(R.array.yes_no)[1] + visitLabel.value = resources.getString(R.string.visit_number_label, saved?.currentVisitNumber ?: 1) + leprosyStatus.value = + getLocalValueInArray(leprosyStatus.arrayId, saved.leprosyStatus) + + if (leprosyStatus.value == leprosyStatus.entries!!.last()) { + leprosyStatus.value = saved.leprosyStatus + list.add(list.indexOf(leprosyStatus) + 1, other) + } else { + leprosyStatus.value = + getLocalValueInArray(leprosyStatus.arrayId, saved.leprosyStatus) + } + list.add(list.indexOf(leprosyStatus) + 1, referredTo) + referredTo.value = + getLocalValueInArray(referredTo.arrayId, saved.referToName) + if (referredTo.value == referredTo.entries!!.last()) { + referredTo.value = saved.referToName + list.add(list.indexOf(referredTo) + 1, other) + } else { + referredTo.value = + getLocalValueInArray(referredTo.arrayId, saved.referToName) + } + val recurrentUlcerationId = saved.recurrentUlcerationId ?: 1 + val recurrentTinglingId = saved.recurrentTinglingId ?: 1 + val hypopigmentedPatchId = saved.hypopigmentedPatchId ?: 1 + val thickenedSkinId = saved.thickenedSkinId ?: 1 + val skinNodulesId = saved.skinNodulesId ?: 1 + val recurrentNumbnessId = saved.recurrentNumbnessId ?: 1 + val clawingFingersId = saved.clawingFingersId ?: 1 + val tinglingNumbnessExtremitiesId = saved.tinglingNumbnessExtremitiesId ?: 1 + val inabilityCloseEyelidId = saved.inabilityCloseEyelidId ?: 1 + val difficultyHoldingObjectsId = saved.difficultyHoldingObjectsId ?: 1 + val weaknessFeetId = saved.weaknessFeetId ?: 1 + recurrentUlceration.value = resources.getStringArray(R.array.yes_no).getOrNull(recurrentUlcerationId) + ?: resources.getStringArray(R.array.yes_no)[1] + cbacRecurrentTingling.value = resources.getStringArray(R.array.yes_no).getOrNull(recurrentTinglingId) + ?: resources.getStringArray(R.array.yes_no)[1] + cbacAnyHyperPigmented.value = resources.getStringArray(R.array.yes_no).getOrNull(hypopigmentedPatchId) + ?: resources.getStringArray(R.array.yes_no)[1] + cbacAnyThickendSkin.value = resources.getStringArray(R.array.yes_no).getOrNull(thickenedSkinId) + ?: resources.getStringArray(R.array.yes_no)[1] + cbacAnyNodulesSkin.value = resources.getStringArray(R.array.yes_no).getOrNull(skinNodulesId) + ?: resources.getStringArray(R.array.yes_no)[1] + cbacRecurrentNumbness.value = resources.getStringArray(R.array.yes_no).getOrNull(recurrentNumbnessId) + ?: resources.getStringArray(R.array.yes_no)[1] + cbacClawingOfFingers.value = resources.getStringArray(R.array.yes_no).getOrNull(clawingFingersId) + ?: resources.getStringArray(R.array.yes_no)[1] + cbacTinglingOrNumbness.value = resources.getStringArray(R.array.yes_no).getOrNull(tinglingNumbnessExtremitiesId) + ?: resources.getStringArray(R.array.yes_no)[1] + cbacInabilityCloseEyelid.value = resources.getStringArray(R.array.yes_no).getOrNull(inabilityCloseEyelidId) + ?: resources.getStringArray(R.array.yes_no)[1] + cbacDiffHoldingObjects.value = resources.getStringArray(R.array.yes_no).getOrNull(difficultyHoldingObjectsId) + ?: resources.getStringArray(R.array.yes_no)[1] + cbacWeeknessInFeet.value = resources.getStringArray(R.array.yes_no).getOrNull(weaknessFeetId) + ?: resources.getStringArray(R.array.yes_no)[1] + /*if + (beneficiaryStatus.value == beneficiaryStatus.entries!![3]) { + list.add(list.indexOf(beneficiaryStatus) + 1, dateOfDeath) + list.add(list.indexOf(beneficiaryStatus) + 2, placeOfDeath) + list.add(list.indexOf(beneficiaryStatus) + 3, reasonOfDeath) + dateOfDeath.value = + getDateFromLong(saved.dateOfDeath) + placeOfDeath.value = + getLocalValueInArray(placeOfDeath.arrayId, saved.placeOfDeath) + if (placeOfDeath.value == placeOfDeath.entries!!.last()) { + list.add(list.indexOf(placeOfDeath) + 1, otherPlaceOfDeath) + otherPlaceOfDeath.value = saved.otherPlaceOfDeath + } + + reasonOfDeath.value = + getLocalValueInArray(reasonOfDeath.arrayId, saved.reasonForDeath) + if (reasonOfDeath.value == reasonOfDeath.entries!!.last()) { + list.add(list.indexOf(reasonOfDeath) + 1, otherReasonOfDeath) + otherReasonOfDeath.value = saved.otherReasonForDeath + } + } else { + list.add(list.indexOf(beneficiaryStatus) + 1, leprosyStatus) + list.add(list.indexOf(beneficiaryStatus) + 2, typeOfLeprosy) + list.add(list.indexOf(beneficiaryStatus) + 3, followUpdate) + list.add(list.indexOf(beneficiaryStatus) + 4, remarks) + remarks.value = saved.remarks + leprosyStatus.value = getLocalValueInArray(leprosyStatus.arrayId, saved.leprosyStatus) + if (leprosyStatus.value == leprosyStatus.entries!![leprosyStatus.entries!!.size-3]) { + list.add(list.indexOf(leprosyStatus) + 1, referredTo) + + } + typeOfLeprosy.value = getLocalValueInArray(typeOfLeprosy.arrayId, saved.typeOfLeprosy) + + } + + + other.value = saved.otherReferredTo*/ + + } + + + + setUpPage(list) + + } + + override suspend fun handleListOnValueChanged(formId: Int, index: Int): Int { + + return when (formId) { + + recurrentUlceration.id, + cbacRecurrentTingling.id, + cbacAnyHyperPigmented.id, + cbacAnyThickendSkin.id, + cbacAnyNodulesSkin.id, + cbacRecurrentNumbness.id, + cbacClawingOfFingers.id, + cbacTinglingOrNumbness.id, + cbacInabilityCloseEyelid.id, + cbacDiffHoldingObjects.id, + cbacWeeknessInFeet.id -> { + + updateLeprosySymptomsFromChecklist() + 0 + } + + leprosySymptoms.id -> { + if (leprosySymptoms.value == resources.getStringArray(R.array.yes_no)[0]) { + + leprosyStatus.value = resources.getStringArray(R.array.leprosy_status)[3] + visitLabel.value = resources.getString(R.string.visit_number_label, 1) + + triggerDependants( + source = leprosySymptoms, + addItems = listOf(visitLabel,leprosyStatus,referredTo), + removeItems = listOf() + ) + + } else { + leprosyStatus.value = resources.getStringArray(R.array.leprosy_status)[0] + triggerDependants( + source = leprosySymptoms, + addItems = listOf(), + removeItems = listOf(referredTo) + ) + } + 0 + } + beneficiaryStatus.id -> { + if (beneficiaryStatus.value == beneficiaryStatus.entries!![3]!!) { + triggerDependants( + source = beneficiaryStatus, + addItems = listOf(dateOfDeath,placeOfDeath,reasonOfDeath), + removeItems = listOf(leprosyStatus,referredTo,typeOfLeprosy,followUpdate, + remarks) + ) + } else { + triggerDependants( + source = beneficiaryStatus, + addItems = listOf(leprosyStatus,typeOfLeprosy,followUpdate, + remarks), + removeItems = listOf(dateOfDeath,placeOfDeath,reasonOfDeath,otherPlaceOfDeath,otherReasonOfDeath) + ) + } + 0 + } + + + leprosyStatus.id -> { + if (leprosyStatus.value == leprosyStatus.entries!![leprosyStatus.entries!!.size-3]) { + triggerDependants( + source = leprosyStatus, + addItems = listOf(referredTo), + removeItems = listOf() + ) + } else { + triggerDependants( + source = leprosyStatus, + addItems = listOf(), + removeItems = listOf(referredTo) + ) + } + 0 + } + + referredTo.id -> { + if (referredTo.value == referredTo.entries!!.last()) { + triggerDependants( + source = referredTo, + addItems = listOf(other), + removeItems = listOf() + ) + } else { + triggerDependants( + source = referredTo, + addItems = listOf(), + removeItems = listOf(other) + ) + } + 0 + } + + + + placeOfDeath.id -> { + if (placeOfDeath.value == placeOfDeath.entries!!.last()) { + triggerDependants( + source = placeOfDeath, + addItems = listOf(otherPlaceOfDeath), + removeItems = listOf() + ) + } else { + triggerDependants( + source = placeOfDeath, + addItems = listOf(), + removeItems = listOf(otherPlaceOfDeath) + ) + } + 0 + } + reasonOfDeath.id -> { + if (reasonOfDeath.value == reasonOfDeath.entries!!.last()) { + triggerDependants( + source = reasonOfDeath, + addItems = listOf(otherReasonOfDeath), + removeItems = listOf() + ) + } else { + triggerDependants( + source = reasonOfDeath, + addItems = listOf(), + removeItems = listOf(otherReasonOfDeath) + ) + } + 0 + } + + other.id -> { + validateEmptyOnEditText(other) + } + + otherReasonOfDeath.id -> { + validateEmptyOnEditText(otherReasonOfDeath) + } + otherPlaceOfDeath.id -> { + validateEmptyOnEditText(otherPlaceOfDeath) + + } + + else -> { + 0 + } + } + } + + override fun mapValues(cacheModel: FormDataModel, pageNumber: Int) { + (cacheModel as LeprosyScreeningCache).let { form -> + form.homeVisitDate = getLongFromDate(dateOfCase.value) + form.referToName = getEnglishValueInArray(R.array.dc_refer, referredTo.value) + form.referredTo = referredTo.getPosition() + form.beneficiaryStatus = getEnglishValueInArray(R.array.benificary_case_status_leprosy, beneficiaryStatus.value) + form.beneficiaryStatusId = beneficiaryStatus.getPosition() + form.reasonForDeath = getEnglishValueInArray(R.array.reason_death, reasonOfDeath.value) + form.otherPlaceOfDeath = otherPlaceOfDeath.value + form.otherReasonForDeath = otherReasonOfDeath.value + form.dateOfDeath = getLongFromDate(dateOfDeath.value) + form.placeOfDeath = getEnglishValueInArray(R.array.death_place, placeOfDeath.value) + form.otherReferredTo = other.value + form.remarks = remarks.value + form.leprosyStatus = getEnglishValueInArray(R.array.leprosy_status, leprosyStatus.value) ?: "Screening" + form.typeOfLeprosy = getEnglishValueInArray(R.array.type_of_leprocy, typeOfLeprosy.value) + form.diseaseTypeID = 5 + form.leprosySymptoms = getEnglishValueInArray(R.array.yes_no, leprosySymptoms.value) + form.visitLabel = englishResources.getString(R.string.visit_number_label, form.currentVisitNumber) + form.syncState = SyncState.UNSYNCED + form.leprosySymptomsPosition = when (leprosySymptoms.value) { + resources.getStringArray(R.array.yes_no)[0] -> 0 + resources.getStringArray(R.array.yes_no)[1] -> 1 + else -> 1 + } + form.recurrentUlcerationId = when (recurrentUlceration.value) { + resources.getStringArray(R.array.yes_no)[0] -> 0 + resources.getStringArray(R.array.yes_no)[1] -> 1 + else -> 1 + } + form.recurrentTinglingId = when (cbacRecurrentTingling.value) { + resources.getStringArray(R.array.yes_no)[0] -> 0 + resources.getStringArray(R.array.yes_no)[1] -> 1 + else -> 1 + } + form.hypopigmentedPatchId = when (cbacAnyHyperPigmented.value) { + resources.getStringArray(R.array.yes_no)[0] -> 0 + resources.getStringArray(R.array.yes_no)[1] -> 1 + else -> 1 + } + form.thickenedSkinId = when (cbacAnyThickendSkin.value) { + resources.getStringArray(R.array.yes_no)[0] -> 0 + resources.getStringArray(R.array.yes_no)[1] -> 1 + else -> 1 + } + form.skinNodulesId = when (cbacAnyNodulesSkin.value) { + resources.getStringArray(R.array.yes_no)[0] -> 0 + resources.getStringArray(R.array.yes_no)[1] -> 1 + else -> 1 + } + form.recurrentNumbnessId = when (cbacRecurrentNumbness .value) { + resources.getStringArray(R.array.yes_no)[0] -> 0 + resources.getStringArray(R.array.yes_no)[1] -> 1 + else -> 1 + } + form.clawingFingersId = when (cbacClawingOfFingers.value) { + resources.getStringArray(R.array.yes_no)[0] -> 0 + resources.getStringArray(R.array.yes_no)[1] -> 1 + else -> 1 + } + form.tinglingNumbnessExtremitiesId = when (cbacTinglingOrNumbness .value) { + resources.getStringArray(R.array.yes_no)[0] -> 0 + resources.getStringArray(R.array.yes_no)[1] -> 1 + else -> 1 + } + form.inabilityCloseEyelidId = when (cbacInabilityCloseEyelid.value) { + resources.getStringArray(R.array.yes_no)[0] -> 0 + resources.getStringArray(R.array.yes_no)[1] -> 1 + else -> 1 + } + form.difficultyHoldingObjectsId = when (cbacDiffHoldingObjects.value) { + resources.getStringArray(R.array.yes_no)[0] -> 0 + resources.getStringArray(R.array.yes_no)[1] -> 1 + else -> 1 + } + form.weaknessFeetId = when (cbacWeeknessInFeet.value) { + resources.getStringArray(R.array.yes_no)[0] -> 0 + resources.getStringArray(R.array.yes_no)[1] -> 1 + else -> 1 + } + form.recurrentUlceration = getEnglishValueInArray(R.array.yes_no, recurrentUlceration.value) + form.recurrentTingling = getEnglishValueInArray(R.array.yes_no, cbacRecurrentTingling.value) + form.hypopigmentedPatch = getEnglishValueInArray(R.array.yes_no, cbacAnyHyperPigmented.value) + form.thickenedSkin = getEnglishValueInArray(R.array.yes_no, cbacAnyThickendSkin.value) + form.skinNodules = getEnglishValueInArray(R.array.yes_no, cbacAnyNodulesSkin.value) + form.recurrentNumbness = getEnglishValueInArray(R.array.yes_no, cbacRecurrentNumbness.value) + form.clawingFingers = getEnglishValueInArray(R.array.yes_no, cbacClawingOfFingers.value) + form.tinglingNumbnessExtremities = getEnglishValueInArray(R.array.yes_no, cbacTinglingOrNumbness.value) + form.inabilityCloseEyelid = getEnglishValueInArray(R.array.yes_no, cbacInabilityCloseEyelid.value) + form.difficultyHoldingObjects = getEnglishValueInArray(R.array.yes_no, cbacDiffHoldingObjects.value) + form.weaknessFeet = getEnglishValueInArray(R.array.yes_no, cbacWeeknessInFeet.value) + + + } + } + + + fun updateBen(benRegCache: BenRegCache) { + benRegCache.genDetails?.let { + it.reproductiveStatus = + englishResources.getStringArray(R.array.nbr_reproductive_status_array2)[1] + it.reproductiveStatusId = 2 + } + if (benRegCache.processed != "N") benRegCache.processed = "U" + } + +// + + fun getIndexOfDate(): Int { + return getIndexById(dateOfCase.id) + } + + private fun updateLeprosySymptomsFromChecklist() + { + val yesValue = resources.getStringArray(R.array.yes_no)[0] + + val anyYes = listOf( + recurrentUlceration.value, + cbacRecurrentTingling.value, + cbacAnyHyperPigmented.value, + cbacAnyThickendSkin.value, + cbacAnyNodulesSkin.value, + cbacRecurrentNumbness.value, + cbacClawingOfFingers.value, + cbacTinglingOrNumbness.value, + cbacInabilityCloseEyelid.value, + cbacDiffHoldingObjects.value, + cbacWeeknessInFeet.value + ).any { it == yesValue } + + leprosySymptoms.value = + if (anyYes) + resources.getStringArray(R.array.yes_no)[0] + + else resources.getStringArray(R.array.yes_no)[1] + + if (anyYes){ + leprosyStatus.value = resources.getStringArray(R.array.leprosy_status)[3] + visitLabel.value = resources.getString(R.string.visit_number_label, 1) + + triggerDependants( + source = leprosySymptoms, + addItems = listOf(visitLabel,leprosyStatus,referredTo), + removeItems = listOf() + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/LeprosySuspectedDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/LeprosySuspectedDataset.kt new file mode 100644 index 000000000..ef5e91ef1 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/LeprosySuspectedDataset.kt @@ -0,0 +1,278 @@ +package org.piramalswasthya.sakhi.configuration + +import android.content.Context +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.helpers.Languages +import org.piramalswasthya.sakhi.model.BenRegCache +import org.piramalswasthya.sakhi.model.FormElement +import org.piramalswasthya.sakhi.model.InputType +import org.piramalswasthya.sakhi.model.LeprosyScreeningCache + +class LeprosySuspectedDataset ( + context: Context, currentLanguage: Languages +) : Dataset(context, currentLanguage) { + + private var homeVisitDateLong: Long = System.currentTimeMillis() + private var treatmentStartDateLong: Long = 0L + private val dateOfCase = FormElement( + id = 1, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.home_visit_date), + arrayId = -1, + required = true, + max = System.currentTimeMillis(), + hasDependants = true, + isEnabled = false + + ) + + private val leprosyStatus = FormElement( + id = 8, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.str_leprosy_status), + arrayId = R.array.leprosy_status, + entries = resources.getStringArray(R.array.leprosy_status), + required = false, + hasDependants = true, + isEnabled = false + + ) + + private var referredTo = FormElement( + id = 9, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.refer_to), + arrayId = R.array.dc_refer, + entries = resources.getStringArray(R.array.dc_refer), + required = false, + hasDependants = true, + isEnabled = false + ) + private var other = FormElement( + id = 10, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.other), + required = true, + hasDependants = false + ) + /* private var typeOfLeprosy = FormElement( + id = 11, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.type_of_leprocy), + arrayId = R.array.type_of_leprocy, + entries = resources.getStringArray(R.array.type_of_leprocy), + required = false, + hasDependants = true + )*/ + + private val followUpdate = FormElement( + id = 12, + inputType = InputType.DATE_PICKER, + title = resources.getString(R.string.follow_up_date), + arrayId = -1, + required = false, + max = System.currentTimeMillis(), + min = System.currentTimeMillis() - (90L * 24 * 60 * 60 * 1000), + hasDependants = true + + ) + private val remarks = FormElement( + id = 13, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.pnc_remarks), + required = false, + isEnabled = false + ) + + private val leprosySymptoms = FormElement( + id = 14, + inputType = InputType.RADIO, + arrayId = R.array.yes_no, + title = resources.getString(R.string.any_leprosy_symptoms_present), + entries = resources.getStringArray(R.array.yes_no), + hasDependants = true, + required = true, + isEnabled = false + ) + + private val visitLabel = FormElement( + id = 15, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.visits), + required = true, + isEnabled = false + ) + + private val typeOfLeprosy = FormElement( + id = 16, + inputType = InputType.RADIO, + isEnabled = true, + required = true, + title = resources.getString(R.string.type_of_leprocy), + arrayId = R.array.type_of_leprocy, + entries = resources.getStringArray(R.array.type_of_leprocy), + hasDependants = true + ) + + private val treatmentStartDate = FormElement( + id = 17, + inputType = InputType.DATE_PICKER, + title = resources.getString(R.string.treatment_start_date), + isEnabled = true, + required = false, + max = System.currentTimeMillis() + + ) + + private val leprosyConfirmed = FormElement( + id = 18, + inputType = InputType.RADIO, + title = resources.getString(R.string.has_leprosy_been_confirmed), + required = true, + isEnabled = true, + arrayId = R.array.yes_no, + entries = resources.getStringArray(R.array.yes_no), + hasDependants = true) + + + suspend fun setUpPage(ben: BenRegCache?, saved: LeprosyScreeningCache?) { + val list = mutableListOf( + dateOfCase, + leprosySymptoms, + visitLabel, + leprosyStatus, + referredTo, + leprosyConfirmed, + //typeOfLeprosy, + + + + ) + homeVisitDateLong = saved?.homeVisitDate ?: System.currentTimeMillis() + if (saved == null) { + dateOfCase.value = getDateFromLong(System.currentTimeMillis()) + leprosyStatus.value = resources.getStringArray(R.array.leprosy_status)[0] + visitLabel.value = resources.getString(R.string.visit_number_label, 1) + leprosySymptoms.value = resources.getStringArray(R.array.yes_no)[1] + + updateDateConstraints() + + } else { + dateOfCase.value = getDateFromLong(saved.homeVisitDate) + val symptomsPosition = saved.leprosySymptomsPosition ?: 1 + leprosySymptoms.value = resources.getStringArray(R.array.yes_no).getOrNull(symptomsPosition) + ?: resources.getStringArray(R.array.yes_no)[1] + visitLabel.value = resources.getString(R.string.visit_number_label, saved?.currentVisitNumber ?: 1) + + leprosyStatus.value = + getLocalValueInArray(leprosyStatus.arrayId, saved.leprosyStatus) + + if (leprosyStatus.value == leprosyStatus.entries!!.last()) { + leprosyStatus.value = saved.leprosyStatus + list.add(list.indexOf(leprosyStatus) + 1, other) + } else { + leprosyStatus.value = + getLocalValueInArray(leprosyStatus.arrayId, saved.leprosyStatus) + } + remarks.value = saved.remarks + referredTo.value = + getLocalValueInArray(referredTo.arrayId, saved.referToName) + if (referredTo.value == referredTo.entries!!.last()) { + referredTo.value = saved.referToName + list.add(list.indexOf(referredTo) + 1, other) + } else { + referredTo.value = + getLocalValueInArray(referredTo.arrayId, saved.referToName) + } + + other.value = saved.otherReferredTo + + + updateDateConstraints() + + } + + + + setUpPage(list) + + } + override suspend fun handleListOnValueChanged(formId: Int, index: Int): Int { + when (formId) { + + leprosyConfirmed.id ->{ + if (leprosyConfirmed.value == resources.getStringArray(R.array.yes_no)[0]) { + triggerDependants(source = leprosyConfirmed, addItems =listOf(typeOfLeprosy),removeItems = listOf()) + } + else{ + triggerDependants(source = leprosyConfirmed, addItems = listOf(),removeItems =listOf(typeOfLeprosy)) + + } + + } + + typeOfLeprosy.id -> { + leprosyStatus.value = resources.getStringArray(R.array.leprosy_status)[4] + + treatmentStartDate.value = getDateFromLong(System.currentTimeMillis()) + + triggerDependants( + source = typeOfLeprosy, + addItems = listOf(treatmentStartDate), + removeItems = listOf(treatmentStartDate) + ) + + + } + + + } + return 0 + } + + override fun mapValues(cacheModel: FormDataModel, pageNumber: Int) { + (cacheModel as LeprosyScreeningCache).let { form -> + form.homeVisitDate = getLongFromDate(dateOfCase.value) + form.referToName = getEnglishValueInArray(R.array.dc_refer, referredTo.value) + form.referredTo = referredTo.getPosition() + form.otherReferredTo = other.value + form.remarks = remarks.value + form.typeOfLeprosy = getEnglishValueInArray(R.array.type_of_leprocy, typeOfLeprosy.value) + form.diseaseTypeID = 5 + form.leprosyStatus = getEnglishValueInArray(R.array.leprosy_status, leprosyStatus.value) ?: "Screening" + form.visitLabel = visitLabel.value + form.leprosySymptomsPosition = when (leprosySymptoms.value) { + resources.getStringArray(R.array.yes_no)[0] -> 0 + resources.getStringArray(R.array.yes_no)[1] -> 1 + else -> 1 + } + form.isConfirmed = !typeOfLeprosy.value.isNullOrEmpty() + form.treatmentStartDate = getLongFromDate(treatmentStartDate.value) + form.syncState = SyncState.UNSYNCED + + } + } + + + fun updateBen(benRegCache: BenRegCache) { + benRegCache.genDetails?.let { + it.reproductiveStatus = + englishResources.getStringArray(R.array.nbr_reproductive_status_array2)[1] + it.reproductiveStatusId = 2 + } + if (benRegCache.processed != "N") benRegCache.processed = "U" + } + +// + + fun getIndexOfDate(): Int { + return getIndexById(dateOfCase.id) + } + + private fun updateDateConstraints() { + treatmentStartDate.min = homeVisitDateLong + + + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/MDSRFormDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/MDSRFormDataset.kt index 36329a972..04abc75de 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/configuration/MDSRFormDataset.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/MDSRFormDataset.kt @@ -1,8 +1,11 @@ package org.piramalswasthya.sakhi.configuration import android.content.Context +import android.net.Uri import android.widget.LinearLayout +import org.piramalswasthya.sakhi.BuildConfig import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao import org.piramalswasthya.sakhi.helpers.Languages import org.piramalswasthya.sakhi.model.BenRegCache import org.piramalswasthya.sakhi.model.FormElement @@ -10,14 +13,23 @@ import org.piramalswasthya.sakhi.model.InputType import org.piramalswasthya.sakhi.model.MDSRCache class MDSRFormDataset( - context: Context, currentLanguage: Languages + context: Context, + currentLanguage: Languages, + val preferences: PreferenceDao, + val pregnancyDeath: Boolean = false, + val abortionDeath: Boolean = false, + val deliveryDeath: Boolean = false, + val pncDeath: Boolean = false, + val pncDeathCause: Boolean = false, + val ancDeathCause: Boolean = false + ) : Dataset(context, currentLanguage) { private val dateOfDeath = FormElement( id = 1, - inputType = InputType.DATE_PICKER, - title = "Date of death ", + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.date_of_death), min = 0L, max = System.currentTimeMillis(), required = true @@ -25,35 +37,36 @@ class MDSRFormDataset( private val address = FormElement( id = 2, inputType = InputType.TEXT_VIEW, - title = "Address", + title = resources.getString(R.string.address), required = false ) private val husbandName = FormElement( id = 3, inputType = InputType.TEXT_VIEW, etMaxLength = 50, - title = "Husband’s Name", + title = resources.getString(R.string.mdsr_husband_name), required = false ) private val causeOfDeath = FormElement( id = 4, inputType = InputType.RADIO, - title = "Cause of death", + title = resources.getString(R.string.mdsr_cause), required = true, - orientation = LinearLayout.VERTICAL, + isEnabled = false, hasDependants = true, - entries = arrayOf("Maternal", "Non-maternal") + arrayId = R.array.mdsr_cause_of_death_type, + entries = resources.getStringArray(R.array.mdsr_cause_of_death_type) ) private val reasonOfDeath = FormElement( id = 5, inputType = InputType.EDIT_TEXT, - title = "Specify Reason", + title = resources.getString(R.string.mdsr_reason_death), required = true ) private val investigationDate = FormElement( id = 6, inputType = InputType.DATE_PICKER, - title = "Date of field investigation", + title = resources.getString(R.string.mdsr_field_investigation), min = 0L, max = System.currentTimeMillis(), required = false @@ -61,15 +74,15 @@ class MDSRFormDataset( private val actionTaken = FormElement( id = 7, inputType = InputType.RADIO, - title = "Action Take", + title = resources.getString(R.string.mdsr_action), required = false, orientation = LinearLayout.VERTICAL, - entries = arrayOf("Yes", "No") + entries = resources.getStringArray(R.array.yes_no) ) private val blockMOSign = FormElement( id = 8, inputType = InputType.EDIT_TEXT, - title = "Signature of MO I/C of the block", + title = resources.getString(R.string.mdsr_signature), required = false ) private val dateIc = FormElement( @@ -77,10 +90,177 @@ class MDSRFormDataset( inputType = InputType.DATE_PICKER, min = 0L, max = System.currentTimeMillis(), - title = "Date", + title = resources.getString(R.string.mdsr_ic_date), + required = false + ) + private val duringPregnancy = FormElement( + id = 4, + inputType = InputType.RADIO, + title = resources.getString(R.string.a_during_pregnancy), + required = false, + isEnabled = false, + entries = resources.getStringArray(R.array.yes_no) + ) + + private val duringDelivery = FormElement( + id = 4, + inputType = InputType.RADIO, + title = resources.getString(R.string.b_during_delivery), + required = false, + isEnabled = false, + entries = resources.getStringArray(R.array.yes_no) + ) + private val duringDelivery42Days = FormElement( + id = 4, + inputType = InputType.RADIO, + title = resources.getString(R.string.c_within_42_days_after_delivery), + required = false, + isEnabled = false, + entries = resources.getStringArray(R.array.yes_no) + ) + private val duringAbortion6Weeks = FormElement( + id = 4, + inputType = InputType.RADIO, + title = resources.getString(R.string.d_during_abortion_or_within_6_weeks_after_abortion), + required = false, + isEnabled = false, + entries = resources.getStringArray(R.array.yes_no) + ) + + private val nameOfReportingPerson = FormElement( + id = 18, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.name_of_reporting_person), + required = false + ) + + private val designation = FormElement( + id = 18, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.designation), + required = false + ) + + private val veryficationByANM = FormElement( + id = 18, + inputType = InputType.HEADLINE, + title = resources.getString(R.string.verification_by_anm_of_the_respective_sub_center_that_death_of_women_occurred_during_pregnancy_or_within_42_days_of_delivery_abortion), + required = false + ) + private val nameOdANM = FormElement( + id = 18, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.name_of_anm), + required = false + ) + private val nameOfSubCenter = FormElement( + id = 18, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.name_of_the_sub_center), required = false ) + private val mdsrFileUpload1 = FormElement( + id = 21, + inputType = InputType.FILE_UPLOAD, + title = resources.getString(R.string.mdsr_form_from_anm_1), + required = false, + ) + private val mdsrFileUpload2 = FormElement( + id = 22, + inputType = InputType.FILE_UPLOAD, + title = resources.getString(R.string.mdsr_form_from_anm_2), + required = false, + ) + private val mdsrDeathFileUpload = FormElement( + id = 23, + inputType = InputType.FILE_UPLOAD, + title = resources.getString(R.string.death_certificate), + required = false, + ) + + //................changes BRD................................ + private val state = FormElement( + id = 24, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.state), + required = false + ) + private val district = FormElement( + id = 25, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.district), + required = false + ) + private val block = FormElement( + id = 26, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.block), + required = false + ) + private val village = FormElement( + id = 27, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.village_town_city), + required = false + ) + + private val fatherName = FormElement( + id = 27, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.father_name), + required = false + ) + private val ageOfWomen = FormElement( + id = 27, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.age_of_woman), + required = false + ) + + private val rchID = FormElement( + id = 27, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.rch_id), + required = false + ) + + + private val mobileNo = FormElement( + id = 27, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.mobile_number), + required = false + ) + + private val timeOfDeath = FormElement( + id = 16, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.time_of_death), + required = false + ) + private val placeOfDate = FormElement( + id = 27, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.place_of_death), + required = false + ) + + private val whenDidDeathOccur = FormElement( + id = 27, + inputType = InputType.HEADLINE, + title = resources.getString(R.string.when_did_death_occur), + required = false + ) + private val nameOfDeceased = FormElement( + id = 27, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.name_of_the_deceased_woman), + required = false + ) + + +//............................................................. suspend fun setUpPage( ben: BenRegCache, @@ -88,26 +268,90 @@ class MDSRFormDataset( saved: MDSRCache? ) { val list = mutableListOf( - dateOfDeath, - this.address, + state, + district, + block, + village, + nameOfDeceased, husbandName, + fatherName, + ageOfWomen, + rchID, + mobileNo, + dateOfDeath, + timeOfDeath, + placeOfDate, + whenDidDeathOccur, + duringPregnancy, + duringDelivery, + duringDelivery42Days, + duringAbortion6Weeks, causeOfDeath, - investigationDate, - actionTaken, - blockMOSign, - dateIc + nameOfReportingPerson, + designation, + veryficationByANM, + nameOdANM, + nameOfSubCenter, + dateIc, + mdsrFileUpload1, + mdsrFileUpload2, + mdsrDeathFileUpload, +// this.address, +// investigationDate, +// actionTaken, +// blockMOSign, + ) + + if (BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true)) { + list.remove(mdsrFileUpload1) + list.remove(mdsrFileUpload2) + list.remove(mdsrDeathFileUpload) + } + this.address.value = address husbandName.value = ben.genDetails?.spouseName + val user = preferences.getLoggedInUser() + nameOfReportingPerson.value = user?.userName ?: "" + designation.value="Asha" + state.value = user?.state?.name ?: "" + district.value = user?.district?.name ?: "" + block.value = user?.block?.name ?: "" +// nameOfSubCenter.value = user?.subs?.name ?: "" + village.value = user!!.villages[0].name + placeOfDate.value = ben.placeOfDeath + rchID.value = ben.rchId + ageOfWomen.value = ben.age.toString() + fatherName.value=ben.fatherName + dateOfDeath.value=ben.dateOfDeath + timeOfDeath.value=ben.timeOfDeath + nameOfDeceased.value = "${ben.firstName} ${ben.lastName}" + duringPregnancy.value = if (pregnancyDeath) duringPregnancy.entries!![0] else duringPregnancy.entries!![1] + duringDelivery.value = if (deliveryDeath) duringDelivery.entries!![0] else duringDelivery.entries!![1] + duringDelivery42Days.value = if (pncDeath) duringDelivery42Days.entries!![0] else duringDelivery42Days.entries!![1] + duringAbortion6Weeks.value = if (abortionDeath) duringAbortion6Weeks.entries!![0] else duringAbortion6Weeks.entries!![1] + + val causeOfDeathValue = if (pregnancyDeath || abortionDeath || deliveryDeath || pncDeath) { + if(pncDeathCause || ancDeathCause) + causeOfDeath.entries!![1] + else + causeOfDeath.entries!![0] + } else { + causeOfDeath.entries!![1] + } + causeOfDeath.value = causeOfDeathValue saved?.let { mdsr -> dateOfDeath.value = mdsr.dateOfDeath?.let { getDateFromLong(it) } this.address.value = mdsr.address husbandName.value = mdsr.husbandName - causeOfDeath.value = mdsr.causeOfDeath + causeOfDeath.value = getLocalValueInArray(R.array.mdsr_cause_of_death_type, mdsr.causeOfDeath) reasonOfDeath.value = mdsr.reasonOfDeath + mdsrFileUpload1.value=mdsr.mdsr1File + mdsrFileUpload2.value=mdsr.mdsr2File + mdsrDeathFileUpload.value=mdsr.mdsrDeathCertFile investigationDate.value = mdsr.investigationDate?.let { getDateFromLong(it) } actionTaken.value = mdsr.actionTaken?.let { - if (it) resources.getString(R.string.yes) else resources.getString(R.string.no) + if (it) actionTaken.entries!![0] else actionTaken.entries!![1] } blockMOSign.value = mdsr.blockMOSign dateIc.value = mdsr.dateIc?.let { getDateFromLong(it) } @@ -130,16 +374,48 @@ class MDSRFormDataset( override fun mapValues(cacheModel: FormDataModel, pageNumber: Int) { (cacheModel as MDSRCache).let { mdsrCache -> - mdsrCache.dateOfDeath = getLongFromDate(dateOfDeath.value!!) + mdsrCache.dateOfDeath = dateOfDeath.value?.let { getLongFromDate(it) } mdsrCache.address = address.value mdsrCache.husbandName = husbandName.value - mdsrCache.causeOfDeath = causeOfDeath.value + mdsrCache.mdsr1File = mdsrFileUpload1.value + mdsrCache.mdsr2File = mdsrFileUpload2.value + mdsrCache.mdsrDeathCertFile = mdsrDeathFileUpload.value + mdsrCache.causeOfDeath = getEnglishValueInArray(R.array.mdsr_cause_of_death_type, causeOfDeath.value) mdsrCache.reasonOfDeath = reasonOfDeath.value - mdsrCache.actionTaken = actionTaken.value?.let { actionTaken.value == "Yes" } + mdsrCache.actionTaken = actionTaken.value?.let { actionTaken.value == actionTaken.entries!![0] } mdsrCache.investigationDate = investigationDate.value?.let { getLongFromDate(it) } mdsrCache.blockMOSign = blockMOSign.value mdsrCache.dateIc = dateIc.value?.let { getLongFromDate(it) } mdsrCache.createdDate = System.currentTimeMillis() } } + + + + fun getIndexOfMDSR1() = getIndexById(mdsrFileUpload1.id) + fun getIndexOfMDSR2() = getIndexById(mdsrFileUpload2.id) + fun getIndexOfIsDeathCertificate() = getIndexById(mdsrDeathFileUpload.id) + + + fun setImageUriToFormElement(lastImageFormId: Int, dpUri: Uri) { + + when (lastImageFormId) { + 21 -> { + mdsrFileUpload1.value = dpUri.toString() + mdsrFileUpload1.errorText = null + } + + 22 -> { + mdsrFileUpload2.value = dpUri.toString() + mdsrFileUpload2.errorText = null + } + + 23 -> { + mdsrDeathFileUpload.value = dpUri.toString() + mdsrDeathFileUpload.errorText = null + } + + } + } + } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/MaaMeetingDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/MaaMeetingDataset.kt new file mode 100644 index 000000000..305c4338d --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/MaaMeetingDataset.kt @@ -0,0 +1,205 @@ +package org.piramalswasthya.sakhi.configuration + +import android.content.Context +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.BuildConfig +import org.piramalswasthya.sakhi.helpers.Languages +import org.piramalswasthya.sakhi.model.FormElement +import org.piramalswasthya.sakhi.model.InputType +import org.piramalswasthya.sakhi.model.InputType.EDIT_TEXT +import java.util.Calendar + +class MaaMeetingDataset( + context: Context, + currentLanguage: Languages +) : Dataset(context, currentLanguage) { + + val meetingDate = FormElement( + id = 1, + inputType = InputType.DATE_PICKER, + title = resources.getString(R.string.maa_meeting_date), + required = true, + max = System.currentTimeMillis(), + ).apply { + val cal = Calendar.getInstance() + cal.add(Calendar.MONTH, -2) + min = cal.timeInMillis + } + + + val meetingPlace = FormElement( + id = 2, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.place_of_maa_meeting), + required = true, + entries = resources.getStringArray(R.array.maa_meeting_place_array) + ) + + val villageName = FormElement( + id = 9, + inputType = EDIT_TEXT, + title = resources.getString(R.string.village_name), + arrayId = -1, + required = false, + etInputType = android.text.InputType.TYPE_CLASS_TEXT or android.text.InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS + ) + val noOfPW = FormElement( + id = 17, + inputType = EDIT_TEXT, + title = resources.getString(R.string.number_of_pregnant_woman_attended), + arrayId = -1, + value = "0", + required = false, + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + isMobileNumber = true, + etMaxLength = 3, + max = 999, + min = 1 + ) + + val noOfLM = FormElement( + id = 16, + inputType = EDIT_TEXT, + title = resources.getString(R.string.number_of_lactating_mothers_attended), + arrayId = -1, + value = "0", + required = false, + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + isMobileNumber = true, + etMaxLength = 3, + max = 999, + min = 1 + ) + + val duringBreastfeeding = FormElement( + id = 15, + inputType = InputType.CHECKBOXES, + title = resources.getString(R.string.during_breastfeeding_counseling_check_list), + arrayId = -1, + entries = resources.getStringArray(R.array.maa_breastfeeding), + required = false, + ) + + + val participants = FormElement( + id = 3, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.total_no_b_attended), + required = true, + etInputType = android.text.InputType.TYPE_CLASS_NUMBER, + etMaxLength = 3, + min = 0, + max = 999 + ) + + val upload1 = FormElement( + id = 10, + inputType = InputType.FILE_UPLOAD, + title = resources.getString(R.string.meeting_photos_mom, 1), + required = false + ) + val upload2 = upload1.copy(id = 11, title = resources.getString(R.string.meeting_photos_mom, 2)) + val upload3 = upload1.copy(id = 12, title = resources.getString(R.string.meeting_photos_mom, 3)) + val upload4 = upload1.copy(id = 13, title = resources.getString(R.string.meeting_photos_mom, 4)) + val upload5 = upload1.copy(id = 14, title = resources.getString(R.string.meeting_photos_mom, 5)) + + suspend fun setUpPage(recordExists: Boolean) { + val list = mutableListOf( + meetingDate, + meetingPlace, + ) + + if (BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true)) { + list.add(villageName) + list.add(participants) + list.add(noOfPW) + list.add(noOfLM) + list.add(duringBreastfeeding) + } + else{ + list.add(participants) + } + + val uploadList = listOf(upload1, upload2, upload3, upload4, upload5) + + if (recordExists) { + val filledUploads = uploadList.filter { !it.value.isNullOrEmpty() } + if (filledUploads.isEmpty()) { + list.addAll(uploadList) + } else { + list.addAll(filledUploads) + } + } else { + list.addAll(uploadList) + } + + setUpPage(list) + } + + + override suspend fun handleListOnValueChanged(formId: Int, index: Int): Int { + return when (formId) { + duringBreastfeeding.id -> { + + val allItems = resources.getStringArray(R.array.maa_breastfeeding) + val totalCount = allItems.size + + val selectedIndexes = duringBreastfeeding.value + ?.split("|") + ?.mapNotNull { it.toIntOrNull() } + ?.toMutableSet() + ?: mutableSetOf() + + if (index == 0) { + + if (selectedIndexes.contains(0)) { + selectedIndexes.clear() + selectedIndexes.addAll(0 until totalCount) + } else { + selectedIndexes.clear() + } + + duringBreastfeeding.value = + if (selectedIndexes.isEmpty()) null + else selectedIndexes.sorted().joinToString("|") + + return -1 + } + val normalIndexes = (1 until totalCount) + + if (selectedIndexes.contains(0) && + normalIndexes.any { !selectedIndexes.contains(it) } + ) { + selectedIndexes.remove(0) + } + + if (normalIndexes.all { selectedIndexes.contains(it) }) { + selectedIndexes.add(0) + } + + duringBreastfeeding.value = + if (selectedIndexes.isEmpty()) null + else selectedIndexes.sorted().joinToString("|") + + -1 + } + + + + + participants.id -> { + validateAllDigitOnEditText(participants) + validateIntMinMax(participants) + } + else -> -1 + } + } + + override fun mapValues(cacheModel: FormDataModel, pageNumber: Int) { + + } + + +} + + diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/MalariaConfirmCasesDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/MalariaConfirmCasesDataset.kt new file mode 100644 index 000000000..7efe72801 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/MalariaConfirmCasesDataset.kt @@ -0,0 +1,226 @@ +package org.piramalswasthya.sakhi.configuration + +import android.content.Context +import android.util.Log +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.helpers.Languages +import org.piramalswasthya.sakhi.model.BenRegCache +import org.piramalswasthya.sakhi.model.FormElement +import org.piramalswasthya.sakhi.model.InputType +import org.piramalswasthya.sakhi.model.MalariaConfirmedCasesCache +import org.piramalswasthya.sakhi.model.MalariaScreeningCache + +class MalariaConfirmCasesDataset( + context: Context, currentLanguage: Languages +) : Dataset(context, currentLanguage) { + + + private val dateOfCase = FormElement( + id = 1, + inputType = InputType.DATE_PICKER, + title = resources.getString(R.string.start_t_date), + arrayId = -1, + required = true, + min = System.currentTimeMillis() - (90L * 24 * 60 * 60 * 1000), + max = System.currentTimeMillis(), + hasDependants = true + + ) + + + private var treatmentGiven = FormElement( + id = 7, + inputType = InputType.RADIO, + title = resources.getString(R.string.treatment_given), + arrayId = R.array.pf_pv, + entries = resources.getStringArray(R.array.pf_pv), + required = false, + isEnabled = false, + hasDependants = true + ) + + + private var dayWiseTrackingPf = FormElement( + id = 22, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.day_wise_tracking_pf), + arrayId = R.array.daysPf, + entries = resources.getStringArray(R.array.daysPf), + required = false, + hasDependants = true + ) + + private var dayWiseTrackingPV = FormElement( + id = 23, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.day_wise_tracking_pv), + arrayId = R.array.daysPv, + entries = resources.getStringArray(R.array.daysPv), + required = false, + hasDependants = true + ) + + private val dateOfCompletion = FormElement( + id = 2, + inputType = InputType.DATE_PICKER, + title = resources.getString(R.string.date_of_completion), + arrayId = -1, + required = true, + min = System.currentTimeMillis(), + max = Long.MAX_VALUE, + hasDependants = true + + ) + + private val referalDate = FormElement( + id = 3, + inputType = InputType.DATE_PICKER, + title = resources.getString(R.string.date_of_referal), + arrayId = -1, + required = false, + min = System.currentTimeMillis() , + max = System.currentTimeMillis() + (3L * 30 * 24 * 60 * 60 * 1000), + hasDependants = true + + ) + + + suspend fun setUpPage(ben: BenRegCache?, slideTestName:String, saved: MalariaConfirmedCasesCache?) { + val list = mutableListOf( + dateOfCase, + treatmentGiven, + dateOfCompletion, + referalDate + ) + + if (saved == null) { + dateOfCase.value = getDateFromLong(System.currentTimeMillis()) + dayWiseTrackingPf.value = resources.getStringArray(R.array.daysPf)[0] + dayWiseTrackingPV.value = resources.getStringArray(R.array.daysPv)[0] + + treatmentGiven.value = getLocalValueInArray(R.array.pf_pv, slideTestName) + if(treatmentGiven.value == resources.getStringArray(R.array.pf_pv)[0]){ + list.add(list.indexOf(treatmentGiven) + 1 ,dayWiseTrackingPf) + } else { + list.add(list.indexOf(treatmentGiven) + 1 ,dayWiseTrackingPV) + + } + updateCompletionDateLimits(setDefault = true) + } else { + dateOfCase.value = getDateFromLong(saved.dateOfDiagnosis) + dateOfCompletion.value = getDateFromLong(saved.treatmentCompletionDate) + referalDate.value = getDateFromLong(saved.referralDate) + treatmentGiven.value = getLocalValueInArray(R.array.pf_pv, saved.treatmentGiven) + + if(treatmentGiven.value == resources.getStringArray(R.array.pf_pv)[0]){ + list.add(list.indexOf(treatmentGiven) + 1 ,dayWiseTrackingPf) + dayWiseTrackingPf.value = getLocalValueInArray(R.array.daysPf,saved.day) + } else { + list.add(list.indexOf(treatmentGiven) + 1 ,dayWiseTrackingPV) + dayWiseTrackingPV.value = getLocalValueInArray(R.array.daysPv,saved.day) + + } + updateCompletionDateLimits() + + } + + setUpPage(list) + + } + + override suspend fun handleListOnValueChanged(formId: Int, index: Int): Int { + + return when (formId) { + + dateOfCase.id -> { + updateCompletionDateLimits(setDefault = true) + 0 + } + + + treatmentGiven.id -> { + updateCompletionDateLimits(setDefault = true) + if(treatmentGiven.value == resources.getStringArray(R.array.pf_pv)[0]){ + triggerDependants( + source = treatmentGiven, + addItems = listOf(dayWiseTrackingPf), + removeItems = listOf(dayWiseTrackingPV) + ) + + } else if(treatmentGiven.value == resources.getStringArray(R.array.pf_pv)[1]){ + triggerDependants( + source = treatmentGiven, + addItems = listOf(dayWiseTrackingPV), + removeItems = listOf(dayWiseTrackingPf) + ) + + } + 0 + } + + + else -> { + 0 + } + } + + + } + + override fun mapValues(cacheModel: FormDataModel, pageNumber: Int) { + (cacheModel as MalariaConfirmedCasesCache).let { form -> + form.dateOfDiagnosis = getLongFromDate(dateOfCase.value) + form.treatmentStartDate = getLongFromDate(dateOfCase.value) + form.treatmentGiven = getEnglishValueInArray(R.array.pf_pv, treatmentGiven.value) + if(treatmentGiven.value == resources.getStringArray(R.array.pf_pv)[0]){ + form.day = getEnglishValueInArray(R.array.daysPv, dayWiseTrackingPf.value) + + } else { + form.day = getEnglishValueInArray(R.array.daysPv, dayWiseTrackingPV.value) + + } + form.treatmentCompletionDate= getLongFromDate(dateOfCompletion.value) + form.referralDate= getLongFromDate(referalDate.value) + + } + } + + + fun updateBen(benRegCache: BenRegCache) { + benRegCache.genDetails?.let { + it.reproductiveStatus = + englishResources.getStringArray(R.array.nbr_reproductive_status_array2)[1] + it.reproductiveStatusId = 2 + } + if (benRegCache.processed != "N") benRegCache.processed = "U" + } + + + fun getIndexOfDate(): Int { + return getIndexById(dateOfCase.id) + } + + + private fun updateCompletionDateLimits(setDefault: Boolean = false) { + val startDate = getLongFromDate(dateOfCase.value) ?: return + val treatment = treatmentGiven.value ?: return + + + + val days = when (treatment) { + resources.getStringArray(R.array.pf_pv)[0] -> 3 + resources.getStringArray(R.array.pf_pv)[1] -> 4 + else -> 0 + } + + if (days > 0) { + val maxDate = startDate + (days * 24 * 60 * 60 * 1000L) + dateOfCompletion.min = startDate + dateOfCompletion.max = maxDate + + if (setDefault) { + dateOfCompletion.value = getDateFromLong(maxDate) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/MalariaFormDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/MalariaFormDataset.kt new file mode 100644 index 000000000..8b8acb247 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/MalariaFormDataset.kt @@ -0,0 +1,961 @@ +package org.piramalswasthya.sakhi.configuration + +import android.content.Context +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.helpers.Languages +import org.piramalswasthya.sakhi.model.BenRegCache +import org.piramalswasthya.sakhi.model.FormElement +import org.piramalswasthya.sakhi.model.InputType +import org.piramalswasthya.sakhi.model.MalariaScreeningCache +import org.piramalswasthya.sakhi.model.TBScreeningCache +import timber.log.Timber +import java.util.Calendar + +class MalariaFormDataset( + context: Context, currentLanguage: Languages +) : Dataset(context, currentLanguage) { + + + + private val dateOfCase = FormElement( + id = 1, + inputType = InputType.DATE_PICKER, + title = resources.getString(R.string.case_date), + arrayId = -1, + required = true, + min = System.currentTimeMillis() - (90L * 24 * 60 * 60 * 1000), + max = System.currentTimeMillis(), + hasDependants = true + + ) + private val beneficiaryStatus = FormElement( + id = 2, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.beneficiary_status), + arrayId = R.array.benificary_case_status, + entries = resources.getStringArray(R.array.benificary_case_status), + required = true, + hasDependants = true + + ) + private val dateOfDeath = FormElement( + id = 3, + inputType = InputType.DATE_PICKER, + title = resources.getString(R.string.death_date), + arrayId = -1, + required = true, + min = System.currentTimeMillis() - (90L * 24 * 60 * 60 * 1000), + max = System.currentTimeMillis(), + hasDependants = true + + ) + + private val placeOfDeath = FormElement( + id = 4, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.place_of_Death), + arrayId = R.array.death_place, + entries = resources.getStringArray(R.array.death_place), + required = true, + hasDependants = true + + ) + + private var otherPlaceOfDeath = FormElement( + id = 5, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.other_place), + required = true, + hasDependants = false + ) + + private val reasonOfDeath = FormElement( + id = 6, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.reason_of_Death), + arrayId = R.array.reason_death, + entries = resources.getStringArray(R.array.reason_death), + required = true, + hasDependants = true + + ) + private var otherReasonOfDeath = FormElement( + id = 28, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.other_reason), + required = true, + hasDependants = false + ) + + private var headline = FormElement( + id = 27, + inputType = InputType.HEADLINE, + title = resources.getString(R.string.sympt), + required = false, + headingLine = false, + hasDependants = false + ) + + private var isFever = FormElement( + id = 7, + inputType = InputType.RADIO, + title = resources.getString(R.string.cbac_feverwks), + entries = resources.getStringArray(R.array.yes_no), + required = false, + hasDependants = true + ).apply { + value = resources.getStringArray(R.array.yes_no)[1] + } + + private var isFluLikeIllness = FormElement( + id = 8, + inputType = InputType.RADIO, + title = resources.getString(R.string.flu_like_illness), + entries = resources.getStringArray(R.array.yes_no), + required = false, + hasDependants = true + ).apply { + value = resources.getStringArray(R.array.yes_no)[1] + } + + private var isShakingchills = FormElement( + id = 9, + inputType = InputType.RADIO, + title = resources.getString(R.string.shakingchills), + entries = resources.getStringArray(R.array.yes_no), + required = false, + hasDependants = true + ).apply { + value = resources.getStringArray(R.array.yes_no)[1] + } + + private var isHeadache = FormElement( + id = 10, + inputType = InputType.RADIO, + title = resources.getString(R.string.headache), + entries = resources.getStringArray(R.array.yes_no), + required = false, + hasDependants = true + ).apply { + value = resources.getStringArray(R.array.yes_no)[1] + } + + private var isMuscleaches = FormElement( + id = 11, + inputType = InputType.RADIO, + title = resources.getString(R.string.muscleaches), + entries = resources.getStringArray(R.array.yes_no), + required = false, + hasDependants = true + ).apply { + value = resources.getStringArray(R.array.yes_no)[1] + } + + private var isTiredness = FormElement( + id = 12, + inputType = InputType.RADIO, + title = resources.getString(R.string.tiredness), + entries = resources.getStringArray(R.array.yes_no), + required = false, + hasDependants = true + ).apply { + value = resources.getStringArray(R.array.yes_no)[1] + } + private var isNausea = FormElement( + id = 13, + inputType = InputType.RADIO, + title = resources.getString(R.string.nausea), + entries = resources.getStringArray(R.array.yes_no), + required = false, + hasDependants = true + ).apply { + value = resources.getStringArray(R.array.yes_no)[1] + } + private var isVomiting = FormElement( + id = 14, + inputType = InputType.RADIO, + title = resources.getString(R.string.vomiting), + entries = resources.getStringArray(R.array.yes_no), + required = false, + hasDependants = true + ).apply { + value = resources.getStringArray(R.array.yes_no)[1] + } + + private var isDiarrhea = FormElement( + id = 15, + inputType = InputType.RADIO, + title = resources.getString(R.string.diarrhea), + entries = resources.getStringArray(R.array.yes_no), + required = false, + hasDependants = true + ).apply { + value = resources.getStringArray(R.array.yes_no)[1] + } + + private val caseStatus = FormElement( + id = 16, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.case_status), + arrayId = -1, + entries = resources.getStringArray(R.array.dc_case_status), + required = false, + isEnabled = false, + hasDependants = true + + ) + + private val testType = FormElement( + id = 28, + inputType = InputType.RADIO, + title = resources.getString(R.string.m_test_type), + arrayId = -1, + entries = resources.getStringArray(R.array.test_type), + required = false, + isEnabled = true, + hasDependants = true + + ) + private var rapidDiagnostic = FormElement( + id = 17, + inputType = InputType.RADIO, + title = resources.getString(R.string.rapid_diagnostic), + arrayId = R.array.positive_negative, + entries = resources.getStringArray(R.array.positive_negative), + required = false, + hasDependants = true + ) + + private val dateOfTest = FormElement( + id = 18, + inputType = InputType.DATE_PICKER, + title = resources.getString(R.string.test_up_date), + arrayId = -1, + required = true, + min = System.currentTimeMillis() - (90L * 24 * 60 * 60 * 1000), + max = System.currentTimeMillis(), + hasDependants = true + + ) + + private var slideTestOptions = FormElement( + id = 27, + inputType = InputType.RADIO, + title = resources.getString(R.string.slide_test), + arrayId = R.array.pf_pv, + entries = resources.getStringArray(R.array.pf_pv), + required = false, + hasDependants = true + ) + + private var slideTestPf = FormElement( + id = 19, + inputType = InputType.RADIO, + title = resources.getString(R.string.slide_test_pf), + arrayId = R.array.positive_negative, + entries = resources.getStringArray(R.array.positive_negative), + required = false, + hasDependants = true + ) + + private var slideTestPv = FormElement( + id = 20, + inputType = InputType.RADIO, + title = resources.getString(R.string.slide_test_pv), + arrayId = R.array.positive_negative, + entries = resources.getStringArray(R.array.positive_negative), + required = false, + hasDependants = true + ) + + private val dateOfSlidetest = FormElement( + id = 21, + inputType = InputType.DATE_PICKER, + title = resources.getString(R.string.test_up_date), + arrayId = -1, + required = true, + min = System.currentTimeMillis() - (90L * 24 * 60 * 60 * 1000), + max = System.currentTimeMillis(), + hasDependants = true + + ) + + private var referredTo = FormElement( + id = 22, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.refer_to), + arrayId = R.array.dc_refer_malaria, + entries = resources.getStringArray(R.array.dc_refer_malaria), + required = false, + hasDependants = true + ) + private var other = FormElement( + id = 23, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.other), + required = true, + hasDependants = false + ) + private val followUpdate = FormElement( + id = 24, + inputType = InputType.DATE_PICKER, + title = resources.getString(R.string.follow_up_date), + arrayId = -1, + required = true, + max = System.currentTimeMillis(), + min = System.currentTimeMillis() - (90L * 24 * 60 * 60 * 1000), + hasDependants = true + + ) + + + private val remarks = FormElement( + id = 25, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.pnc_remarks), + required = false, + + ) + private val dateOfVisitBySupervisor = FormElement( + id = 26, + inputType = InputType.DATE_PICKER, + title = resources.getString(R.string.supervisor_visit_date), + arrayId = -1, + required = false, + max = System.currentTimeMillis(), + min = System.currentTimeMillis() - (90L * 24 * 60 * 60 * 1000), + hasDependants = true + + ) + + var visitId = 0L + + suspend fun setUpPage(ben: BenRegCache?, saved: MalariaScreeningCache?) { + val list = mutableListOf( + dateOfCase, + beneficiaryStatus, + headline, + isFever, + isFluLikeIllness, + isShakingchills, + isHeadache, + isMuscleaches, + isTiredness, + isNausea, + isVomiting, + isDiarrhea, + caseStatus + ) + if (saved == null) { + dateOfCase.value = getDateFromLong(System.currentTimeMillis()) + beneficiaryStatus.value = resources.getStringArray(R.array.benificary_case_status)[0] + visitId = 0 + caseStatus.value = resources.getStringArray(R.array.dc_case_status)[0] + } else { + dateOfCase.value = getDateFromLong(saved.caseDate) + followUpdate.value = getDateFromLong(saved.followUpDate) + visitId = saved.visitId + val entries = resources.getStringArray(R.array.test_type) + val index = saved.malariaTestType ?: 0 + if (index != 0) { + testType.value = entries[index-1] + } + remarks.value = saved.remarks + if (saved.caseStatus == "Suspected") { + caseStatus.entries = resources.getStringArray(R.array.dc_case_status) + .filter { it == "Confirmed" || it == "Not Confirmed" } + .toTypedArray() + } else { + caseStatus.entries = resources.getStringArray(R.array.dc_case_status) + } + saved.caseStatus + isFever.value = + if (saved.feverMoreThanTwoWeeks == true) resources.getStringArray(R.array.yes_no)[0] else resources.getStringArray( + R.array.yes_no + )[1] + isFluLikeIllness.value = + if (saved.fluLikeIllness == true) resources.getStringArray(R.array.yes_no)[0] else resources.getStringArray( + R.array.yes_no + )[1] + isShakingchills.value = + if (saved.shakingChills == true) resources.getStringArray(R.array.yes_no)[0] else resources.getStringArray( + R.array.yes_no + )[1] + isHeadache.value = + if (saved.headache == true) resources.getStringArray(R.array.yes_no)[0] else resources.getStringArray( + R.array.yes_no + )[1] + isMuscleaches.value = + if (saved.muscleAches == true) resources.getStringArray(R.array.yes_no)[0] else resources.getStringArray( + R.array.yes_no + )[1] + isTiredness.value = + if (saved.tiredness == true) resources.getStringArray(R.array.yes_no)[0] else resources.getStringArray( + R.array.yes_no + )[1] + isNausea.value = + if (saved.nausea == true) resources.getStringArray(R.array.yes_no)[0] else resources.getStringArray( + R.array.yes_no + )[1] + isVomiting.value = + if (saved.vomiting == true) resources.getStringArray(R.array.yes_no)[0] else resources.getStringArray( + R.array.yes_no + )[1] + isDiarrhea.value = + if (saved.diarrhea == true) resources.getStringArray(R.array.yes_no)[0] else resources.getStringArray( + R.array.yes_no + )[1] + if (saved.caseStatus != null) { + caseStatus.value = + getLocalValueInArray(R.array.dc_case_status, saved.caseStatus) + } + + rapidDiagnostic.value = getLocalValueInArray(R.array.positive_negative, saved.rapidDiagnosticTest) + ?: resources.getStringArray(R.array.positive_negative)[2] + + slideTestPv.value = getLocalValueInArray(R.array.positive_negative, saved.slideTestPv) + ?: resources.getStringArray(R.array.positive_negative)[2] + + slideTestPf.value = getLocalValueInArray(R.array.positive_negative, saved.slideTestPf) + ?: resources.getStringArray(R.array.positive_negative)[2] + + + if (saved.malariaSlideTestType != 0) { + slideTestOptions.value = resources.getStringArray(R.array.pf_pv)[saved.malariaSlideTestType!!-1] + + } + beneficiaryStatus.value = + getLocalValueInArray(beneficiaryStatus.arrayId, saved.beneficiaryStatus) + + + if (beneficiaryStatus.value == beneficiaryStatus.entries!!.last()) { + list.add(list.indexOf(beneficiaryStatus) + 1, dateOfDeath) + list.add(list.indexOf(beneficiaryStatus) + 2, placeOfDeath) + list.add(list.indexOf(beneficiaryStatus) + 3, reasonOfDeath) + list.remove(isFever) + list.remove(isDiarrhea) + list.remove(isFluLikeIllness) + list.remove(isShakingchills) + list.remove(isHeadache) + list.remove(isMuscleaches) + list.remove(isTiredness) + list.remove(isNausea) + list.remove(isVomiting) + list.remove(headline) + list.remove(caseStatus) + + + dateOfDeath.value = + getDateFromLong(saved.dateOfDeath) + placeOfDeath.value = + getLocalValueInArray(placeOfDeath.arrayId, saved.placeOfDeath) + if (placeOfDeath.value == placeOfDeath.entries!!.last()) { + list.add(list.indexOf(placeOfDeath) + 1, otherPlaceOfDeath) + otherPlaceOfDeath.value = saved.otherPlaceOfDeath + } + + reasonOfDeath.value = + getLocalValueInArray(reasonOfDeath.arrayId, saved.reasonForDeath) + if (reasonOfDeath.value == reasonOfDeath.entries!!.last()) { + list.add(list.indexOf(reasonOfDeath) + 1, otherReasonOfDeath) + otherReasonOfDeath.value = saved.otherReasonForDeath + } + } else { + /* list.add(list.indexOf(beneficiaryStatus) + 1, headline) + list.add(list.indexOf(beneficiaryStatus) + 2, isFever) + list.add(list.indexOf(beneficiaryStatus) + 3, isFluLikeIllness) + list.add(list.indexOf(beneficiaryStatus) + 4, isShakingchills) + list.add(list.indexOf(beneficiaryStatus) + 5, isHeadache) + list.add(list.indexOf(beneficiaryStatus) + 6, isMuscleaches) + list.add(list.indexOf(beneficiaryStatus) + 7, isTiredness) + list.add(list.indexOf(beneficiaryStatus) + 8, isNausea) + list.add(list.indexOf(beneficiaryStatus) + 9, isVomiting) + list.add(list.indexOf(beneficiaryStatus) + 10, isDiarrhea) + list.add(list.indexOf(beneficiaryStatus) + 11, caseStatus)*/ + /* list.add(list.indexOf(beneficiaryStatus) + 12, rapidDiagnostic) + list.add(list.indexOf(beneficiaryStatus) + 13, referredTo) + list.add(list.indexOf(beneficiaryStatus) + 14, remarks) + list.add(list.indexOf(beneficiaryStatus) + 15, dateOfVisitBySupervisor)*/ + + + if (caseStatus.value == resources.getStringArray(R.array.dc_case_status)[0] || caseStatus.value == resources.getStringArray(R.array.dc_case_status)[1] || caseStatus.value == resources.getStringArray(R.array.dc_case_status)[2]) { + list.add(list.indexOf(caseStatus) + 1, testType) + if (testType.value == resources.getStringArray(R.array.test_type)[0]) { + list.add(list.indexOf(testType) + 1, rapidDiagnostic) + if (rapidDiagnostic.value != resources.getStringArray(R.array.positive_negative)[2]) { + list.add(list.indexOf(rapidDiagnostic) + 1, dateOfTest) + dateOfTest.value = getDateFromLong(saved.dateOfRdt) + + } + + + } else if (testType.value == resources.getStringArray(R.array.test_type)[1]) { + list.add(list.indexOf(testType) + 1, slideTestOptions) + if (slideTestOptions.value == resources.getStringArray(R.array.pf_pv)[0]) { + list.add(list.indexOf(slideTestOptions) + 1, slideTestPf) + } else { + list.add(list.indexOf(slideTestOptions) + 1, slideTestPv) + } + if (slideTestPv.value != resources.getStringArray(R.array.positive_negative)[2]) { + list.add(list.indexOf(slideTestPv) + 1, dateOfSlidetest) + dateOfSlidetest.value = getDateFromLong(saved.dateOfSlideTest) + } + + if (slideTestPf.value != resources.getStringArray(R.array.positive_negative)[2]) { + list.add(list.indexOf(slideTestPf) + 1, dateOfSlidetest) + dateOfSlidetest.value = getDateFromLong(saved.dateOfSlideTest) + } + + } else { + list.add(list.indexOf(testType) + 1, rapidDiagnostic) + if (rapidDiagnostic.value != resources.getStringArray(R.array.positive_negative)[2]) { + list.add(list.indexOf(rapidDiagnostic) + 1, dateOfTest) + dateOfTest.value = getDateFromLong(saved.dateOfRdt) + list.add(list.indexOf(rapidDiagnostic) + 2, slideTestOptions) + + } + + if (slideTestOptions.value == resources.getStringArray(R.array.pf_pv)[0]) { + list.add(list.indexOf(slideTestOptions) + 1, slideTestPf) + } else { + list.add(list.indexOf(slideTestOptions) + 1, slideTestPv) + } + if (slideTestPv.value != resources.getStringArray(R.array.positive_negative)[2]) { + list.add(list.indexOf(slideTestPv) + 1, dateOfSlidetest) + dateOfSlidetest.value = getDateFromLong(saved.dateOfSlideTest) + } + + if (slideTestPf.value != resources.getStringArray(R.array.positive_negative)[2]) { + list.add(list.indexOf(slideTestPf) + 1, dateOfSlidetest) + dateOfSlidetest.value = getDateFromLong(saved.dateOfSlideTest) + } + } + + } else if (caseStatus.value == resources.getStringArray(R.array.dc_case_status)[1]) { + list.add(list.indexOf(caseStatus) + 1, testType) + } + + + + + + + + } + referredTo.value = + getLocalValueInArray(referredTo.arrayId, saved.referToName) + if (referredTo.value == referredTo.entries!![referredTo.entries!!.size - 2]) { + referredTo.value = saved.referToName + list.add(list.indexOf(referredTo) + 1, other) + } else { + referredTo.value = + getLocalValueInArray(referredTo.arrayId, saved.referToName) + } + + other.value = saved.otherReferredFacility + followUpdate.value = getDateFromLong(saved.followUpDate) + + } + + setUpPage(list) + + } + + override suspend fun handleListOnValueChanged(formId: Int, index: Int): Int { + + return when (formId) { + + isFever.id, + isFluLikeIllness.id, + isShakingchills.id, + isHeadache.id, + isMuscleaches.id, + isTiredness.id, + isNausea.id, + isVomiting.id, + isDiarrhea.id -> { + + val yesValue = resources.getStringArray(R.array.yes_no)[0] + val suspectedValue = resources.getStringArray(R.array.dc_case_status)[0] + val notConfirmedValue = resources.getStringArray(R.array.dc_case_status)[2] + + val symptoms = listOf( + isFluLikeIllness.value, + isShakingchills.value, + isHeadache.value, + isMuscleaches.value, + isTiredness.value, + isNausea.value, + isVomiting.value, + isDiarrhea.value + ) + + val nonFeverYesCount = symptoms.count { it == yesValue } + + caseStatus.value = when { + isFever.value == yesValue -> suspectedValue + nonFeverYesCount >= 2 -> suspectedValue + else -> notConfirmedValue + } + + if(caseStatus.value == suspectedValue){ + triggerDependants( + source = caseStatus, + addItems = listOf(testType), + removeItems = listOf() + ) + } else { + triggerDependants( + source = caseStatus, + addItems = listOf(), + removeItems = listOf(testType) + ) + } + } + + caseStatus.id -> { + if(caseStatus.value == resources.getStringArray(R.array.dc_case_status)[0]){ + triggerDependants( + source = caseStatus, + addItems = listOf(testType), + removeItems = listOf() + ) + } else { + triggerDependants( + source = caseStatus, + addItems = listOf(), + removeItems = listOf(testType) + ) + } + 0 + } + + + testType.id -> { + if(testType.value == resources.getStringArray(R.array.test_type)[0]){ + triggerDependants( + source = testType, + addItems = listOf(rapidDiagnostic), + removeItems = listOf(slideTestOptions,slideTestPf,slideTestPv,dateOfTest,dateOfSlidetest) + ) + } else if(testType.value == resources.getStringArray(R.array.test_type)[1]){ + triggerDependants( + source = testType, + addItems = listOf(slideTestOptions), + removeItems = listOf(rapidDiagnostic,slideTestPf,slideTestPv,dateOfTest,dateOfSlidetest) + ) + } else { + triggerDependants( + source = testType, + addItems = listOf(rapidDiagnostic,slideTestOptions), + removeItems = listOf(slideTestPf,slideTestPv,dateOfTest,dateOfSlidetest) + ) + } + 0 + } + + beneficiaryStatus.id -> { + if (beneficiaryStatus.value == beneficiaryStatus.entries!!.last()) { + triggerDependants( + source = beneficiaryStatus, + addItems = listOf(dateOfDeath,placeOfDeath,reasonOfDeath), + removeItems = listOf(headline,isFever,isFluLikeIllness,isShakingchills,isHeadache, + isMuscleaches,isTiredness,isNausea,isVomiting,isDiarrhea, + caseStatus,rapidDiagnostic,slideTestPf,slideTestPv,referredTo, + remarks,dateOfVisitBySupervisor) + ) + } else { + triggerDependants( + source = beneficiaryStatus, + addItems = listOf(headline,isFever,isFluLikeIllness,isShakingchills,isHeadache, + isMuscleaches,isTiredness,isNausea,isVomiting,isDiarrhea, + caseStatus), + removeItems = listOf(dateOfDeath,placeOfDeath,reasonOfDeath) + ) + } + 0 + } + + referredTo.id -> { + if (referredTo.value == referredTo.entries!![referredTo.entries!!.size - 2]) { + triggerDependants( + source = referredTo, + addItems = listOf(other), + removeItems = listOf() + ) + } else { + triggerDependants( + source = referredTo, + addItems = listOf(), + removeItems = listOf(other) + ) + } + 0 + } + + other.id -> { + validateEmptyOnEditText(other) + } + + placeOfDeath.id -> { + if (placeOfDeath.value == placeOfDeath.entries!!.last()) { + triggerDependants( + source = placeOfDeath, + addItems = listOf(otherPlaceOfDeath), + removeItems = listOf() + ) + } else { + triggerDependants( + source = placeOfDeath, + addItems = listOf(), + removeItems = listOf(otherPlaceOfDeath) + ) + } + 0 + } + otherPlaceOfDeath.id -> { + validateEmptyOnEditText(otherPlaceOfDeath) + + } + + rapidDiagnostic.id -> { + if (rapidDiagnostic.value == resources.getStringArray(R.array.positive_negative)[2]) { + + triggerDependants( + source = rapidDiagnostic, + addItems = listOf(), + removeItems = listOf(dateOfTest,slideTestOptions,slideTestPf,slideTestPv,dateOfSlidetest) + ) + } else if (rapidDiagnostic.value == resources.getStringArray(R.array.positive_negative)[1]) { + if (testType.value == resources.getStringArray(R.array.test_type)[2]) { + triggerDependants( + source = rapidDiagnostic, + addItems = listOf(dateOfTest,slideTestOptions), + removeItems = listOf() + ) + } else { + triggerDependants( + source = rapidDiagnostic, + addItems = listOf(dateOfTest), + removeItems = listOf(slideTestOptions,slideTestPf,slideTestPv) + ) + } + + + caseStatus.value = resources.getStringArray(R.array.dc_case_status)[2] + + +// caseStatus.value = getLocalValueInArray(caseStatus.arrayId, resources.getStringArray(R.array.dc_case_status)[1]) + } else { + if (testType.value == resources.getStringArray(R.array.test_type)[2]) { + triggerDependants( + source = rapidDiagnostic, + addItems = listOf(dateOfTest,slideTestOptions), + removeItems = listOf() + ) + } else { + triggerDependants( + source = rapidDiagnostic, + addItems = listOf(dateOfTest), + removeItems = listOf(slideTestOptions,slideTestPf,slideTestPv) + ) + } + /* triggerDependants( + source = rapidDiagnostic, + addItems = listOf(dateOfTest), + removeItems = listOf() + )*/ + + caseStatus.value = resources.getStringArray(R.array.dc_case_status)[1] + + + } + 0 + } + + slideTestOptions.id -> { + slideTestOptions.isEnabled = true + if (slideTestOptions.value == resources.getStringArray(R.array.pf_pv)[0]) { + triggerDependants( + source = slideTestOptions, + addItems = listOf(slideTestPf), + removeItems = listOf(slideTestPv) + ) + } else if (slideTestOptions.value == resources.getStringArray(R.array.pf_pv)[1]) { + + triggerDependants( + source = slideTestOptions, + addItems = listOf(slideTestPv), + removeItems = listOf(slideTestPf) + ) + } + 0 + } + + slideTestPv.id -> { + slideTestPv.isEnabled = true + if (slideTestPv.value == resources.getStringArray(R.array.positive_negative)[0]) { + triggerDependants( + source = slideTestPv, + addItems = listOf(dateOfSlidetest), + removeItems = listOf() + ) + + caseStatus.value = resources.getStringArray(R.array.dc_case_status)[1] + + + + } else if (slideTestPv.value == resources.getStringArray(R.array.positive_negative)[1]) { + + triggerDependants( + source = slideTestPv, + addItems = listOf(dateOfSlidetest), + removeItems = listOf() + ) + + caseStatus.value = resources.getStringArray(R.array.dc_case_status)[2] + + + + } else { + triggerDependants( + source = slideTestPv, + addItems = listOf(), + removeItems = listOf(dateOfSlidetest) + ) + + caseStatus.value = resources.getStringArray(R.array.dc_case_status)[2] + + + + } + 0 + } + + slideTestPf.id -> { + slideTestPf.isEnabled = true + if (slideTestPf.value == resources.getStringArray(R.array.positive_negative)[0]) { + triggerDependants( + source = slideTestPf, + addItems = listOf(dateOfSlidetest), + removeItems = listOf() + ) + + caseStatus.value = resources.getStringArray(R.array.dc_case_status)[1] + + + + } else if (slideTestPf.value == resources.getStringArray(R.array.positive_negative)[1]) { + + triggerDependants( + source = slideTestPf, + addItems = listOf(dateOfSlidetest), + removeItems = listOf() + ) + + caseStatus.value = resources.getStringArray(R.array.dc_case_status)[2] + + + + } else { + triggerDependants( + source = slideTestPf, + addItems = listOf(), + removeItems = listOf(dateOfSlidetest) + ) + + caseStatus.value = resources.getStringArray(R.array.dc_case_status)[2] + + + + } + 0 + } + + reasonOfDeath.id -> { + if (reasonOfDeath.value == reasonOfDeath.entries!!.last()) { + triggerDependants( + source = reasonOfDeath, + addItems = listOf(otherReasonOfDeath), + removeItems = listOf() + ) + } else { + triggerDependants( + source = reasonOfDeath, + addItems = listOf(), + removeItems = listOf(otherReasonOfDeath) + ) + } + 0 + } + otherReasonOfDeath.id -> { + validateEmptyOnEditText(otherReasonOfDeath) + } + + else -> { + 0 + } + } + + + } + + override fun mapValues(cacheModel: FormDataModel, pageNumber: Int) { + (cacheModel as MalariaScreeningCache).let { form -> + form.caseDate = getLongFromDate(dateOfCase.value) + form.feverMoreThanTwoWeeks = + isFever.value == resources.getStringArray(R.array.yes_no)[0] + form.fluLikeIllness = isFluLikeIllness.value == resources.getStringArray(R.array.yes_no)[0] + form.shakingChills = isShakingchills.value == resources.getStringArray(R.array.yes_no)[0] + form.headache = isHeadache.value == resources.getStringArray(R.array.yes_no)[0] + form.muscleAches = isMuscleaches.value == resources.getStringArray(R.array.yes_no)[0] + form.tiredness = isTiredness.value == resources.getStringArray(R.array.yes_no)[0] + form.nausea = + isNausea.value == resources.getStringArray(R.array.yes_no)[0] + form.vomiting = + isVomiting.value == resources.getStringArray(R.array.yes_no)[0] + form.diarrhea = + isDiarrhea.value == resources.getStringArray(R.array.yes_no)[0] + form.caseStatus = getEnglishValueInArray(R.array.dc_case_status, caseStatus.value) + form.referToName = getEnglishValueInArray(R.array.dc_refer_malaria, referredTo.value) + form.referredTo = referredTo.getPosition() + form.beneficiaryStatus = getEnglishValueInArray(R.array.benificary_case_status, beneficiaryStatus.value) + form.beneficiaryStatusId = beneficiaryStatus.getPosition() + form.reasonForDeath = getEnglishValueInArray(R.array.reason_death, reasonOfDeath.value) + form.otherPlaceOfDeath = otherPlaceOfDeath.value + form.otherReasonForDeath = otherReasonOfDeath.value + form.dateOfDeath = getLongFromDate(dateOfDeath.value) + form.dateOfRdt = getLongFromDate(dateOfTest.value) + form.dateOfSlideTest = getLongFromDate(dateOfSlidetest.value) + form.placeOfDeath = getEnglishValueInArray(R.array.death_place, placeOfDeath.value) + form.otherReferredFacility = other.value + form.rapidDiagnosticTest = getEnglishValueInArray(R.array.positive_negative, rapidDiagnostic.value) + form.slideTestPv = getEnglishValueInArray(R.array.positive_negative, slideTestPv.value) + form.slideTestPf = getEnglishValueInArray(R.array.positive_negative, slideTestPf.value) + form.slideTestName = getEnglishValueInArray(R.array.pf_pv, slideTestOptions.value) + form.remarks = remarks.value + form.dateOfVisitBySupervisor = getLongFromDate(dateOfVisitBySupervisor.value) + form.diseaseTypeID = 1 + form.followUpDate = getLongFromDate(followUpdate.value) + form.visitId = 1 + form.malariaTestType = testType.getPosition() + form.malariaSlideTestType = slideTestOptions.getPosition() + + } + } + + + fun updateBen(benRegCache: BenRegCache) { + benRegCache.genDetails?.let { + it.reproductiveStatus = + englishResources.getStringArray(R.array.nbr_reproductive_status_array2)[1] + it.reproductiveStatusId = 2 + } + if (benRegCache.processed != "N") benRegCache.processed = "U" + } + + + fun getIndexOfDate(): Int { + return getIndexById(dateOfCase.id) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/NewChildBenRegDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/NewChildBenRegDataset.kt new file mode 100644 index 000000000..1ec09b1f2 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/NewChildBenRegDataset.kt @@ -0,0 +1,1508 @@ +package org.piramalswasthya.sakhi.configuration + +import android.content.Context +import android.text.InputType +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.helpers.Konstants +import org.piramalswasthya.sakhi.helpers.Languages +import org.piramalswasthya.sakhi.helpers.setToStartOfTheDay +import org.piramalswasthya.sakhi.model.AgeUnit +import org.piramalswasthya.sakhi.model.BenRegCache +import org.piramalswasthya.sakhi.model.EligibleCoupleRegCache +import org.piramalswasthya.sakhi.model.FormElement +import org.piramalswasthya.sakhi.model.Gender +import org.piramalswasthya.sakhi.model.Gender.FEMALE +import org.piramalswasthya.sakhi.model.Gender.MALE +import org.piramalswasthya.sakhi.model.Gender.TRANSGENDER +import org.piramalswasthya.sakhi.model.HouseholdCache +import org.piramalswasthya.sakhi.model.InputType.DATE_PICKER +import org.piramalswasthya.sakhi.model.InputType.EDIT_TEXT +import org.piramalswasthya.sakhi.model.InputType.HEADLINE +import org.piramalswasthya.sakhi.model.InputType.RADIO +import org.piramalswasthya.sakhi.model.InputType.TEXT_VIEW +import org.piramalswasthya.sakhi.ui.home_activity.all_ben.new_ben_registration.ben_form.NewBenRegViewModel.Companion.isOtpVerified +import org.piramalswasthya.sakhi.utils.HelperUtil.getDiffYears +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Locale + + + +class NewChildBenRegDataset(context: Context, language: Languages) : Dataset(context, language) { + + + companion object { + private fun getCurrentDateString(): String { + val calendar = Calendar.getInstance() + val mdFormat = SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH) + return mdFormat.format(calendar.time) + } + + private fun getLongFromDate(dateString: String): Long { + val f = SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH) + val date = f.parse(dateString) + return date?.time ?: throw IllegalStateException("Invalid date for dateReg") + } + + private fun getMinLmpMillis(): Long { + val cal = Calendar.getInstance() + cal.add(Calendar.DAY_OF_YEAR, -1 * 400) //before it is 280 + return cal.timeInMillis + } + + private fun getMinDobMillis(): Long { + val cal = Calendar.getInstance() + cal.add(Calendar.YEAR, -15) + cal.add(Calendar.DAY_OF_MONTH, +1) + return cal.timeInMillis + } + + private fun getMaxDobMillis(): Long { + return System.currentTimeMillis() + } + + fun getMinimumSecondChildDob(firstChildDobStr: String?): String { + + val dateFormat = SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH) + val firstChildDob = dateFormat.parse(firstChildDobStr) + + val calendar = Calendar.getInstance() + calendar.time = firstChildDob!! + + return dateFormat.format(calendar.time) + } + } + + private var isExistingRecord = false + private var selectedBen: BenRegCache? = null + private val rchId = 0L + private val dateOfReg = FormElement( + id = 0, + inputType = DATE_PICKER, + title = resources.getString(R.string.ecrdset_date_of_reg), + arrayId = -1, + required = true, + max = System.currentTimeMillis(), + min = 0L, + hasDependants = true, + backgroundDrawable=R.drawable.ic_bg_circular, + iconDrawableRes=R.drawable.ic_anc_date, + showDrawable = true + ) + + private val elderChildrenCount = FormElement( + id = 1, + inputType = org.piramalswasthya.sakhi.model.InputType.TEXT_VIEW, + title = resources.getString(R.string.no_of_elderly_children), + arrayId = -1, + required = true, + hasDependants = true, + + ) + + + private val ageRestrictionLabel = FormElement( + id = 69, + inputType = org.piramalswasthya.sakhi.model.InputType.CHECKBOXES, + title = resources.getString(R.string.ecrdset_reg_children_15_below), + arrayId = -1, + required = false, + headingLine = true, + etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, + etMaxLength = 1, + max = 9, + min = 0, + ) + + val noOfChildren = FormElement( + id = 12, + inputType = org.piramalswasthya.sakhi.model.InputType.NUMBER_PICKER, + title = resources.getString(R.string.ecrdset_no_live_child), + required = false, + hasDependants = true, + etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, + etMaxLength = 1, + max = 9, + min = 0, + + ) + + + private val numMale = FormElement( + id = 14, + inputType = TEXT_VIEW, + title = resources.getString(R.string.ecrdset_male), + arrayId = -1, + required = false, + etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, + etMaxLength = 1, + max = 9, + min = 0, + backgroundDrawable=R.drawable.ic_bg_circular, + iconDrawableRes=R.drawable.ic_male, + showDrawable = true + ) + + private val numFemale = FormElement( + id = 15, + inputType = TEXT_VIEW, + title = resources.getString(R.string.ecrdset_female), + arrayId = -1, + required = false, + etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, + etMaxLength = 1, + max = 9, + min = 0, + backgroundDrawable=R.drawable.ic_bg_circular, + iconDrawableRes=R.drawable.ic_female, + showDrawable = true + + ) + + private val ageAtMarriage = FormElement( + id = 5, + inputType = TEXT_VIEW, + title = resources.getString(R.string.ecrdset_cur_ag_of_wo_marr), + arrayId = -1, + required = false, + etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, + etMaxLength = 2, + max = Konstants.maxAgeForGenBen.toLong(), + min = Konstants.minAgeForGenBen.toLong(), + isEnabled = false, + backgroundDrawable=R.drawable.ic_bg_circular, + iconDrawableRes=R.drawable.ic_age_of_women_at_marriage, + showDrawable = true + ) + + private val firstChildDetails = FormElement( + id = 16, + inputType = HEADLINE, + title = resources.getString(R.string.ecrdset_dls_1_child), + arrayId = -1, + required = false, + + ) + + private val firstChildName = FormElement( + id = 111, + inputType = EDIT_TEXT, + title = resources.getString(R.string.nbr_child_first_name), + arrayId = -1, + required = true, + allCaps = true, + hasSpeechToText = true, + etInputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS + ) + private val dob1 = FormElement( + id = 17, + inputType = DATE_PICKER, + title = resources.getString(R.string.ecrdset_1_child_bth), + arrayId = -1, + required = true, + hasDependants = true, + max = getMaxDobMillis(), + min = getMinDobMillis(), + backgroundDrawable=R.drawable.ic_bg_circular, + iconDrawableRes=R.drawable.ic_anc_date, + showDrawable = true + ) + + private val age1 = FormElement( + id = 18, + inputType = TEXT_VIEW, + title = resources.getString(R.string.ecrdset_1_child_age_in_yrs), + arrayId = -1, + required = true, + hasDependants = true, + etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, + etMaxLength = 2, + max = Konstants.maxAgeForAdolescent.toLong(), + min = 0, + backgroundDrawable=R.drawable.ic_bg_circular, + iconDrawableRes=R.drawable.ic_no_of_live_child, + showDrawable = true + ) + + private val gender1 = FormElement( + id = 19, + inputType = RADIO, + title = resources.getString(R.string.ecrdset_1_child_sex), + arrayId = -1, + entries = resources.getStringArray(R.array.ecr_gender_array), + required = true, + hasDependants = true, + ) + + private val marriageFirstChildGap = FormElement( + id = 20, + inputType = TEXT_VIEW, + title = resources.getString(R.string.ecrdset_gap_1_child_marr), + arrayId = -1, + required = true, + etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, + etMaxLength = 2, + max = 99, + min = 0, + backgroundDrawable=R.drawable.ic_bg_circular, + iconDrawableRes=R.drawable.ic_gap_bet_marriage_child, + showDrawable = true + ) + + private val secondChildDetails = FormElement( + id = 21, + inputType = HEADLINE, + title = resources.getString(R.string.ecrdset_dts_2_child), + arrayId = -1, + required = false + ) + private val secondChildName = FormElement( + id = 112, + inputType = EDIT_TEXT, + title = resources.getString(R.string.nbr_child_first_name), + arrayId = -1, + required = true, + allCaps = true, + hasSpeechToText = true, + etInputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS + ) + + private val dob2 = FormElement( + id = 22, + inputType = DATE_PICKER, + title = resources.getString(R.string.ecrdset_2_child_bth), + arrayId = -1, + required = true, + hasDependants = true, + max = getMaxDobMillis(), + min = getMinDobMillis(), + ) + + private val age2 = FormElement( + id = 23, + inputType = TEXT_VIEW, + title = resources.getString(R.string.ecrdset_2_child_age_yrs), + arrayId = -1, + required = true, + hasDependants = true, + etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, + etMaxLength = 2, + max = Konstants.maxAgeForAdolescent.toLong(), + min = 0, + ) + + private val gender2 = FormElement( + id = 24, + inputType = RADIO, + title = resources.getString(R.string.ecrdset_2_child_sex), + arrayId = -1, + entries = resources.getStringArray(R.array.ecr_gender_array), + required = true, + hasDependants = true, + ) + + private val firstAndSecondChildGap = FormElement( + id = 25, + inputType = TEXT_VIEW, + title = resources.getString(R.string.ecrdset_gap_1_child_2_child), + arrayId = -1, + required = true, + etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, + etMaxLength = 2, + max = 99, + min = 0, + ) + + private val thirdChildDetails = FormElement( + id = 26, + inputType = HEADLINE, + title = resources.getString(R.string.ecrdset_dts_3_child), + arrayId = -1, + required = false + ) + + private val thirdChildName = FormElement( + id = 113, + inputType = EDIT_TEXT, + title = resources.getString(R.string.nbr_child_first_name), + arrayId = -1, + required = true, + allCaps = true, + hasSpeechToText = true, + etInputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS + ) + + private val dob3 = FormElement( + id = 27, + inputType = DATE_PICKER, + title = resources.getString(R.string.ecrdset_3_child_bth), + arrayId = -1, + required = true, + hasDependants = true, + max = getMaxDobMillis(), + min = getMinDobMillis(), + ) + + private val age3 = FormElement( + id = 28, + inputType = TEXT_VIEW, + title = resources.getString(R.string.ecrdset_3_child_age_yrs), + arrayId = -1, + required = true, + hasDependants = true, + etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, + etMaxLength = 2, + max = Konstants.maxAgeForAdolescent.toLong(), + min = 0, + ) + + private val gender3 = FormElement( + id = 29, + inputType = RADIO, + title = resources.getString(R.string.ecrdset_3_child_sex), + arrayId = -1, + entries = resources.getStringArray(R.array.ecr_gender_array), + required = true, + hasDependants = true, + ) + + private val secondAndThirdChildGap = FormElement( + id = 30, + inputType = TEXT_VIEW, + title = resources.getString(R.string.ecrdset_gap_bet_2_3_child_sex), + arrayId = -1, + required = true, + etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, + etMaxLength = 2, + max = 99, + min = 0, + ) + + private val fourthChildDetails = FormElement( + id = 31, + inputType = HEADLINE, + title = resources.getString(R.string.ecrdset_dts_4_child), + arrayId = -1, + required = false + ) + + private val forthChildName = FormElement( + id = 114, + inputType = EDIT_TEXT, + title = resources.getString(R.string.nbr_child_first_name), + arrayId = -1, + required = true, + allCaps = true, + hasSpeechToText = true, + etInputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS + ) + + private val dob4 = FormElement( + id = 32, + inputType = DATE_PICKER, + title = resources.getString(R.string.ecrdset_4_child_bth), + arrayId = -1, + required = true, + hasDependants = true, + max = getMaxDobMillis(), + min = getMinDobMillis(), + ) + + private val age4 = FormElement( + id = 33, + inputType = TEXT_VIEW, + title = resources.getString(R.string.ecrdset_4_child_age_yrs), + arrayId = -1, + required = true, + hasDependants = true, + etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, + etMaxLength = 2, + max = Konstants.maxAgeForAdolescent.toLong(), + min = 0, + ) + + private val gender4 = FormElement( + id = 34, + inputType = RADIO, + title = resources.getString(R.string.ecrdset_4_child_sex), + arrayId = -1, + entries = resources.getStringArray(R.array.ecr_gender_array), + required = true, + hasDependants = true, + ) + + private val thirdAndFourthChildGap = FormElement( + id = 35, + inputType = TEXT_VIEW, + title = resources.getString(R.string.ecrdset_bet_3_4_child), + arrayId = -1, + required = true, + etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, + etMaxLength = 2, + max = 99, + min = 0, + ) + + private val fifthChildDetails = FormElement( + id = 36, + inputType = HEADLINE, + title = resources.getString(R.string.ecrdset_dts_5_child), + arrayId = -1, + required = false + ) + + private val fifthChildName = FormElement( + id = 115, + inputType = EDIT_TEXT, + title = resources.getString(R.string.nbr_child_first_name), + arrayId = -1, + required = true, + allCaps = true, + hasSpeechToText = true, + etInputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS + ) + + private val dob5 = FormElement( + id = 37, + inputType = DATE_PICKER, + title = resources.getString(R.string.ecrdset_5_child_bth), + arrayId = -1, + required = true, + hasDependants = true, + max = getMaxDobMillis(), + min = getMinDobMillis(), + ) + + private val age5 = FormElement( + id = 38, + inputType = TEXT_VIEW, + title = resources.getString(R.string.ecrdset_5_child_age_yrs), + arrayId = -1, + required = true, + hasDependants = true, + etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, + etMaxLength = 2, + max = Konstants.maxAgeForAdolescent.toLong(), + min = 0, + ) + + private val gender5 = FormElement( + id = 39, + inputType = RADIO, + title = resources.getString(R.string.ecrdset_5_child_sex), + arrayId = -1, + entries = resources.getStringArray(R.array.ecr_gender_array), + required = true, + hasDependants = true, + ) + + private val fourthAndFifthChildGap = FormElement( + id = 40, + inputType = TEXT_VIEW, + title = resources.getString(R.string.ecrdset_gap_4_5_child), + arrayId = -1, + required = true, + etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, + etMaxLength = 2, + max = 99, + min = 0, + ) + + private val sixthChildDetails = FormElement( + id = 41, + inputType = HEADLINE, + title = resources.getString(R.string.ecrdset_dts_6_child), + arrayId = -1, + required = false + ) + + private val sixthChildName = FormElement( + id = 116, + inputType = EDIT_TEXT, + title = resources.getString(R.string.nbr_child_first_name), + arrayId = -1, + required = true, + allCaps = true, + hasSpeechToText = true, + etInputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS + ) + + private val dob6 = FormElement( + id = 42, + inputType = DATE_PICKER, + title = resources.getString(R.string.ecrdset_dts_6_bth), + arrayId = -1, + required = true, + hasDependants = true, + max = getMaxDobMillis(), + min = getMinDobMillis(), + ) + + private val age6 = FormElement( + id = 43, + inputType = TEXT_VIEW, + title = resources.getString(R.string.ecrdset_6_child_age), + arrayId = -1, + required = true, + hasDependants = true, + etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, + etMaxLength = 2, + max = Konstants.maxAgeForAdolescent.toLong(), + min = 0, + ) + + private val gender6 = FormElement( + id = 44, + inputType = RADIO, + title = resources.getString(R.string.ecrdset_6_child_sex), + arrayId = -1, + entries = resources.getStringArray(R.array.ecr_gender_array), + required = true, + hasDependants = true, + ) + + private val fifthAndSixthChildGap = FormElement( + id = 45, + inputType = TEXT_VIEW, + title = resources.getString(R.string.ecrdset_gap_5_6_child), + arrayId = -1, + required = true, + etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, + etMaxLength = 2, + max = 99, + min = 0, + ) + + private val seventhChildDetails = FormElement( + id = 46, + inputType = HEADLINE, + title = resources.getString(R.string.ecrdset_7_dts_child), + arrayId = -1, + required = false + ) + + private val seventhChildName = FormElement( + id = 117, + inputType = EDIT_TEXT, + title = resources.getString(R.string.nbr_child_first_name), + arrayId = -1, + required = true, + allCaps = true, + hasSpeechToText = true, + etInputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS + ) + private val dob7 = FormElement( + id = 47, + inputType = DATE_PICKER, + title = resources.getString(R.string.ecrdset_7_child_bth), + arrayId = -1, + required = true, + hasDependants = true, + max = getMaxDobMillis(), + min = getMinDobMillis(), + ) + + private val age7 = FormElement( + id = 48, + inputType = TEXT_VIEW, + title = resources.getString(R.string.ecrdset_7_child_age_yrs), + arrayId = -1, + required = true, + hasDependants = true, + etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, + etMaxLength = 2, + max = Konstants.maxAgeForAdolescent.toLong(), + min = 0, + ) + + private val gender7 = FormElement( + id = 49, + inputType = RADIO, + title = resources.getString(R.string.ecrdset_7_child_sex), + arrayId = -1, + entries = resources.getStringArray(R.array.ecr_gender_array), + required = true, + hasDependants = true, + ) + + private val sixthAndSeventhChildGap = FormElement( + id = 50, + inputType = TEXT_VIEW, + title = resources.getString(R.string.ecrdset_gap_6_7_child), + arrayId = -1, + required = true, + etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, + etMaxLength = 2, + max = 99, + min = 0, + ) + + private val eighthChildDetails = FormElement( + id = 51, + inputType = HEADLINE, + title = resources.getString(R.string.ecrdset_dts_8_child), + arrayId = -1, + required = false + ) + + private val eightChildName = FormElement( + id = 118, + inputType = EDIT_TEXT, + title = resources.getString(R.string.nbr_child_first_name), + arrayId = -1, + required = true, + allCaps = true, + hasSpeechToText = true, + etInputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS + ) + + private val dob8 = FormElement( + id = 52, + inputType = DATE_PICKER, + title = resources.getString(R.string.ecrdset_8_child_bth), + arrayId = -1, + required = true, + hasDependants = true, + max = getMaxDobMillis(), + min = getMinDobMillis(), + ) + + private val age8 = FormElement( + id = 53, + inputType = TEXT_VIEW, + title = resources.getString(R.string.ecrdset_8_child_age), + arrayId = -1, + required = true, + hasDependants = true, + etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, + etMaxLength = 2, + max = Konstants.maxAgeForAdolescent.toLong(), + min = 0, + ) + + private val gender8 = FormElement( + id = 54, + inputType = RADIO, + title = resources.getString(R.string.ecrdset_8_child_sex), + arrayId = -1, + entries = resources.getStringArray(R.array.ecr_gender_array), + required = true, + hasDependants = true, + ) + + private val seventhAndEighthChildGap = FormElement( + id = 55, + inputType = TEXT_VIEW, + title = resources.getString(R.string.ecrdset_gap_7_8_child), + arrayId = -1, + required = true, + etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, + etMaxLength = 2, + max = 99, + min = 0, + ) + + private val ninthChildDetails = FormElement( + id = 56, + inputType = HEADLINE, + title = resources.getString(R.string.ecrdset_dts_9_child), + arrayId = -1, + required = false + ) + + private val ninthChildName = FormElement( + id = 119, + inputType = EDIT_TEXT, + title = resources.getString(R.string.nbr_child_first_name), + arrayId = -1, + required = true, + allCaps = true, + hasSpeechToText = true, + etInputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS + ) + private val dob9 = FormElement( + id = 57, + inputType = DATE_PICKER, + title = resources.getString(R.string.ecrdset_9_bth), + arrayId = -1, + required = true, + hasDependants = true, + max = getMaxDobMillis(), + min = getMinDobMillis(), + ) + + private val age9 = FormElement( + id = 58, + inputType = TEXT_VIEW, + title = resources.getString(R.string.ecrdset_9_age_yrs), + arrayId = -1, + required = true, + hasDependants = true, + etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, + etMaxLength = 2, + max = Konstants.maxAgeForAdolescent.toLong(), + min = 0, + ) + + private val gender9 = FormElement( + id = 59, + inputType = RADIO, + title = resources.getString(R.string.ecrdset_9_child_sex), + arrayId = -1, + entries = resources.getStringArray(R.array.ecr_gender_array), + required = true, + hasDependants = true, + ) + + private var maleChild = 0 + + private var femaleChild = 0 + + private var dateOfBirth = 0L + + private var lastDeliveryDate = 0L + private var timeAtMarriage: Long = 0L + private val eighthAndNinthChildGap = FormElement( + id = 60, + inputType = TEXT_VIEW, + title = resources.getString(R.string.ecrdset_gap_8_9_child), + arrayId = -1, + required = true, + etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, + etMaxLength = 2, + max = 99, + min = 0, + ) + + suspend fun setUpPage( + ben: EligibleCoupleRegCache?, + household: HouseholdCache, + hoF: BenRegCache?, + benGender: Gender, + relationToHeadId: Int, + hoFSpouse: List = emptyList(), + selectedben: BenRegCache?, + isAddspouse: Int, + childList: List, + elderChildCount: Int + + ) { + val list = mutableListOf( + dateOfReg, + elderChildrenCount, + ageRestrictionLabel, + noOfChildren, + + + + ) + + isExistingRecord = ben != null + + selectedBen = selectedben + selectedben?.let { + dateOfReg.min = it.regDate + selectedben.genDetails?.ageAtMarriage?.let { it1 -> + ageAtMarriage.value = it1.toString() + val cal = Calendar.getInstance() + cal.timeInMillis = selectedben.dob + cal.set(Calendar.YEAR, cal.get(Calendar.YEAR) + it1) + timeAtMarriage = cal.timeInMillis + } + } + + + + /* if (ecCache.dateOfReg == 0L) { + ecCache.dateOfReg = System.currentTimeMillis() + }*/ + + dateOfReg.value = getDateFromLong(System.currentTimeMillis()) + + noOfChildren.value = childList.size.toString() + + elderChildrenCount.value = elderChildCount.coerceAtMost(5).toString() + + + var insertIndex = list.indexOf(noOfChildren) + 1 + + childList.forEachIndexed { index, child -> + + val bundle = children[index] + + bundle.name.value = child.firstName + bundle.dob.value = getDateFromLong(child.dob) + bundle.age.value = child.age?.toString() + + bundle.gender.value = getLocalValueInArray( + R.array.ecr_gender_array, + child.gender?.name + ?.lowercase() + ?.replaceFirstChar { it.uppercase() } + ) + + if (index == 0) { + setSiblingAgeDiff(timeAtMarriage, child.dob, bundle.gap) + } else { + setSiblingAgeDiff( + childList[index - 1].dob, + child.dob, + bundle.gap + ) + } + + val childViews = listOf( + bundle.name, + bundle.dob, + bundle.age, + bundle.gender, + bundle.gap + ) + + list.addAll(insertIndex, childViews) + + insertIndex += childViews.size + } + + setUpPage(list) + + } + + + private suspend fun updateTimeLessThan18() { + val dobStrings = listOf( + dob1.value, dob2.value, dob3.value, dob4.value, dob5.value, + dob6.value, dob7.value, dob8.value, dob9.value + ).filter { !it.isNullOrBlank() } + + if (dobStrings.isNotEmpty()) { + val dobLongs = dobStrings.map { getLongFromDate(it!!) } + lastDeliveryDate = dobLongs.maxOrNull()!! + } + + } + + private fun setSiblingAgeDiff(old: Long, new: Long, target: FormElement) { + val calOld = Calendar.getInstance().setToStartOfTheDay().apply { + timeInMillis = old + } + val calNew = Calendar.getInstance().setToStartOfTheDay().apply { + timeInMillis = new + } + val diff = getDiffYears(calOld, calNew) + target.value = "${diff.toString()} years" + } + + private fun getRelationStringFromId(familyHeadRelationId: Int): String { + return if (familyHeadRelationId == 9) + "Son" + else + "Daughter" + } + + private fun getFamilyHeadRelationFromMother(childGender: Gender): Int { + return if (childGender == Gender.MALE) + 9 + else + 10 + } + fun mapChild( + cacheModel: BenRegCache, + childIndex: Int + ): BenRegCache { + + val ben = cacheModel.copy() + val childGender = getChildGender(childIndex) + if (childGender == null) { + throw IllegalStateException("Gender must be selected for child $childIndex") + } + val familyHeadRelationId = + getFamilyHeadRelationFromMother(childGender) + val familyHeadRelation = getRelationStringFromId(familyHeadRelationId) + when(childIndex) { + + 1 -> { + ben.firstName = firstChildName.value + ben.lastName = selectedBen?.lastName + ben.dob = Dataset.getLongFromDate(dob1.value!!) + ben.age = age1.value!!.toInt() + ben.ageUnitId = 3 + ben.ageUnit = AgeUnit.YEARS + ben.isAdult = ben.ageUnit == AgeUnit.YEARS && ben.age >= 15 + ben.isKid = !ben.isAdult + ben.genderId = when (gender1.value) { + gender1.entries!![0] -> 1 + gender1.entries!![1] -> 2 + gender1.entries!![2] -> 3 + else -> 0 + } + ben.gender = when (ben.genderId) { + 1 -> MALE + 2 -> FEMALE + 3 -> TRANSGENDER + else -> null + } + ben.familyHeadRelation = familyHeadRelation + ben.familyHeadRelationPosition = familyHeadRelationId + } + 2 -> { + ben.firstName = secondChildName.value + ben.lastName = selectedBen?.lastName + ben.dob = Dataset.getLongFromDate(dob2.value!!) + ben.age = age2.value!!.toInt() + ben.ageUnitId = 3 + ben.ageUnit = AgeUnit.YEARS + ben.isAdult = ben.ageUnit == AgeUnit.YEARS && ben.age >= 15 + ben.isKid = !ben.isAdult + ben.genderId = when (gender2.value) { + gender2.entries!![0] -> 1 + gender2.entries!![1] -> 2 + gender2.entries!![2] -> 3 + else -> 0 + } + ben.gender = when (ben.genderId) { + 1 -> MALE + 2 -> FEMALE + 3 -> TRANSGENDER + else -> null + } + ben.familyHeadRelation = familyHeadRelation + ben.familyHeadRelationPosition = familyHeadRelationId + } + + 3 -> { + ben.firstName = thirdChildName.value + ben.lastName = selectedBen?.lastName + ben.dob = Dataset.getLongFromDate(dob3.value!!) + ben.age = age3.value!!.toInt() + ben.ageUnitId = 3 + ben.ageUnit = AgeUnit.YEARS + ben.isAdult = ben.ageUnit == AgeUnit.YEARS && ben.age >= 15 + ben.isKid = !ben.isAdult + ben.genderId = when (gender3.value) { + gender2.entries!![0] -> 1 + gender2.entries!![1] -> 2 + gender2.entries!![2] -> 3 + else -> 0 + } + ben.gender = when (ben.genderId) { + 1 -> MALE + 2 -> FEMALE + 3 -> TRANSGENDER + else -> null + } + ben.familyHeadRelation = familyHeadRelation + ben.familyHeadRelationPosition = familyHeadRelationId + } + + 4 -> { + ben.firstName = forthChildName.value + ben.lastName = selectedBen?.lastName + ben.dob = Dataset.getLongFromDate(dob4.value!!) + ben.age = age4.value!!.toInt() + ben.ageUnitId = 3 + ben.ageUnit = AgeUnit.YEARS + ben.isAdult = ben.ageUnit == AgeUnit.YEARS && ben.age >= 15 + ben.isKid = !ben.isAdult + ben.genderId = when (gender4.value) { + gender2.entries!![0] -> 1 + gender2.entries!![1] -> 2 + gender2.entries!![2] -> 3 + else -> 0 + } + ben.gender = when (ben.genderId) { + 1 -> MALE + 2 -> FEMALE + 3 -> TRANSGENDER + else -> null + } + ben.familyHeadRelation = familyHeadRelation + ben.familyHeadRelationPosition = familyHeadRelationId + } + 5 -> { + ben.firstName = fifthChildName.value + ben.lastName = selectedBen?.lastName + ben.dob = Dataset.getLongFromDate(dob5.value!!) + ben.age = age5.value!!.toInt() + ben.ageUnitId = 3 + ben.ageUnit = AgeUnit.YEARS + ben.isAdult = ben.ageUnit == AgeUnit.YEARS && ben.age >= 15 + ben.isKid = !ben.isAdult + ben.genderId = when (gender5.value) { + gender2.entries!![0] -> 1 + gender2.entries!![1] -> 2 + gender2.entries!![2] -> 3 + else -> 0 + } + ben.gender = when (ben.genderId) { + 1 -> MALE + 2 -> FEMALE + 3 -> TRANSGENDER + else -> null + } + ben.familyHeadRelation = familyHeadRelation + ben.familyHeadRelationPosition = familyHeadRelationId + } + 6 -> { + ben.firstName = sixthChildName.value + ben.lastName = selectedBen?.lastName + ben.dob = Dataset.getLongFromDate(dob6.value!!) + ben.age = age6.value!!.toInt() + ben.ageUnitId = 3 + ben.ageUnit = AgeUnit.YEARS + ben.isAdult = ben.ageUnit == AgeUnit.YEARS && ben.age >= 15 + ben.isKid = !ben.isAdult + ben.genderId = when (gender6.value) { + gender2.entries!![0] -> 1 + gender2.entries!![1] -> 2 + gender2.entries!![2] -> 3 + else -> 0 + } + ben.gender = when (ben.genderId) { + 1 -> MALE + 2 -> FEMALE + 3 -> TRANSGENDER + else -> null + } + ben.familyHeadRelation = familyHeadRelation + ben.familyHeadRelationPosition = familyHeadRelationId + } + 7 -> { + ben.firstName = seventhChildName.value + ben.lastName = selectedBen?.lastName + ben.dob = Dataset.getLongFromDate(dob7.value!!) + ben.age = age7.value!!.toInt() + ben.ageUnitId = 3 + ben.ageUnit = AgeUnit.YEARS + ben.isAdult = ben.ageUnit == AgeUnit.YEARS && ben.age >= 15 + ben.isKid = !ben.isAdult + ben.genderId = when (gender7.value) { + gender2.entries!![0] -> 1 + gender2.entries!![1] -> 2 + gender2.entries!![2] -> 3 + else -> 0 + } + ben.gender = when (ben.genderId) { + 1 -> MALE + 2 -> FEMALE + 3 -> TRANSGENDER + else -> null + } + ben.familyHeadRelation = familyHeadRelation + ben.familyHeadRelationPosition = familyHeadRelationId + } + 8 -> { + ben.firstName = eightChildName.value + ben.lastName = selectedBen?.lastName + ben.dob = Dataset.getLongFromDate(dob8.value!!) + ben.age = age8.value!!.toInt() + ben.ageUnitId = 3 + ben.ageUnit = AgeUnit.YEARS + ben.isAdult = ben.ageUnit == AgeUnit.YEARS && ben.age >= 15 + ben.isKid = !ben.isAdult + ben.genderId = when (gender8.value) { + gender2.entries!![0] -> 1 + gender2.entries!![1] -> 2 + gender2.entries!![2] -> 3 + else -> 0 + } + ben.gender = when (ben.genderId) { + 1 -> MALE + 2 -> FEMALE + 3 -> TRANSGENDER + else -> null + } + ben.familyHeadRelation = familyHeadRelation + ben.familyHeadRelationPosition = familyHeadRelationId + } + 9 -> { + ben.firstName = ninthChildName.value + ben.lastName = selectedBen?.lastName + ben.dob = Dataset.getLongFromDate(dob9.value!!) + ben.age = age9.value!!.toInt() + ben.ageUnitId = 3 + ben.ageUnit = AgeUnit.YEARS + ben.isAdult = ben.ageUnit == AgeUnit.YEARS && ben.age >= 15 + ben.isKid = !ben.isAdult + ben.genderId = when (gender9.value) { + gender2.entries!![0] -> 1 + gender2.entries!![1] -> 2 + gender2.entries!![2] -> 3 + else -> 0 + } + ben.gender = when (ben.genderId) { + 1 -> MALE + 2 -> FEMALE + 3 -> TRANSGENDER + else -> null + } + ben.familyHeadRelation = familyHeadRelation + ben.familyHeadRelationPosition = familyHeadRelationId + } + } + + ben.householdId = selectedBen?.householdId!! + ben.regDate = Dataset.Companion.getLongFromDate(dateOfReg.value!!) + ben.fatherName = "${selectedBen?.genDetails?.spouseName}" + ben.motherName = "${selectedBen?.firstName}" + ben.isDeath = false + ben.isDeathValue = "false" + ben.dateOfDeath = null + ben.timeOfDeath = null + ben.reasonOfDeath = null + ben.doYouHavechildren = false + ben.placeOfDeath = null + ben.mobileNoOfRelationId = 5 + ben.otherPlaceOfDeath = null + ben.contactNumber = selectedBen!!.contactNumber + ben.mobileNoOfRelationId = 5 + ben.isDraft = false + ben.isConsent = isOtpVerified + ben.isSpouseAdded = false + ben.isChildrenAdded = false + ben.isMarried = false + ben.doYouHavechildren = false + ben.community = selectedBen!!.community + ben.communityId = selectedBen!!.communityId + + return ben + } + + private fun mapGender(genderField: FormElement): Gender? { + return when (genderField.value) { + genderField.entries?.getOrNull(0) -> MALE + genderField.entries?.getOrNull(1) -> FEMALE + genderField.entries?.getOrNull(2) -> TRANSGENDER + else -> null + } + } + + private fun getChildGender(childIndex: Int): Gender? { + return when (childIndex) { + 1 -> mapGender(gender1) + 2 -> mapGender(gender2) + 3 -> mapGender(gender3) + 4 -> mapGender(gender4) + 5 -> mapGender(gender5) + 6 -> mapGender(gender6) + 7 -> mapGender(gender7) + 8 -> mapGender(gender8) + 9 -> mapGender(gender9) + else -> null + } + } + override fun mapValues(cacheModel: FormDataModel, pageNumber: Int) { + (cacheModel as EligibleCoupleRegCache).let { ecr -> + ecr.dateOfReg = + getLongFromDate(dateOfReg.value!!) + + ecr.noOfChildren = noOfChildren.value?.takeIf { it.isNotBlank() }?.toInt() ?: 0 + ecr.noOfMaleChildren = numMale.value?.takeIf { it.isNotBlank() }?.toInt() ?: 0 + ecr.noOfFemaleChildren = numFemale.value?.takeIf { it.isNotBlank() }?.toInt() ?: 0 + ecr.dob1 = getLongFromDate(dob1.value) + ecr.age1 = age1.value?.toInt() + ecr.gender1 = when (gender1.value) { + gender1.entries!![0] -> Gender.MALE + gender1.entries!![1] -> Gender.FEMALE + else -> null + } + ecr.marriageFirstChildGap =marriageFirstChildGap.value?.filter { it.isDigit() }?.toIntOrNull() ?: 0 + if ((noOfChildren.value?.toIntOrNull() ?: 0) > 1){ + ecr.dob2 = getLongFromDate(dob2.value) + ecr.age2 = age2.value?.toInt() + ecr.gender2 = when (gender2.value) { + gender2.entries!![0] -> Gender.MALE + gender2.entries!![1] -> Gender.FEMALE + else -> null + } + ecr.firstAndSecondChildGap =firstAndSecondChildGap.value?.filter { it.isDigit() }?.toIntOrNull() ?: 0 + } + if (noOfChildren.value?.toInt()!! > 2) { + ecr.dob3 = getLongFromDate(dob3.value) + ecr.age3 = age3.value?.toInt() + ecr.gender3 = when (gender3.value) { + gender3.entries!![0] -> Gender.MALE + gender3.entries!![1] -> Gender.FEMALE + else -> null + } + ecr.secondAndThirdChildGap =secondAndThirdChildGap.value?.filter { it.isDigit() }?.toIntOrNull() ?: 0 + } + if (noOfChildren.value?.toInt()!! > 3) { + ecr.dob4 = getLongFromDate(dob4.value) + ecr.age4 = age4.value?.toInt() + ecr.gender4 = when (gender4.value) { + gender4.entries!![0] -> Gender.MALE + gender4.entries!![1] -> Gender.FEMALE + else -> null + } + ecr.thirdAndFourthChildGap =thirdAndFourthChildGap.value?.filter { it.isDigit() }?.toIntOrNull() ?: 0 + } + if (noOfChildren.value?.toInt()!! > 4) { + ecr.dob5 = getLongFromDate(dob5.value) + ecr.age5 = age5.value?.toInt() + ecr.gender5 = when (gender5.value) { + gender5.entries!![0] -> Gender.MALE + gender5.entries!![1] -> Gender.FEMALE + else -> null + } + ecr.fourthAndFifthChildGap =fourthAndFifthChildGap.value?.filter { it.isDigit() }?.toIntOrNull() ?: 0 + } + if (noOfChildren.value?.toInt()!! > 5) { + ecr.dob6 = getLongFromDate(dob6.value) + ecr.age6 = age6.value?.toInt() + ecr.gender6 = when (gender6.value) { + gender6.entries!![0] -> Gender.MALE + gender6.entries!![1] -> Gender.FEMALE + else -> null + } + ecr.fifthANdSixthChildGap =fifthAndSixthChildGap.value?.filter { it.isDigit() }?.toIntOrNull() ?: 0 + } + if (noOfChildren.value?.toInt()!! > 6) { + ecr.dob7 = getLongFromDate(dob7.value) + ecr.age7 = age7.value?.toInt() + ecr.gender7 = when (gender7.value) { + gender7.entries!![0] -> Gender.MALE + gender7.entries!![1] -> Gender.FEMALE + else -> null + } + ecr.sixthAndSeventhChildGap =sixthAndSeventhChildGap.value?.filter { it.isDigit() }?.toIntOrNull() ?: 0 + } + if (noOfChildren.value?.toInt()!! > 7) { + ecr.dob8 = getLongFromDate(dob8.value) + ecr.age8 = age8.value?.toInt() + ecr.gender8 = when (gender8.value) { + gender8.entries!![0] -> Gender.MALE + gender8.entries!![1] -> Gender.FEMALE + else -> null + } + ecr.seventhAndEighthChildGap = seventhAndEighthChildGap.value?.filter { it.isDigit() }?.toIntOrNull() ?: 0 + } + if (noOfChildren.value?.toInt()!! > 8) { + ecr.dob9 = getLongFromDate(dob9.value) + ecr.age9 = age9.value?.toInt() + ecr.gender9 = when (gender9.value) { + gender9.entries!![0] -> Gender.MALE + gender9.entries!![1] -> Gender.FEMALE + else -> null + } + ecr.eighthAndNinthChildGap = eighthAndNinthChildGap.value?.filter { it.isDigit() }?.toIntOrNull() ?: 0 + } + } + } + + + private val children = listOf( + ChildBundle(firstChildDetails, firstChildName, dob1, age1, gender1, marriageFirstChildGap), + ChildBundle(secondChildDetails, secondChildName, dob2, age2, gender2, firstAndSecondChildGap), + ChildBundle(thirdChildDetails, thirdChildName, dob3, age3, gender3, secondAndThirdChildGap), + ChildBundle(fourthChildDetails, forthChildName, dob4, age4, gender4, thirdAndFourthChildGap), + ChildBundle(fifthChildDetails, fifthChildName, dob5, age5, gender5, fourthAndFifthChildGap), + ChildBundle(sixthChildDetails, sixthChildName, dob6, age6, gender6, fifthAndSixthChildGap), + ChildBundle(seventhChildDetails, seventhChildName, dob7, age7, gender7, sixthAndSeventhChildGap), + ChildBundle(eighthChildDetails, eightChildName, dob8, age8, gender8, seventhAndEighthChildGap), + ChildBundle(ninthChildDetails, ninthChildName, dob9, age9, gender9, eighthAndNinthChildGap) + ) + + private val childNameFields = listOf( + firstChildName, + secondChildName, + thirdChildName, + forthChildName, + fifthChildName, + sixthChildName, + seventhChildName, + eightChildName, + ninthChildName + ) + + + override suspend fun handleListOnValueChanged(formId: Int, index: Int): Int { + childNameFields.firstOrNull { it.id == formId }?.let { childName -> + validateEmptyOnEditText(childName) + validateAllCapsOrSpaceOnEditTextWithHindiEnabled(childName) + } + + + children.forEachIndexed { idx, child -> + if (formId == child.dob.id) { + val prevDobValue = if (idx == 0) { + null + } else children[idx - 1].dob.value + + prevDobValue?.let { isValidChildGap(child.dob, it) } + + val currentDobStr = child.dob.value + if ((idx == 0 && currentDobStr != null && timeAtMarriage != 0L) || + (idx != 0 && children[idx - 1].dob.value != null && currentDobStr != null) + ) { + val currDobLong = getLongFromDate(currentDobStr) + assignValuesToAgeFromDob(currDobLong, child.age) + validateIntMinMax(child.age) + + val months = getMonthsFromDob(currDobLong) + val existingName = child.name.value + if (months <= 3) { + val motherName = selectedBen?.firstName ?: "" + child.name.value = "Baby of $motherName" + child.name.required = false + } else { + child.name.required = true + if (existingName.isNullOrBlank()) { + child.name.value = "" + } + } + + + val prevLong = when { + idx == 0 -> timeAtMarriage + else -> getLongFromDate(children[idx - 1].dob.value!!) + } + setSiblingAgeDiff(prevLong, currDobLong, child.gap) + + children.getOrNull(idx + 1)?.let { nextChild -> + nextChild.dob.min = getLongFromDate(getMinimumSecondChildDob(currentDobStr)) + } + + updateTimeLessThan18() + } + return -1 + } + } + + if (formId == noOfChildren.id) { + noOfChildren.min = noOfChildren.value.takeIf { !it.isNullOrEmpty() }?.toLong() + validateIntMinMax(noOfChildren) + + if (isExistingRecord) { + + val newCount = noOfChildren.value?.toIntOrNull() ?: 0 + + val oldCount = children.count { child -> + child.dob.value != null || + child.gender.value != null || + child.age.value != null + } + + if (newCount > oldCount) { + for (i in oldCount until newCount) { + if (i == 0) { + if (timeAtMarriage != 0L) { + children[i].dob.min = timeAtMarriage + } + } else { + val prevDob = children[i - 1].dob.value + if (!prevDob.isNullOrEmpty()) { + children[i].dob.min = + getLongFromDate(getMinimumSecondChildDob(prevDob)) + } + } + } + + val addItems = children.subList(oldCount, newCount) + .flatMap { it.toFormList() } + + infantTriggerDependants( + source = noOfChildren, + addItems = addItems, + removeItems = emptyList() + ) + } + } else { + val count = noOfChildren.value?.toIntOrNull() ?: 0 + val addItems = children.take(count).flatMap { it.toFormList() } + val removeItems = children.drop(count).flatMap { it.toFormList() } + triggerDependants( source = noOfChildren, addItems = addItems, removeItems = removeItems ) + children.drop(count).forEach { it.clearValues() } + + } + + + + + + return 1 + } + + val genderIds = children.map { it.gender.id } + if (genderIds.contains(formId)) { + var male = 0 + var female = 0 + val genderArray = resources.getStringArray(R.array.ecr_gender_array) + + children.forEach { child -> + val g = child.gender.value + if (g == genderArray[0]) male += 1 + else if (g == genderArray[1]) female += 1 + } + + numFemale.value = female.toString() + numMale.value = male.toString() + return -1 + } + + return -1 + } + + fun getMonthsFromDob(dob: Long): Int { + val now = Calendar.getInstance() + val birth = Calendar.getInstance().apply { timeInMillis = dob } + + val years = now.get(Calendar.YEAR) - birth.get(Calendar.YEAR) + val months = now.get(Calendar.MONTH) - birth.get(Calendar.MONTH) + return years * 12 + months + } + fun getIndexOfChildren() = getIndexById(noOfChildren.id) + + fun getIndexOfMaleChildren() = getIndexById(numMale.id) + fun getIndexOfFeMaleChildren() = getIndexById(numFemale.id) + fun getIndexOfAge1() = getIndexById(age1.id) + fun getIndexOfGap1() = getIndexById(marriageFirstChildGap.id) + fun getIndexOfAge2() = getIndexById(age2.id) + fun getIndexOfGap2() = getIndexById(firstAndSecondChildGap.id) + fun getIndexOfAge3() = getIndexById(age3.id) + fun getIndexOfGap3() = getIndexById(secondAndThirdChildGap.id) + fun getIndexOfAge4() = getIndexById(age4.id) + fun getIndexOfGap4() = getIndexById(thirdAndFourthChildGap.id) + fun getIndexOfAge5() = getIndexById(age5.id) + fun getIndexOfGap5() = getIndexById(fourthAndFifthChildGap.id) + + fun getIndexOfAge6() = getIndexById(age6.id) + fun getIndexOfGap6() = getIndexById(fifthAndSixthChildGap.id) + + fun getIndexOfAge7() = getIndexById(age7.id) + fun getIndexOfGap7() = getIndexById(sixthAndSeventhChildGap.id) + + fun getIndexOfAge8() = getIndexById(age8.id) + fun getIndexOfGap8() = getIndexById(seventhAndEighthChildGap.id) + + fun getIndexOfAge9() = getIndexById(age9.id) + fun getIndexOfGap9() = getIndexById(eighthAndNinthChildGap.id) + + +} + + +data class ChildBundle( + val details: FormElement, + val name: FormElement, + val dob: FormElement, + val age: FormElement, + val gender: FormElement, + val gap: FormElement +) { + fun isEmpty(): Boolean { + return dob.value.isNullOrEmpty() && + age.value.isNullOrEmpty() && + gender.value.isNullOrEmpty() && + name.value.isNullOrEmpty() + } + fun toFormList() = listOf(details, name, dob, age, gender, gap) + fun clearValues() { + name.value = null + dob.value = null + age.value = null + gender.value = null + gap.value = null + } +} + diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/PHCReviewDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/PHCReviewDataset.kt new file mode 100644 index 000000000..36b4a549e --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/PHCReviewDataset.kt @@ -0,0 +1,278 @@ +package org.piramalswasthya.sakhi.configuration + +import android.content.Context +import android.net.Uri +import android.util.Log +import org.piramalswasthya.sakhi.BuildConfig +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.helpers.Konstants +import org.piramalswasthya.sakhi.helpers.Languages +import org.piramalswasthya.sakhi.model.BenRegCache +import org.piramalswasthya.sakhi.model.FormElement +import org.piramalswasthya.sakhi.model.InputType +import org.piramalswasthya.sakhi.model.InputType.DATE_PICKER +import org.piramalswasthya.sakhi.model.InputType.EDIT_TEXT +import org.piramalswasthya.sakhi.model.InputType.IMAGE_VIEW +import org.piramalswasthya.sakhi.model.PHCReviewMeetingCache +import org.piramalswasthya.sakhi.model.VHNCCache +import org.piramalswasthya.sakhi.utils.HelperUtil.parseSelections +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Locale + +class PHCReviewDataset( + context: Context, currentLanguage: Languages +) : Dataset(context, currentLanguage) { + + companion object { + private fun getCurrentDateString(): String { + val calendar = Calendar.getInstance() + val mdFormat = SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH) + return mdFormat.format(calendar.time) + } + + } + + + private val phcReviewDate = FormElement( + id = 3, + inputType = DATE_PICKER, + title = resources.getString(R.string.phc_review_meeting_date), + arrayId = -1, + required = true, + min = System.currentTimeMillis() - (60L * 24 * 60 * 60 * 1000), + max = System.currentTimeMillis(), + ) + + private val place = FormElement( + id = 4, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.place_of_meeting), + entries = resources.getStringArray(R.array.place_of_vhsnc), + arrayId = R.array.place_of_vhsnc, + required = true, + allCaps = true, + ) + + private var villageName = FormElement( + id = 5, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.village_name), + required = false, + hasDependants = false + ) + + private val noOfParticipantsAttended = FormElement( + id = 6, + inputType = EDIT_TEXT, + title = resources.getString(R.string.total_no_of_participants_attended), + arrayId = -1, + required = false, + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + value = "0", + etMaxLength = 3, + max = 999, + min = 0 + ) + + private val mitanin = FormElement( + id = 8, + inputType = InputType.CHECKBOXES, + title = resources.getString(R.string.participants_attended), + arrayId = -1, + required = false, + entries = resources.getStringArray(R.array.mitanin_array), + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + ) + + private val mT = FormElement( + id = 9, + inputType = InputType.CHECKBOXES, + title = resources.getString(R.string.mt), + arrayId = -1, + required = false, + entries = resources.getStringArray(R.array.activity_checklist), + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + ) + + private val pic1 = FormElement( + id = 1, + inputType = InputType.FILE_UPLOAD, + title = resources.getString(R.string.upload_image), + arrayId = -1, + required = false, + ) + private val pic2 = FormElement( + id = 2, + inputType = InputType.FILE_UPLOAD, + title = resources.getString(R.string.upload_image), + arrayId = -1, + required = false, + ) + + suspend fun setUpPage(phc: PHCReviewMeetingCache?) { + if (pic1.value.isNullOrBlank()) { + pic1.value = "default" + } + + if (pic2.value.isNullOrBlank()) { + pic2.value = "default" + } + val list = mutableListOf( + phcReviewDate, + place, + villageName, + noOfParticipantsAttended, + ) + + if (BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true)) { + list.addAll( + listOf( + mitanin, + mT, + + + ) + ) + } + + list.addAll( + listOf( + pic1, + pic2 + ) + ) + phcReviewDate.value = getCurrentDateString() + phc?.let { + phcReviewDate.value = it.phcReviewDate + pic1.value = it.image1 + pic2.value = it.image2 + place.value = getLocalValueInArray(R.array.place_of_vhsnc, it.place) + villageName.value = it.villageName + noOfParticipantsAttended.value = it.noOfBeneficiariesAttended.toString() + if (BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true)) { + val parsedMitaninHistoryList = parseSelections(it.mitaninHistory, mitanin.entries!!) + mitanin.value = if (parsedMitaninHistoryList.isNotEmpty()) { + parsedMitaninHistoryList.joinToString("|") + } else { + it.mitaninHistory ?: "" + } + + val parsedMitaninCheckListList = parseSelections(it.mitaninActivityCheckList, mT.entries!!) + mT.value = if (parsedMitaninCheckListList.isNotEmpty()) { + parsedMitaninCheckListList.joinToString("|") + } else { + it.mitaninActivityCheckList ?: "" + } + } + + } + setUpPage(list) + } + + + override suspend fun handleListOnValueChanged(formId: Int, index: Int): Int { + + return when (formId) { + + mT.id -> { + + val allItems = resources.getStringArray(R.array.activity_checklist) + val itemCount = allItems.size + + val selectedIndexes = mT.value + ?.split("|") + ?.mapNotNull { it.toIntOrNull() } + ?.toMutableSet() + ?: mutableSetOf() + + if (index == 0) { + + if (selectedIndexes.contains(0)) { + selectedIndexes.clear() + selectedIndexes.addAll(0 until itemCount) + } else { + selectedIndexes.clear() + } + + mT.value = + if (selectedIndexes.isEmpty()) null + else selectedIndexes.sorted().joinToString("|") + + return -1 + } + val normalIndexes = (1 until itemCount) + + if (selectedIndexes.contains(0) && + normalIndexes.any { !selectedIndexes.contains(it) } + ) { + selectedIndexes.remove(0) + } + + if (normalIndexes.all { selectedIndexes.contains(it) }) { + selectedIndexes.add(0) + } + + mT.value = + if (selectedIndexes.isEmpty()) null + else selectedIndexes.sorted().joinToString("|") + + + -1 + } + place.id -> { + validateEmptyOnEditText(place) + validateAllAlphabetsSpecialAndNumericOnEditText(place) + -1 + } + villageName.id -> { + validateEmptyOnEditText(villageName) + validateAllAlphabetsSpecialAndNumericOnEditText(villageName) + -1 + } + + noOfParticipantsAttended.id -> { + validateEmptyOnEditText(noOfParticipantsAttended) + -1 + } + + else -> { + -1 + } + } + } + + private fun toCsv(rawValue: String?, entries: Array): String { + return parseSelections(rawValue, entries).joinToString("|") + } + override fun mapValues(cacheModel: FormDataModel, pageNumber: Int) { + (cacheModel as PHCReviewMeetingCache).let { form -> + + form.phcReviewDate = phcReviewDate.value!! + form.place = getEnglishValueInArray(R.array.place_of_vhsnc, place.value) + form.placeId = place.getPosition() + form.noOfBeneficiariesAttended = noOfParticipantsAttended.value?.toIntOrNull() ?: 0 + form.image1 = pic1.value + form.image2 = pic2.value + form.villageName = villageName.value + form.mitaninHistory = toCsv(mitanin.value,mitanin.entries!!) + form.mitaninActivityCheckList = toCsv(mT.value,mT.entries!!) + + } + + } + fun setImageUriToFormElement(lastImageFormId: Int, dpUri: Uri) { + when (lastImageFormId) { + pic1.id -> { + pic1.value = dpUri.toString() + pic1.errorText = null + } + pic2.id -> { + pic2.value = dpUri.toString() + pic2.errorText = null + } + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/PMSMAFormDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/PMSMAFormDataset.kt index 294409a96..53eff5be5 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/configuration/PMSMAFormDataset.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/PMSMAFormDataset.kt @@ -1,511 +1,660 @@ -package org.piramalswasthya.sakhi.configuration - -import android.content.Context -import android.text.InputType -import org.piramalswasthya.sakhi.R -import org.piramalswasthya.sakhi.helpers.Languages -import org.piramalswasthya.sakhi.helpers.setToStartOfTheDay -import org.piramalswasthya.sakhi.model.BenRegCache -import org.piramalswasthya.sakhi.model.FormElement -import org.piramalswasthya.sakhi.model.HouseholdCache -import org.piramalswasthya.sakhi.model.InputType.EDIT_TEXT -import org.piramalswasthya.sakhi.model.InputType.HEADLINE -import org.piramalswasthya.sakhi.model.InputType.RADIO -import org.piramalswasthya.sakhi.model.InputType.TEXT_VIEW -import org.piramalswasthya.sakhi.model.PMSMACache -import org.piramalswasthya.sakhi.model.PregnantWomanAncCache -import org.piramalswasthya.sakhi.model.PregnantWomanRegistrationCache -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Locale -import java.util.concurrent.TimeUnit - -class PMSMAFormDataset( - context: Context, currentLanguage: Languages -) : Dataset(context, currentLanguage) { - - companion object { - private fun getLongFromDate(dateString: String): Long { - val f = SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH) - val date = f.parse(dateString) - return date?.time ?: throw IllegalStateException("Invalid date for dateReg") - } - } - - private val mctsNumberOrRchNumber = FormElement( - id = 1, - inputType = TEXT_VIEW, - title = resources.getString(R.string.pmsma_mcts_rch_number), - required = false - ) - private val haveMCPCard = FormElement( - id = 2, - inputType = RADIO, - title = resources.getString(R.string.pmsma_have_mcp_card), - entries = resources.getStringArray(R.array.pmsma_confirmation_array), - hasDependants = true, - required = false - ) - val givenMCPCard = FormElement( - id = 3, - inputType = RADIO, - title = resources.getString(R.string.pmsma_given_mcp_card), - entries = resources.getStringArray(R.array.pmsma_confirmation_array), - required = false - ) - - private val husbandName = FormElement( - id = 4, - inputType = TEXT_VIEW, - title = resources.getString(R.string.pmsma_husband_name), - required = false - ) - private val address = FormElement( - id = 5, - inputType = EDIT_TEXT, - title = resources.getString(R.string.pmsma_address), - required = false - ) - private val mobileNumber = FormElement( - id = 6, - inputType = TEXT_VIEW, - title = resources.getString(R.string.pmsma_mobile_number), - required = false - ) - private val numANC = FormElement( - id = 7, - inputType = EDIT_TEXT, - etMaxLength = 2, - title = resources.getString(R.string.pmsma_num_anc), - etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, - required = false - ) - private val motherStatus = FormElement( - id = 8, - inputType = HEADLINE, - title = resources.getString(R.string.pmsma_mother_status), - required = false - ) - private val weight = FormElement( - id = 9, - inputType = EDIT_TEXT, - title = resources.getString(R.string.pmsma_weight), - min = 30, - max = 200, - etMaxLength = 3, - etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, - required = false - ) - private val bp = FormElement( - id = 10, - inputType = EDIT_TEXT, - title = "BP of PW – Systolic/ Diastolic (mm Hg) ", -// etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, - etMaxLength = 7, - required = false, - ) - private val abdominalCheckUp = FormElement( - id = 12, - inputType = TEXT_VIEW, - title = resources.getString(R.string.pmsma_abdominal_check_up), - required = false - ) - private val fetalStatus = FormElement( - id = 13, - inputType = HEADLINE, - title = resources.getString(R.string.pmsma_fetal_status), - required = false - ) - private val fetalHRPM = FormElement( - id = 14, - inputType = EDIT_TEXT, - etMaxLength = 3, - title = resources.getString(R.string.pmsma_fetal_hrpm), - etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, - required = false - ) - private val twinPregnancy = FormElement( - id = 15, - inputType = RADIO, - title = resources.getString(R.string.pmsma_twin_pregnancy), - entries = resources.getStringArray(R.array.pmsma_confirmation_array), - required = false - ) - private val investigations = FormElement( - id = 16, - inputType = HEADLINE, - title = resources.getString(R.string.pmsma_investigation), - required = false - ) - private val urineAlbumin = FormElement( - id = 17, - inputType = EDIT_TEXT, - title = resources.getString(R.string.pmsma_urine_albumin), - required = false - ) - private val haemoglobinAndBloodGroup = FormElement( - id = 18, - inputType = EDIT_TEXT, - title = resources.getString(R.string.pmsma_haemoglobin_and_blood_group), - required = false - ) - private val hiv = FormElement( - id = 19, - inputType = EDIT_TEXT, - title = resources.getString(R.string.pmsma_hiv), - required = false - ) - private val vdrl = FormElement( - id = 20, - inputType = EDIT_TEXT, - title = resources.getString(R.string.pmsma_vdrl), - required = false - ) - private val hbsc = FormElement( - id = 21, - inputType = EDIT_TEXT, - title = resources.getString(R.string.pmsma_hbsc), - required = false - ) - private val malaria = FormElement( - id = 22, - inputType = EDIT_TEXT, - title = resources.getString(R.string.pmsma_malaria), - required = false - ) - private val hivTestDuringANC = FormElement( - id = 23, - inputType = RADIO, - title = resources.getString(R.string.pmsma_hiv_test_during_anc), - entries = resources.getStringArray(R.array.pmsma_confirmation_array), - required = false - ) - private val swollenCondtion = FormElement( - id = 24, - inputType = RADIO, - title = resources.getString(R.string.pmsma_swollen_condition), - entries = resources.getStringArray(R.array.pmsma_confirmation_array), - required = false - ) - private val bloodSugarTest = FormElement( - id = 25, - inputType = RADIO, - title = resources.getString(R.string.pmsma_blood_sugar_test), - entries = resources.getStringArray(R.array.pmsma_confirmation_array), - required = false - ) - private val ultraSound = FormElement( - id = 26, - inputType = RADIO, - title = resources.getString(R.string.pmsma_ultrasound), - entries = resources.getStringArray(R.array.pmsma_confirmation_array), - required = false - ) - private val interventionDetails = FormElement( - id = 27, - inputType = HEADLINE, - title = resources.getString(R.string.pmsma_intervention_details), - required = false - ) - private val ironFolicAcid = FormElement( - id = 28, - inputType = RADIO, - title = resources.getString(R.string.pmsma_iron_folic_acid), - entries = resources.getStringArray(R.array.pmsma_confirmation_array), - required = false - ) - private val calciumSupplementation = FormElement( - id = 29, - inputType = RADIO, - title = resources.getString(R.string.pmsma_calcium_supplementation), - entries = resources.getStringArray(R.array.pmsma_confirmation_array), - required = false - ) - private val tetanusToxoid = FormElement( - id = 30, - inputType = RADIO, - title = resources.getString(R.string.pmsma_tetanus_toxoid), - entries = resources.getStringArray(R.array.pmsma_tetanus_toxoid_array), - required = false - ) - private val lmp = FormElement( - id = 31, - inputType = HEADLINE, - title = resources.getString(R.string.pmsma_lmp), - required = false - ) - private val lastMenstrualPeriod = FormElement( - id = 32, - inputType = TEXT_VIEW, - title = resources.getString(R.string.pmsma_last_mestrual_period), - min = 0L, - max = System.currentTimeMillis(), - required = false - ) - private val expectedDateOfDelivery = FormElement( - id = 33, - inputType = TEXT_VIEW, - title = resources.getString(R.string.pmsma_expected_delivery_date), - required = false - ) - private val highRiskFactors = FormElement( - id = 34, - inputType = HEADLINE, - title = resources.getString(R.string.pmsma_high_risk_factors), - required = false - ) - private val highriskSymbols = FormElement( - id = 35, - inputType = RADIO, - title = resources.getString(R.string.pmsma_high_risk_symbols), - entries = resources.getStringArray(R.array.pmsma_confirmation_array), - hasDependants = true, - required = false - ) - private val highRiskReason = FormElement( - id = 36, - inputType = EDIT_TEXT, - title = resources.getString(R.string.pmsma_high_risk_reason), - required = false - ) - private val highRiskPregnant = FormElement( - id = 37, - inputType = RADIO, - title = resources.getString(R.string.pmsma_high_risk_pregnant), - entries = resources.getStringArray(R.array.pmsma_confirmation_array), - required = false - ) - private val highRiskPregnancyReferred = FormElement( - id = 38, - inputType = RADIO, - title = resources.getString(R.string.pmsma_high_risk_pregnancy_referred), - entries = resources.getStringArray(R.array.pmsma_confirmation_array), - required = false - ) - private val advice = FormElement( - id = 39, - inputType = HEADLINE, - title = resources.getString(R.string.pmsma_advice), - required = false - ) - private val birthPrepAndNutritionAndFamilyPlanning = FormElement( - id = 40, - inputType = RADIO, - title = resources.getString(R.string.pmsma_birth_complications_and_planning), - entries = resources.getStringArray(R.array.pmsma_confirmation_array), - required = false - ) - private val medicalOfficerSign = FormElement( - id = 41, - inputType = EDIT_TEXT, - title = resources.getString(R.string.pmsma_medical_officer_sign), - required = false - ) - - suspend fun setUpFirstPage( - household: HouseholdCache, - ben: BenRegCache, - pwr: PregnantWomanRegistrationCache, - lastAnc: PregnantWomanAncCache?, - pmsma: PMSMACache? - ) { - mctsNumberOrRchNumber.value = pwr.rchId?.toString() - address.value = getAddress(household) - mobileNumber.value = ben.contactNumber.toString() - husbandName.value = ben.genDetails?.spouseName - mctsNumberOrRchNumber.value = ben.rchId - lastAnc?.let { - "${(it.ancDate - pwr.lmpDate) / 7} Weeks" - } - lastMenstrualPeriod.value = getDateFromLong(pwr.lmpDate) - expectedDateOfDelivery.value = getDateFromLong(getEddFromLmp(pwr.lmpDate)) - abdominalCheckUp.value = "${ - (TimeUnit.MILLISECONDS.toDays( - Calendar.getInstance().setToStartOfTheDay().timeInMillis - pwr.lmpDate - ) / 7) - } Weeks" - pmsma?.let { setExistingValues(it) } - val list = firstPage - setUpPage(list) - } - - - private val firstPage by lazy { - listOf( - mctsNumberOrRchNumber, - haveMCPCard, - husbandName, - address, - mobileNumber, - numANC, - motherStatus, - weight, - bp, - abdominalCheckUp, - fetalStatus, - fetalHRPM, - twinPregnancy, - investigations, - urineAlbumin, - haemoglobinAndBloodGroup, - hiv, - vdrl, - hbsc, - malaria, - hivTestDuringANC, - swollenCondtion, - bloodSugarTest, - ultraSound, - interventionDetails, - ironFolicAcid, - calciumSupplementation, - tetanusToxoid, - lmp, - lastMenstrualPeriod, - expectedDateOfDelivery, - highRiskFactors, - highriskSymbols, - highRiskPregnant, - highRiskPregnancyReferred, - advice, - birthPrepAndNutritionAndFamilyPlanning, - medicalOfficerSign, - - ) - } - - - override fun mapValues(cacheModel: FormDataModel, pageNumber: Int) { - (cacheModel as PMSMACache).let { pmsmaCache -> - pmsmaCache.mctsNumberOrRchNumber = mctsNumberOrRchNumber.value - pmsmaCache.haveMCPCard = haveMCPCard.value == "Yes" - pmsmaCache.husbandName = husbandName.value - pmsmaCache.address = address.value - pmsmaCache.mobileNumber = mobileNumber.value?.toLong() ?: 0L - pmsmaCache.numANC = numANC.value?.toInt() ?: 0 - pmsmaCache.weight = weight.value?.toInt() ?: 0 - pmsmaCache.systolicBloodPressure = - bp.value?.takeIf { it.isNotEmpty() }?.substringBefore("/") - pmsmaCache.bloodPressure = bp.value?.takeIf { it.isNotEmpty() }?.substringAfter("/") - pmsmaCache.abdominalCheckUp = abdominalCheckUp.value - pmsmaCache.fetalHRPM = fetalHRPM.value?.toInt() ?: 0 - pmsmaCache.twinPregnancy = twinPregnancy.value == "Yes" - pmsmaCache.urineAlbumin = urineAlbumin.value - pmsmaCache.haemoglobinAndBloodGroup = haemoglobinAndBloodGroup.value - pmsmaCache.hiv = hiv.value - pmsmaCache.vdrl = vdrl.value - pmsmaCache.hbsc = hbsc.value - pmsmaCache.malaria = malaria.value - pmsmaCache.hivTestDuringANC = hivTestDuringANC.value == "Yes" - pmsmaCache.swollenCondtion = swollenCondtion.value == "Yes" - pmsmaCache.bloodSugarTest = bloodSugarTest.value == "Yes" - pmsmaCache.ultraSound = ultraSound.value == "Yes" - pmsmaCache.ironFolicAcid = ironFolicAcid.value == "Yes" - pmsmaCache.calciumSupplementation = calciumSupplementation.value == "Yes" - pmsmaCache.tetanusToxoid = tetanusToxoid.value - pmsmaCache.lastMenstrualPeriod = - lastMenstrualPeriod.value?.let { getLongFromDate(it) } ?: 0L - pmsmaCache.expectedDateOfDelivery = getLongFromDate(expectedDateOfDelivery.value!!) - pmsmaCache.highriskSymbols = highriskSymbols.value == "Yes" - pmsmaCache.highRiskReason = highRiskReason.value - pmsmaCache.highRiskPregnant = highRiskPregnant.value == "Yes" - pmsmaCache.highRiskPregnancyReferred = highRiskPregnancyReferred.value == "Yes" - pmsmaCache.birthPrepAndNutritionAndFamilyPlanning = - birthPrepAndNutritionAndFamilyPlanning.value == "Yes" - pmsmaCache.medicalOfficerSign = medicalOfficerSign.value + package org.piramalswasthya.sakhi.configuration + + import android.app.AlertDialog + import android.content.Context + import android.text.InputType + import android.util.Log + import org.piramalswasthya.sakhi.R + import org.piramalswasthya.sakhi.helpers.Konstants.english + import org.piramalswasthya.sakhi.helpers.Languages + import org.piramalswasthya.sakhi.helpers.setToStartOfTheDay + import org.piramalswasthya.sakhi.model.BenRegCache + import org.piramalswasthya.sakhi.model.FormElement + import org.piramalswasthya.sakhi.model.HouseholdCache + import org.piramalswasthya.sakhi.model.InputType.EDIT_TEXT + import org.piramalswasthya.sakhi.model.InputType.HEADLINE + import org.piramalswasthya.sakhi.model.InputType.RADIO + import org.piramalswasthya.sakhi.model.InputType.TEXT_VIEW + import org.piramalswasthya.sakhi.model.PMSMACache + import org.piramalswasthya.sakhi.model.PregnantWomanAncCache + import org.piramalswasthya.sakhi.model.PregnantWomanRegistrationCache + import org.piramalswasthya.sakhi.ui.home_activity.maternal_health.pmsma.PmsmaViewModel + import java.text.SimpleDateFormat + import java.util.Calendar + import java.util.Date + import java.util.Locale + import java.util.concurrent.TimeUnit + + class PMSMAFormDataset( + context: Context, currentLanguage: Languages, + private val viewModel: PmsmaViewModel + ) : Dataset(context, currentLanguage) { + + companion object { + private fun getLongFromDate(dateString: String): Long { + val f = SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH) + val date = f.parse(dateString) + return date?.time ?: throw IllegalStateException("Invalid date for dateReg") + } } - } - - fun setExistingValues(pmsma: PMSMACache) { - mctsNumberOrRchNumber.value = pmsma.mctsNumberOrRchNumber - haveMCPCard.value = if (pmsma.haveMCPCard == true) "Yes" else "No" - husbandName.value = pmsma.husbandName - address.value = pmsma.address?.let { if (it.length > 100) it.substring(0, 100) else it } - mobileNumber.value = pmsma.mobileNumber.toString() - numANC.value = pmsma.numANC.toString() - weight.value = pmsma.weight.toString() - bp.value = - if (pmsma.systolicBloodPressure == null || pmsma.bloodPressure == null) null else "${pmsma.systolicBloodPressure}/${pmsma.bloodPressure}" - abdominalCheckUp.value = pmsma.abdominalCheckUp - fetalHRPM.value = pmsma.fetalHRPM.toString() - twinPregnancy.value = if (pmsma.twinPregnancy == true) "Yes" else "No" - urineAlbumin.value = pmsma.urineAlbumin - haemoglobinAndBloodGroup.value = pmsma.haemoglobinAndBloodGroup - hiv.value = pmsma.hiv - vdrl.value = pmsma.vdrl - hbsc.value = pmsma.hbsc - malaria.value = pmsma.malaria - hivTestDuringANC.value = if (pmsma.hivTestDuringANC == true) "Yes" else "No" - swollenCondtion.value = if (pmsma.swollenCondtion == true) "Yes" else "No" - bloodSugarTest.value = if (pmsma.bloodSugarTest == true) "Yes" else "No" - ultraSound.value = if (pmsma.ultraSound == true) "Yes" else "No" - ironFolicAcid.value = if (pmsma.ironFolicAcid == true) "Yes" else "No" - calciumSupplementation.value = - if (pmsma.calciumSupplementation == true) "Yes" else "No" - tetanusToxoid.value = pmsma.tetanusToxoid - lastMenstrualPeriod.value = pmsma.lastMenstrualPeriod.let { getDateFromLong(it) } - expectedDateOfDelivery.value = pmsma.expectedDateOfDelivery.let { getDateFromLong(it) } - highriskSymbols.value = if (pmsma.highriskSymbols == true) "Yes" else "No" - highRiskReason.value = pmsma.highRiskReason - highRiskPregnant.value = if (pmsma.highRiskPregnant == true) "Yes" else "No" - highRiskPregnancyReferred.value = - if (pmsma.highRiskPregnancyReferred == true) "Yes" else "No" - birthPrepAndNutritionAndFamilyPlanning.value = - if (pmsma.birthPrepAndNutritionAndFamilyPlanning == true) "Yes" else "No" - medicalOfficerSign.value = pmsma.medicalOfficerSign - } - - - override suspend fun handleListOnValueChanged(formId: Int, index: Int): Int { - return when (formId) { - haveMCPCard.id -> { - triggerDependants( - source = haveMCPCard, - passedIndex = index, - triggerIndex = 1, - target = givenMCPCard - ) + private val pmsmaVisitDate = FormElement( + id = 43, + inputType = org.piramalswasthya.sakhi.model.InputType.DATE_PICKER, + title = resources.getString(R.string.pmsma_visit_date), + required = true, + hasDependants = true, + showDrawable = true + ) + + private val pmsmaVisit = FormElement( + id = 44, + inputType = org.piramalswasthya.sakhi.model.InputType.TEXT_VIEW, + title = resources.getString(R.string.pmsma_visit), + required = true, + showDrawable = true, + ) + private val detailsOfPW = FormElement( + id = 46, + inputType = HEADLINE, + title = resources.getString(R.string.details_of_pregnant_women), + required = false + ) + + private val highRiskReason = FormElement( + id = 45, + inputType = org.piramalswasthya.sakhi.model.InputType.DROPDOWN, + title = resources.getString(R.string.if_yes_select_high_risk_condition), + arrayId = R.array.select_high_risk_condition, + entries = resources.getStringArray(R.array.select_high_risk_condition), + required = true, + showDrawable = true, + ) + private val anyOtherHighRiskCondition = FormElement( + id = 43, + etMaxLength = 50, + inputType = EDIT_TEXT, + title = context.getString(R.string.any_other_high_risk_conditions), + required = true, + hasDependants = true, + ) + + private val mctsNumberOrRchNumber = FormElement( + id = 1, + inputType = TEXT_VIEW, + title = resources.getString(R.string.pmsma_mcts_rch_number), + required = false + ) + private val haveMCPCard = FormElement( + id = 2, + inputType = RADIO, + title = resources.getString(R.string.pmsma_have_mcp_card), + entries = resources.getStringArray(R.array.pmsma_confirmation_array), + hasDependants = true, + required = false + ) + val givenMCPCard = FormElement( + id = 3, + inputType = RADIO, + title = resources.getString(R.string.pmsma_given_mcp_card), + entries = resources.getStringArray(R.array.pmsma_confirmation_array), + required = false + ) + + private val husbandName = FormElement( + id = 4, + inputType = TEXT_VIEW, + title = resources.getString(R.string.pmsma_husband_name), + required = false + ) + private val address = FormElement( + id = 5, + inputType = EDIT_TEXT, + title = resources.getString(R.string.pmsma_address), + required = false + ) + private val mobileNumber = FormElement( + id = 6, + inputType = TEXT_VIEW, + title = resources.getString(R.string.pmsma_mobile_number), + required = false + ) + private val numANC = FormElement( + id = 7, + inputType = TEXT_VIEW, + etMaxLength = 2, + title = resources.getString(R.string.pmsma_num_anc), + etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, + required = false + ) + private val motherStatus = FormElement( + id = 8, + inputType = HEADLINE, + title = resources.getString(R.string.pmsma_mother_status), + required = false + ) + private val weight = FormElement( + id = 9, + inputType = EDIT_TEXT, + title = resources.getString(R.string.pmsma_weight), + min = 30, + max = 200, + etMaxLength = 3, + etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, + required = false + ) + private val bp = FormElement( + id = 10, + inputType = EDIT_TEXT, + title = "BP of PW – Systolic/ Diastolic (mm Hg) ", + // etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + etMaxLength = 7, + required = false, + ) + private val abdominalCheckUp = FormElement( + id = 12, + inputType = TEXT_VIEW, + title = resources.getString(R.string.pmsma_abdominal_check_up), + required = false + ) + private val fetalStatus = FormElement( + id = 13, + inputType = HEADLINE, + title = resources.getString(R.string.pmsma_fetal_status), + required = false + ) + private val fetalHRPM = FormElement( + id = 14, + inputType = EDIT_TEXT, + etMaxLength = 3, + title = resources.getString(R.string.pmsma_fetal_hrpm), + etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, + required = false + ) + private val twinPregnancy = FormElement( + id = 15, + inputType = RADIO, + title = resources.getString(R.string.pmsma_twin_pregnancy), + entries = resources.getStringArray(R.array.pmsma_confirmation_array), + required = false + ) + private val investigations = FormElement( + id = 16, + inputType = HEADLINE, + title = resources.getString(R.string.pmsma_investigation), + required = false + ) + private val urineAlbumin = FormElement( + id = 17, + inputType = RADIO, + arrayId = R.array.pmsma_confirmation_array, + entries = resources.getStringArray(R.array.pmsma_confirmation_array), + title = resources.getString(R.string.pmsma_urine_albumin), + required = false + ) + private val haemoglobinAndBloodGroup = FormElement( + id = 18, + inputType = EDIT_TEXT, + title = resources.getString(R.string.pmsma_haemoglobin_and_blood_group), + required = false + ) + private val hiv = FormElement( + id = 19, + inputType = TEXT_VIEW, + title = resources.getString(R.string.pmsma_hiv), + required = false + ) + private val vdrl = FormElement( + id = 20, + inputType = TEXT_VIEW, + title = resources.getString(R.string.pmsma_vdrl), + required = false + ) + private val hbsc = FormElement( + id = 21, + inputType = TEXT_VIEW, + title = resources.getString(R.string.pmsma_hbsc), + required = false + ) + private val malaria = FormElement( + id = 22, + inputType = RADIO, + arrayId = R.array.pmsma_malaria_array, + entries = resources.getStringArray(R.array.pmsma_malaria_array), + title = resources.getString(R.string.pmsma_malaria), + required = false + ) + private val hivTestDuringANC = FormElement( + id = 23, + inputType = RADIO, + title = resources.getString(R.string.pmsma_hiv_test_during_anc), + entries = resources.getStringArray(R.array.pmsma_confirmation_array), + required = false + ) + private val swollenCondtion = FormElement( + id = 24, + inputType = RADIO, + title = resources.getString(R.string.pmsma_swollen_condition), + entries = resources.getStringArray(R.array.pmsma_confirmation_array), + required = false + ) + private val bloodSugarTest = FormElement( + id = 25, + inputType = RADIO, + title = resources.getString(R.string.pmsma_blood_sugar_test), + entries = resources.getStringArray(R.array.pmsma_confirmation_array), + required = false + ) + private val ultraSound = FormElement( + id = 26, + inputType = RADIO, + title = resources.getString(R.string.pmsma_ultrasound), + entries = resources.getStringArray(R.array.pmsma_confirmation_array), + required = false + ) + private val interventionDetails = FormElement( + id = 27, + inputType = HEADLINE, + title = resources.getString(R.string.pmsma_intervention_details), + required = false + ) + private val ironFolicAcid = FormElement( + id = 28, + inputType = RADIO, + title = resources.getString(R.string.pmsma_iron_folic_acid), + entries = resources.getStringArray(R.array.pmsma_confirmation_array), + required = false + ) + private val calciumSupplementation = FormElement( + id = 29, + inputType = RADIO, + title = resources.getString(R.string.pmsma_calcium_supplementation), + entries = resources.getStringArray(R.array.pmsma_confirmation_array), + required = false + ) + private val tetanusToxoid = FormElement( + id = 30, + inputType = RADIO, + title = resources.getString(R.string.pmsma_tetanus_toxoid), + arrayId = R.array.pmsma_tetanus_toxoid_array, + entries = resources.getStringArray(R.array.pmsma_tetanus_toxoid_array), + required = false + ) + private val lmp = FormElement( + id = 31, + inputType = HEADLINE, + title = resources.getString(R.string.pmsma_lmp), + required = false + ) + private val lastMenstrualPeriod = FormElement( + id = 32, + inputType = TEXT_VIEW, + title = resources.getString(R.string.pmsma_last_mestrual_period), + min = 0L, + max = System.currentTimeMillis(), + required = false + ) + private val expectedDateOfDelivery = FormElement( + id = 33, + inputType = TEXT_VIEW, + title = resources.getString(R.string.pmsma_expected_delivery_date), + required = false + ) + private val highRiskFactors = FormElement( + id = 34, + inputType = HEADLINE, + title = resources.getString(R.string.pmsma_high_risk_factors), + required = false + ) + private val highriskSymbols = FormElement( + id = 35, + inputType = RADIO, + title = resources.getString(R.string.pmsma_high_risk_symbols), + entries = resources.getStringArray(R.array.pmsma_confirmation_array), + hasDependants = true, + required = false + ) + // private val highRiskReason = FormElement( + // id = 36, + // inputType = EDIT_TEXT, + // title = resources.getString(R.string.pmsma_high_risk_reason), + // required = false + // ) + private val highRiskPregnant = FormElement( + id = 37, + inputType = RADIO, + title = resources.getString(R.string.pmsma_high_risk_pregnant), + entries = resources.getStringArray(R.array.pmsma_confirmation_array), + required = false + ) + private val highRiskPregnancyReferred = FormElement( + id = 38, + inputType = RADIO, + title = resources.getString(R.string.pmsma_high_risk_pregnancy_referred), + entries = resources.getStringArray(R.array.pmsma_confirmation_array), + required = false + ) + private val advice = FormElement( + id = 39, + inputType = HEADLINE, + title = resources.getString(R.string.pmsma_advice), + required = false + ) + private val birthPrepAndNutritionAndFamilyPlanning = FormElement( + id = 40, + inputType = RADIO, + title = resources.getString(R.string.pmsma_birth_complications_and_planning), + entries = resources.getStringArray(R.array.pmsma_confirmation_array), + required = false + ) + private val medicalOfficerSign = FormElement( + id = 41, + inputType = EDIT_TEXT, + title = resources.getString(R.string.pmsma_medical_officer_sign), + required = false + ) + + + suspend fun setUpFirstPage( + household: HouseholdCache, + ben: BenRegCache, + pwr: PregnantWomanRegistrationCache, + lastAnc: PregnantWomanAncCache?, + pmsma: PMSMACache?, + visitNumber:Int, + countOfANC:Int, + lastPmsmaVisit:PMSMACache? + ) { + val minDate = lastPmsmaVisit?.visitDate?.let { lastVisit -> + lastVisit + TimeUnit.DAYS.toMillis(7) + } ?: pwr.lmpDate.toStartOfDay() + + val maxFromLMP = (pwr.lmpDate + TimeUnit.DAYS.toMillis(42 * 7)).toStartOfDay() + val today = Calendar.getInstance().apply { + set(Calendar.HOUR_OF_DAY, 0) + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + }.timeInMillis + val finalMaxDate = minOf(today, maxFromLMP) + val clampedMinDate = if (minDate > finalMaxDate) finalMaxDate else minDate + pmsmaVisitDate.min = minDate + pmsmaVisitDate.max = today + + hiv.value=pwr.hivTestResult + vdrl.value=pwr.vdrlRprTestResult + hbsc.value=pwr.hbsAgTestResult + malaria.value = resources.getStringArray(R.array.pmsma_malaria_array)[0] + numANC.value=countOfANC.toString() +// hivTestDuringANC.value=pwr.hivTestResult + + pmsmaVisit.value=visitNumber.toString() + mctsNumberOrRchNumber.value = pwr.rchId?.toString() + address.value = getAddress(household) + mobileNumber.value = ben.contactNumber.toString() + husbandName.value = ben.genDetails?.spouseName + mctsNumberOrRchNumber.value = ben.rchId + lastAnc?.let { + "${(it.ancDate - pwr.lmpDate) / 7} Weeks" } + lastMenstrualPeriod.value = getDateFromLong(pwr.lmpDate) + expectedDateOfDelivery.value = getDateFromLong(getEddFromLmp(pwr.lmpDate)) + abdominalCheckUp.value = "${ + (TimeUnit.MILLISECONDS.toDays( + Calendar.getInstance().setToStartOfTheDay() - pwr.lmpDate + ) / 7) + } Weeks" + + pmsma?.let { data -> + + val updatedList = firstPage.toMutableList().apply { + if (data.highriskSymbols == true) { + indexOf(highriskSymbols).takeIf { it != -1 }?.let { idx -> + add(idx + 1, highRiskReason) + } + } + if (data.highRiskReason == highRiskReason.entries?.lastOrNull()) { + indexOf(highRiskReason).takeIf { it != -1 }?.let { idx -> + add(idx + 1, anyOtherHighRiskCondition) + } + } + } + setUpPage(updatedList) + setExistingValues(data) + + } ?: run { + setUpPage(firstPage) + } + + } + + + private val firstPage by lazy { + listOf( + pmsmaVisitDate, + pmsmaVisit, + detailsOfPW, + mctsNumberOrRchNumber, + haveMCPCard, + husbandName, + address, + mobileNumber, + numANC, + motherStatus, + weight, + bp, + abdominalCheckUp, + fetalStatus, + fetalHRPM, + twinPregnancy, + investigations, + urineAlbumin, + haemoglobinAndBloodGroup, + hiv, + vdrl, + hbsc, + malaria, + hivTestDuringANC, + swollenCondtion, + bloodSugarTest, + ultraSound, + interventionDetails, + ironFolicAcid, + calciumSupplementation, + tetanusToxoid, + lmp, + lastMenstrualPeriod, + expectedDateOfDelivery, + highRiskFactors, + highriskSymbols, + highRiskPregnant, + highRiskPregnancyReferred, + advice, + birthPrepAndNutritionAndFamilyPlanning, + medicalOfficerSign, - highriskSymbols.id -> { - triggerDependants( - source = highriskSymbols, - passedIndex = index, - triggerIndex = 0, - target = highRiskReason ) + } + + + override fun mapValues(cacheModel: FormDataModel, pageNumber: Int) { + (cacheModel as PMSMACache).let { pmsmaCache -> + pmsmaCache.mctsNumberOrRchNumber = mctsNumberOrRchNumber.value + pmsmaCache.haveMCPCard = haveMCPCard.value == haveMCPCard.entries!![0] + pmsmaCache.husbandName = husbandName.value + pmsmaCache.visitDate= getLongFromDate(pmsmaVisitDate.value) + pmsmaCache.visitNumber = pmsmaVisit.value!!.toInt() + pmsmaCache.anyOtherHighRiskCondition=anyOtherHighRiskCondition.value + pmsmaCache.address = address.value + pmsmaCache.mobileNumber = mobileNumber.value?.toLong() ?: 0L + pmsmaCache.numANC = numANC.value?.toInt() ?: 0 + pmsmaCache.weight = weight.value?.toInt() ?: 0 + pmsmaCache.systolicBloodPressure = + bp.value?.takeIf { it.isNotEmpty() }?.substringBefore("/") + pmsmaCache.bloodPressure = bp.value?.takeIf { it.isNotEmpty() }?.substringAfter("/") + pmsmaCache.abdominalCheckUp = abdominalCheckUp.value + pmsmaCache.fetalHRPM = fetalHRPM.value?.toInt() ?: 0 + pmsmaCache.twinPregnancy = twinPregnancy.value == twinPregnancy.entries!![0] + pmsmaCache.urineAlbumin = getEnglishValueInArray(R.array.pmsma_confirmation_array, urineAlbumin.value) + pmsmaCache.haemoglobinAndBloodGroup = haemoglobinAndBloodGroup.value + pmsmaCache.hiv = hiv.value + pmsmaCache.vdrl = vdrl.value + pmsmaCache.hbsc = hbsc.value + pmsmaCache.malaria = getEnglishValueInArray(R.array.pmsma_malaria_array, malaria.value) + pmsmaCache.hivTestDuringANC = hivTestDuringANC.value == hivTestDuringANC.entries!![0] + pmsmaCache.swollenCondtion = swollenCondtion.value == swollenCondtion.entries!![0] + pmsmaCache.bloodSugarTest = bloodSugarTest.value == bloodSugarTest.entries!![0] + pmsmaCache.ultraSound = ultraSound.value == ultraSound.entries!![0] + pmsmaCache.ironFolicAcid = ironFolicAcid.value == ironFolicAcid.entries!![0] + pmsmaCache.calciumSupplementation = calciumSupplementation.value == calciumSupplementation.entries!![0] + pmsmaCache.tetanusToxoid = getEnglishValueInArray(R.array.pmsma_tetanus_toxoid_array, tetanusToxoid.value) + pmsmaCache.lastMenstrualPeriod = + lastMenstrualPeriod.value?.let { getLongFromDate(it) } ?: 0L + pmsmaCache.expectedDateOfDelivery = getLongFromDate(expectedDateOfDelivery.value!!) + pmsmaCache.highriskSymbols = highriskSymbols.value == highriskSymbols.entries!![0] + pmsmaCache.highRiskReason = getEnglishValueInArray(R.array.select_high_risk_condition, highRiskReason.value) + pmsmaCache.highRiskPregnant = highRiskPregnant.value == highRiskPregnant.entries!![0] + pmsmaCache.highRiskPregnancyReferred = highRiskPregnancyReferred.value == highRiskPregnancyReferred.entries!![0] + pmsmaCache.birthPrepAndNutritionAndFamilyPlanning = + birthPrepAndNutritionAndFamilyPlanning.value == birthPrepAndNutritionAndFamilyPlanning.entries!![0] + pmsmaCache.medicalOfficerSign = medicalOfficerSign.value } - mobileNumber.id -> validateMobileNumberOnEditText(mobileNumber) - husbandName.id -> validateAllCapsOrSpaceOnEditText(husbandName) - bp.id -> validateForBp(bp) - weight.id -> validateIntMinMax(weight) + } + + fun setExistingValues(pmsma: PMSMACache) { + mctsNumberOrRchNumber.value = pmsma.mctsNumberOrRchNumber + haveMCPCard.value = if (pmsma.haveMCPCard == true) haveMCPCard.entries!![0] else haveMCPCard.entries!![1] + husbandName.value = pmsma.husbandName + pmsmaVisitDate.value = getDateFromLong(pmsma.visitDate!!) + pmsmaVisit.value = pmsma.visitNumber.toString() + anyOtherHighRiskCondition.value = pmsma.anyOtherHighRiskCondition + address.value = pmsma.address?.let { if (it.length > 100) it.substring(0, 100) else it } + mobileNumber.value = pmsma.mobileNumber.toString() + numANC.value = pmsma.numANC.toString() + weight.value = pmsma.weight.toString() + bp.value = + if (pmsma.systolicBloodPressure == null || pmsma.bloodPressure == null) null else "${pmsma.systolicBloodPressure}/${pmsma.bloodPressure}" + abdominalCheckUp.value = pmsma.abdominalCheckUp + fetalHRPM.value = pmsma.fetalHRPM.toString() + twinPregnancy.value = if (pmsma.twinPregnancy == true) twinPregnancy.entries!![0] else twinPregnancy.entries!![1] + urineAlbumin.value = getLocalValueInArray(R.array.pmsma_confirmation_array, pmsma.urineAlbumin) + haemoglobinAndBloodGroup.value = pmsma.haemoglobinAndBloodGroup + hiv.value = pmsma.hiv + vdrl.value = pmsma.vdrl + hbsc.value = pmsma.hbsc + malaria.value = getLocalValueInArray(R.array.pmsma_malaria_array, pmsma.malaria) + hivTestDuringANC.value = if (pmsma.hivTestDuringANC == true) hivTestDuringANC.entries!![0] else hivTestDuringANC.entries!![1] + swollenCondtion.value = if (pmsma.swollenCondtion == true) swollenCondtion.entries!![0] else swollenCondtion.entries!![1] + bloodSugarTest.value = if (pmsma.bloodSugarTest == true) bloodSugarTest.entries!![0] else bloodSugarTest.entries!![1] + ultraSound.value = if (pmsma.ultraSound == true) ultraSound.entries!![0] else ultraSound.entries!![1] + ironFolicAcid.value = if (pmsma.ironFolicAcid == true) ironFolicAcid.entries!![0] else ironFolicAcid.entries!![1] + calciumSupplementation.value = + if (pmsma.calciumSupplementation == true) calciumSupplementation.entries!![0] else calciumSupplementation.entries!![1] + tetanusToxoid.value = getLocalValueInArray(R.array.pmsma_tetanus_toxoid_array, pmsma.tetanusToxoid) + lastMenstrualPeriod.value = pmsma.lastMenstrualPeriod.let { getDateFromLong(it) } + expectedDateOfDelivery.value = pmsma.expectedDateOfDelivery.let { getDateFromLong(it) } + highriskSymbols.value = if (pmsma.highriskSymbols == true) highriskSymbols.entries!![0] else highriskSymbols.entries!![1] + highRiskReason.value = getLocalValueInArray(R.array.select_high_risk_condition, pmsma.highRiskReason) + highRiskPregnant.value = if (pmsma.highRiskPregnant == true) highRiskPregnant.entries!![0] else highRiskPregnant.entries!![1] + highRiskPregnancyReferred.value = + if (pmsma.highRiskPregnancyReferred == true) highRiskPregnancyReferred.entries!![0] else highRiskPregnancyReferred.entries!![1] + birthPrepAndNutritionAndFamilyPlanning.value = + if (pmsma.birthPrepAndNutritionAndFamilyPlanning == true) birthPrepAndNutritionAndFamilyPlanning.entries!![0] else birthPrepAndNutritionAndFamilyPlanning.entries!![1] + medicalOfficerSign.value = pmsma.medicalOfficerSign + } + + + override suspend fun handleListOnValueChanged(formId: Int, index: Int): Int { + return when (formId) { + haveMCPCard.id -> { + triggerDependants( + source = haveMCPCard, + passedIndex = index, + triggerIndex = 1, + target = givenMCPCard + ) + } + highriskSymbols.id -> { + if(highriskSymbols.value == highriskSymbols.entries!![0]) + { + viewModel.onHighRiskSelected(true) - else -> -1 + } + triggerDependants( + source = highriskSymbols, + passedIndex = index, + triggerIndex = 0, + target = highRiskReason, + targetSideEffect = listOf(anyOtherHighRiskCondition) + ) + + } + + highRiskReason.id -> { + val index = highRiskReason.entries?.indexOf(highRiskReason.value).takeIf { it!! >= 0 } + ?: return -1 + val triggerIndex = 7 + return triggerDependants( + source = highRiskReason, + passedIndex = index, + triggerIndex = triggerIndex, + target = anyOtherHighRiskCondition + ) + } + + mobileNumber.id -> validateMobileNumberOnEditText(mobileNumber) + husbandName.id -> { + if (currentLanguage.toString() == english) { + // validateAllCapsOrSpaceOnEditText(husbandName) + validateAllCapsOrSpaceOnEditTextWithHindiEnabled(husbandName) + } else -1 + } + + bp.id -> validateForBp(bp) + weight.id -> validateIntMinMax(weight) + + + else -> -1 + } } - } - private fun getAddress(household: HouseholdCache): String { - val houseNo = household.family?.houseNo - val wardNo = household.family?.wardNo - val name = household.family?.wardName - val mohalla = household.family?.mohallaName - val district = household.locationRecord.district.name - val city = household.locationRecord.village.name - val state = household.locationRecord.state.name + private fun getAddress(household: HouseholdCache): String { + val houseNo = household.family?.houseNo + val wardNo = household.family?.wardNo + val name = household.family?.wardName + val mohalla = household.family?.mohallaName + val district = household.locationRecord.district.name + val city = household.locationRecord.village.name + val state = household.locationRecord.state.name - var address = "$houseNo, $wardNo, $name, $mohalla, $city, $district, $state" - address = address.replace(", ,", ",") - address = address.replace(",,", ",") - address = address.replace(" ,", "") - address = address.replace("null, ", "") - address = address.replace(", null", "") + var address = "$houseNo, $wardNo, $name, $mohalla, $city, $district, $state" + address = address.replace(", ,", ",") + address = address.replace(",,", ",") + address = address.replace(" ,", "") + address = address.replace("null, ", "") + address = address.replace(", null", "") - return address - } + return address + } + fun Long.toStartOfDay(): Long { + val cal = Calendar.getInstance() + cal.timeInMillis = this + cal.set(Calendar.HOUR_OF_DAY, 0) + cal.set(Calendar.MINUTE, 0) + cal.set(Calendar.SECOND, 0) + cal.set(Calendar.MILLISECOND, 0) + return cal.timeInMillis + } + fun Calendar.setToStartOfTheDay(): Long { + this.set(Calendar.HOUR_OF_DAY, 0) + this.set(Calendar.MINUTE, 0) + this.set(Calendar.SECOND, 0) + this.set(Calendar.MILLISECOND, 0) + return this.timeInMillis + } -} \ No newline at end of file + } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/PncFormDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/PncFormDataset.kt index 1b05c2304..a0a80ec5f 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/configuration/PncFormDataset.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/PncFormDataset.kt @@ -1,47 +1,78 @@ package org.piramalswasthya.sakhi.configuration import android.content.Context +import android.net.Uri +import org.piramalswasthya.sakhi.BuildConfig import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.configuration.dynamicDataSet.FormField import org.piramalswasthya.sakhi.helpers.Languages import org.piramalswasthya.sakhi.helpers.setToStartOfTheDay import org.piramalswasthya.sakhi.model.BenRegCache import org.piramalswasthya.sakhi.model.DeliveryOutcomeCache import org.piramalswasthya.sakhi.model.FormElement import org.piramalswasthya.sakhi.model.InputType +import org.piramalswasthya.sakhi.model.InputType.EDIT_TEXT import org.piramalswasthya.sakhi.model.PNCVisitCache import org.piramalswasthya.sakhi.model.getDateStrFromLong +import org.piramalswasthya.sakhi.ui.home_activity.maternal_health.pnc.form.PncFormViewModel +import timber.log.Timber import java.util.Calendar import java.util.concurrent.TimeUnit class PncFormDataset( - context: Context, currentLanguage: Languages + context: Context, currentLanguage: Languages, private val viewModel: PncFormViewModel? = null ) : Dataset(context, currentLanguage) { private var visit: Int = 0 private var dateOfDelivery: Long = 0L - private val pncPeriod = FormElement( + companion object{ + fun getMinDeliveryDate(): Long { + val cal = Calendar.getInstance() + cal.add(Calendar.YEAR, -1) + return cal.timeInMillis + } + } + + + private val deliveryDate = FormElement( id = 1, + inputType = InputType.DATE_PICKER, + title = resources.getString(R.string.date_of_delivery), + arrayId = -1, + required = true, + max = System.currentTimeMillis(), + min = getMinDeliveryDate(), + isEnabled = true + ) + + private val pncDays = listOf(1, 3, 7, 14, 21, 28, 42) + private val pncDayLabels = resources.getStringArray(R.array.pnc_period_array) + private val pncDayLabelMap: Map = pncDays.zip(pncDayLabels).toMap() + private val pncLabelDayMap: Map = pncDayLabels.zip(pncDays).toMap() + + private val pncPeriod = FormElement( + id = 2, inputType = InputType.DROPDOWN, title = resources.getString(R.string.pnc_period), -// entries = resources.getStringArray(R.array.pnc_period_array), arrayId = -1, required = true, hasDependants = false ) private val visitDate = FormElement( - id = 2, - inputType = InputType.TEXT_VIEW, + id = 3, + inputType = InputType.DATE_PICKER, title = resources.getString(R.string.pnc_visit_date), arrayId = -1, required = true, hasDependants = false ) + private val ifaTabsGiven = FormElement( - id = 3, - inputType = InputType.EDIT_TEXT, + id = 4, + inputType = InputType.NUMBER_PICKER, title = resources.getString(R.string.pnc_ifa_tabs_given), required = false, hasDependants = false, @@ -49,10 +80,11 @@ class PncFormDataset( etMaxLength = 3, max = 400, min = 0, + value = "0" ) private val anyContraceptionMethod = FormElement( - id = 4, + id = 5, inputType = InputType.RADIO, title = resources.getString(R.string.pnc_any_contraception_method), entries = resources.getStringArray(R.array.pnc_confirmation_array), @@ -61,16 +93,25 @@ class PncFormDataset( ) private val contraceptionMethod = FormElement( - id = 5, + id = 6, inputType = InputType.DROPDOWN, title = resources.getString(R.string.pnc_contraception_method), - entries = resources.getStringArray(R.array.pnc_contraception_method_array), + entries = resources.getStringArray(R.array.pnc_contraception_method_array).let { entries -> + val englishEntries = englishResources.getStringArray(R.array.pnc_contraception_method_array) + if (BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true)) { + val miniLapIndex = englishEntries.indexOfFirst { it.equals("MiniLap", ignoreCase = true) } + entries.filterIndexed { index, _ -> index != miniLapIndex }.toTypedArray() + } else { + val femSterIndex = englishEntries.indexOfFirst { it.equals("FEMALE STERILIZATION", ignoreCase = true) } + entries.filterIndexed { index, _ -> index != femSterIndex }.toTypedArray() + } + }, required = false, hasDependants = true ) private val otherPpcMethod = FormElement( - id = 6, + id = 7, inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.pnc_other_ppc_method), required = true, @@ -78,16 +119,16 @@ class PncFormDataset( ) private val motherDangerSign = FormElement( - id = 7, + id = 8, inputType = InputType.DROPDOWN, title = resources.getString(R.string.pnc_mother_danger_sign), entries = resources.getStringArray(R.array.pnc_mother_danger_sign_array), - required = false, + required = true, hasDependants = true ) private val otherDangerSign = FormElement( - id = 8, + id = 9, inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.pnc_other_danger_sign), required = true, @@ -95,7 +136,7 @@ class PncFormDataset( ) private val referralFacility = FormElement( - id = 9, + id = 10, inputType = InputType.DROPDOWN, title = resources.getString(R.string.pnc_referral_facility), entries = resources.getStringArray(R.array.pnc_referral_facility_array), @@ -104,7 +145,7 @@ class PncFormDataset( ) private val motherDeath = FormElement( - id = 10, + id = 11, inputType = InputType.RADIO, title = resources.getString(R.string.pnc_mother_death), entries = resources.getStringArray(R.array.pnc_confirmation_array), @@ -113,7 +154,7 @@ class PncFormDataset( ) private val deathDate = FormElement( - id = 11, + id = 12, inputType = InputType.DATE_PICKER, title = resources.getString(R.string.pnc_death_date), arrayId = -1, @@ -123,7 +164,7 @@ class PncFormDataset( ) private val causeOfDeath = FormElement( - id = 12, + id = 13, inputType = InputType.DROPDOWN, title = resources.getString(R.string.pnc_death_cause), entries = resources.getStringArray(R.array.pnc_death_cause_array), @@ -132,7 +173,7 @@ class PncFormDataset( ) private val otherDeathCause = FormElement( - id = 13, + id = 14, inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.pnc_other_death_cause), required = true, @@ -140,51 +181,138 @@ class PncFormDataset( ) private val placeOfDeath = FormElement( - id = 14, + id = 15, inputType = InputType.DROPDOWN, title = resources.getString(R.string.pnc_death_place), - entries = resources.getStringArray(R.array.pnc_death_place_array), + entries = resources.getStringArray(R.array.death_place_array), required = true, hasDependants = false, ) private val remarks = FormElement( - id = 15, + id = 16, inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.pnc_remarks), required = false, hasDependants = false ) + private val otherPlaceOfDeath = FormElement( + id = 55, + inputType = EDIT_TEXT, + title = resources.getString(R.string.other_place_of_death), + required = true, + hasDependants = true, + ) + + private val dateOfSterilisation = FormElement( + id = 56, + inputType = InputType.DATE_PICKER, + title = resources.getString(R.string.date_of_sterilisation), + arrayId = -1, + max = System.currentTimeMillis(), + min = getMinDeliveryDate(), + required = true, + hasDependants = false + + ) + private val anySignOfDanger = FormElement( + id = 57, + inputType = InputType.RADIO, + title = resources.getString(R.string.any_danger_signs) + + resources.getString(R.string.any_high_risk_identified), + entries = resources.getStringArray(R.array.pnc_confirmation_array), + required = false, + hasDependants = true + ) + + private val deliveryDischargeSummary1 = FormElement( + id = 58, + inputType = InputType.FILE_UPLOAD, + title = resources.getString(R.string.delivery_discharge_summary_1), + required = false + + ) - suspend fun setUpPage( + private val deliveryDischargeSummary2 = FormElement( + id =59, + inputType = InputType.FILE_UPLOAD, + title = resources.getString(R.string.delivery_discharge_summary_2), + required = false + ) + + private val deliveryDischargeSummary3 = FormElement( + id =60, + inputType = InputType.FILE_UPLOAD, + title = resources.getString(R.string.delivery_discharge_summary_3), + required = false + ) + + private val deliveryDischargeSummary4 = FormElement( + id =61, + inputType = InputType.FILE_UPLOAD, + title = resources.getString(R.string.delivery_discharge_summary_4), + required = false + ) + + + private val sterilisation : Array by lazy { + resources.getStringArray(R.array.sterilization_methods_array) + } + + suspend fun + setUpPage( visitNumber: Int, ben: BenRegCache, - deliveryOutcomeCache: DeliveryOutcomeCache, + deliveryOutcomeCache: DeliveryOutcomeCache?=null, previousPnc: PNCVisitCache?, - saved: PNCVisitCache? + saved: PNCVisitCache?, + hasPreviousPermanentSterilization: Boolean = false ) { val list = mutableListOf( + deliveryDate, pncPeriod, visitDate, + motherDeath, ifaTabsGiven, anyContraceptionMethod, - motherDangerSign, + anySignOfDanger, referralFacility, - motherDeath, - remarks + remarks, + deliveryDischargeSummary1, + deliveryDischargeSummary2, + deliveryDischargeSummary3, + deliveryDischargeSummary4, + + ) - dateOfDelivery = deliveryOutcomeCache.dateOfDelivery!! - deathDate.min = dateOfDelivery + + if (BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true)) { + list.remove(deliveryDischargeSummary1) + list.remove(deliveryDischargeSummary2) + list.remove(deliveryDischargeSummary3) + list.remove(deliveryDischargeSummary4) + } + + dateOfDelivery = deliveryOutcomeCache?.dateOfDelivery?:0L + if (dateOfDelivery!=0L){ + deathDate.min = dateOfDelivery + deliveryDate.isEnabled = false + } + deathDate.max = System.currentTimeMillis() + anySignOfDanger.value = anySignOfDanger.entries!!.last() motherDeath.value = motherDeath.entries!!.last() val daysSinceDeliveryMillis = Calendar.getInstance() - .setToStartOfTheDay().timeInMillis - deliveryOutcomeCache.dateOfDelivery!!.let { + .setToStartOfTheDay().timeInMillis - deliveryOutcomeCache?.dateOfDelivery.let { val cal = Calendar.getInstance() - cal.timeInMillis = it + if (it != null) { + cal.timeInMillis = it + } cal.setToStartOfTheDay() cal.timeInMillis } val daysSinceDelivery = TimeUnit.MILLISECONDS.toDays(daysSinceDeliveryMillis) + deliveryDate.value = getDateFromLong(dateOfDelivery) pncPeriod.entries = listOf( 1, @@ -196,10 +324,46 @@ class PncFormDataset( 42 ).filter { if (daysSinceDelivery == 0L) it <= 1 else it <= daysSinceDelivery } .filter { it > (previousPnc?.pncPeriod ?: 0) } - .map { "Day $it" }.toTypedArray() + .map { pncDayLabelMap[it] ?: "Day $it" }.toTypedArray() + + if (hasPreviousPermanentSterilization) { + + anyContraceptionMethod.isEnabled = false + contraceptionMethod.isEnabled = false + dateOfSterilisation.isEnabled = false + otherPpcMethod.isEnabled = false + + + val lastSterilizationVisit = + viewModel?.getLastPermanentSterilizationVisit(ben.beneficiaryId, visitNumber) + lastSterilizationVisit?.let { sterilizationVisit -> + + anyContraceptionMethod.value = if (sterilizationVisit.anyContraceptionMethod == true) + anyContraceptionMethod.entries!!.first() else anyContraceptionMethod.entries!!.last() + + contraceptionMethod.value = sterilizationVisit.contraceptionMethod + dateOfSterilisation.value = getDateFromLong(sterilizationVisit.sterilisationDate!!) + otherPpcMethod.value = sterilizationVisit.otherPpcMethod + + if (sterilizationVisit.anyContraceptionMethod == true) { + list.add(list.indexOf(anyContraceptionMethod) + 1, contraceptionMethod) + + if (sterilizationVisit.contraceptionMethod in sterilisation) { + list.add(list.indexOf(contraceptionMethod) + 1, dateOfSterilisation) + } + + if (sterilizationVisit.contraceptionMethod == contraceptionMethod.entries!!.last()) { + list.add(list.indexOf(contraceptionMethod) + 1, otherPpcMethod) + } + } + + + }} + + saved?.let { - pncPeriod.value = "Day ${it.pncPeriod}" + pncPeriod.value = pncDayLabelMap[it.pncPeriod] ?: "Day ${it.pncPeriod}" visitDate.value = getDateFromLong(it.pncDate) ifaTabsGiven.value = it.ifaTabsGiven?.toString() anyContraceptionMethod.value = it.anyContraceptionMethod?.let { @@ -211,24 +375,45 @@ class PncFormDataset( if (it.anyContraceptionMethod == true) { list.add(list.indexOf(anyContraceptionMethod) + 1, contraceptionMethod) } - contraceptionMethod.value = it.contraceptionMethod - if (it.contraceptionMethod == contraceptionMethod.entries!!.last()) { + contraceptionMethod.value = getLocalValueInArray(R.array.pnc_contraception_method_array, it.contraceptionMethod) + it.sterilisationDate.let { it2 -> + dateOfSterilisation.value = getDateFromLong(it2 ?: 0L) + } + if (contraceptionMethod.value == contraceptionMethod.entries!!.last()) { list.add(list.indexOf(contraceptionMethod) + 1, otherPpcMethod) + + } + if(contraceptionMethod.value in sterilisation ) + { + list.add(list.indexOf(contraceptionMethod) + 1, dateOfSterilisation) + } + otherPpcMethod.value = it.otherPpcMethod - motherDangerSign.value = it.motherDangerSign - if (it.motherDangerSign == motherDangerSign.entries!!.last()) { + anySignOfDanger.value = getLocalValueInArray(R.array.pnc_confirmation_array, it.anyDangerSign) + anySignOfDanger.value?.let { dangerSignValue -> + val isDangerSignYes = dangerSignValue == anySignOfDanger.entries!!.first() + referralFacility.required = isDangerSignYes + } + motherDangerSign.value = getLocalValueInArray(R.array.pnc_mother_danger_sign_array, it.motherDangerSign) + if (motherDangerSign.value == motherDangerSign.entries!!.last()) { list.add(list.indexOf(motherDangerSign) + 1, otherDangerSign) } otherDangerSign.value = it.otherDangerSign - referralFacility.value = it.referralFacility + referralFacility.value = getLocalValueInArray(R.array.pnc_referral_facility_array, it.referralFacility) motherDeath.value = if (it.motherDeath) motherDeath.entries!!.first() else motherDeath.entries!!.last() if (it.motherDeath) { deathDate.value = getDateStrFromLong(it.deathDate) - causeOfDeath.value = it.causeOfDeath + causeOfDeath.value = getLocalValueInArray(R.array.pnc_death_cause_array, it.causeOfDeath) otherDeathCause.value = it.otherDeathCause - placeOfDeath.value = it.placeOfDeath + placeOfDeath.value = getLocalValueInArray(R.array.death_place_array, it.placeOfDeath) + otherPlaceOfDeath.value = it.otherPlaceOfDeath + placeOfDeath.entries?.indexOf(placeOfDeath.value)?.takeIf { it >= 0 }?.let { index -> + if (index == 8) { + list.add(list.indexOf(placeOfDeath) + 1, otherPlaceOfDeath) + } + } list.addAll( list.indexOf(motherDeath) + 1, listOf(deathDate, causeOfDeath, placeOfDeath) @@ -237,6 +422,11 @@ class PncFormDataset( list.add(list.indexOf(causeOfDeath) + 1, otherDeathCause) } remarks.value = it.remarks + deliveryDischargeSummary1.value = it.deliveryDischargeSummary1 + deliveryDischargeSummary2.value = it.deliveryDischargeSummary2 + deliveryDischargeSummary3.value = it.deliveryDischargeSummary3 + deliveryDischargeSummary4.value = it.deliveryDischargeSummary4 + } // pncPeriod.entries = pncPeriod.entries!!. @@ -287,10 +477,15 @@ class PncFormDataset( override suspend fun handleListOnValueChanged(formId: Int, index: Int): Int { return when (formId) { pncPeriod.id -> { - visitDate.inputType = InputType.DATE_PICKER + dateOfDelivery = getLongFromDate(deliveryDate.value) + visitDate.value = null + visitDate.inputType = InputType.DATE_PICKER + + + val today = Calendar.getInstance().setToStartOfTheDay().timeInMillis - when (val visitNumber = pncPeriod.value!!.substring(4).toInt()) { + when (val visitNumber = pncLabelDayMap[pncPeriod.value] ?: pncPeriod.value!!.replace("Day ", "").toInt()) { 1 -> { visitDate.min = minOf(today, dateOfDelivery) visitDate.max = minOf( @@ -397,21 +592,120 @@ class PncFormDataset( return -1 } + placeOfDeath.id -> { + val index = placeOfDeath.entries?.indexOf(placeOfDeath.value).takeIf { it!! >= 0 } ?: return -1 + val triggerIndex = 8 + return triggerDependants( + source = placeOfDeath, + passedIndex = index, + triggerIndex = triggerIndex, + target = otherPlaceOfDeath + ) + } + ifaTabsGiven.id -> validateIntMinMax(ifaTabsGiven) + anyContraceptionMethod.id -> triggerDependants( source = anyContraceptionMethod, passedIndex = index, triggerIndex = 0, target = contraceptionMethod, - targetSideEffect = listOf(otherPpcMethod) + targetSideEffect = listOf( + otherPpcMethod, + dateOfSterilisation + ) ) - contraceptionMethod.id -> triggerDependants( - source = contraceptionMethod, - passedIndex = index, - triggerIndex = contraceptionMethod.entries!!.lastIndex, - target = otherPpcMethod, - ) + + contraceptionMethod.id -> { + val selected = contraceptionMethod.entries?.getOrNull(index)?.trim() ?: "" + Timber.d("Selected contraception: '$selected' (index=$index)") + + val requiresIncentiveAlert = selected.isNotEmpty() && + !selected.equals("CONDOM", ignoreCase = true) && + !selected.equals(contraceptionMethod.entries!!.last().trim(), ignoreCase = true) + + if (requiresIncentiveAlert) { + if (!BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true)) { + viewModel?.triggerIncentiveAlert() + } + } + + val anyOtherValue = contraceptionMethod.entries!!.last().trim() + val result1 = if (selected.equals(anyOtherValue, ignoreCase = true)) { + Timber.d("Will add OtherPPCMethod") + triggerDependants( + source = contraceptionMethod, + passedIndex = index, + triggerIndex = contraceptionMethod.entries!!.lastIndex, + target = otherPpcMethod + ) + } else { + + triggerDependants( + source = contraceptionMethod, + passedIndex = -1, // Force removal + triggerIndex = contraceptionMethod.entries!!.lastIndex, + target = otherPpcMethod + ) + } + + + val isSterilisation = sterilisation.any { it.equals(selected, ignoreCase = true) } + Timber.d("isSterilisation = $isSterilisation") + val result2 = if (isSterilisation) { + dateOfSterilisation.min = dateOfDelivery + dateOfSterilisation.max = System.currentTimeMillis() + Timber.d("Will add DateOfSterilisation") + triggerDependants( + source = contraceptionMethod, + passedIndex = index, + triggerIndex = index, + target = dateOfSterilisation + ) + } else { + dateOfSterilisation.value = null + + triggerDependants( + source = contraceptionMethod, + passedIndex = -1, + triggerIndex = index, + target = dateOfSterilisation + ) + } + + if (result1 != -1) result1 else result2 + } + + anySignOfDanger.id -> { + val result = triggerDependants( + source = anySignOfDanger, + passedIndex = index, + triggerIndex = 0, + target = motherDangerSign, + targetSideEffect = listOf(otherDangerSign) + ) + + val oldRequiredState = referralFacility.required + + if (index == 0) { + referralFacility.required = true + + /* if (referralFacility.value.isNullOrEmpty()) { + referralFacility.errorText = "This field is required" + }*/ + } else { + referralFacility.required = false + referralFacility.errorText = null + } + val referralFacilityIndex = getIndexById(referralFacility.id) + return if (oldRequiredState != referralFacility.required && referralFacilityIndex != -1) { + referralFacilityIndex + } else { + result + }} + + motherDangerSign.id -> triggerDependants( @@ -421,7 +715,8 @@ class PncFormDataset( target = otherDangerSign ) - motherDeath.id -> { + /* motherDeath.id -> { + triggerDependants( source = motherDeath, passedIndex = index, @@ -429,8 +724,51 @@ class PncFormDataset( target = listOf(deathDate, causeOfDeath, placeOfDeath), targetSideEffect = listOf(otherDeathCause) ) + + }*/ + + motherDeath.id -> { + if (index == 0) { + triggerDependants( + source = motherDeath, + removeItems = listOf( + ifaTabsGiven, + anyContraceptionMethod, + anySignOfDanger, + referralFacility, + remarks + + ), + addItems = listOf( + deathDate, + causeOfDeath, + placeOfDeath, + otherDeathCause + ) + ) + } else { + triggerDependants( + source = motherDeath, + removeItems = listOf( + deathDate, + causeOfDeath, + placeOfDeath, + otherDeathCause + ), + addItems = listOf( + ifaTabsGiven, + anyContraceptionMethod, + anySignOfDanger, + referralFacility, + remarks + + ) + ) + } } + + causeOfDeath.id -> { triggerDependants( source = causeOfDeath, @@ -455,25 +793,68 @@ class PncFormDataset( // return -1 // } + + override fun mapValues(cacheModel: FormDataModel, pageNumber: Int) { (cacheModel as PNCVisitCache).let { form -> - form.pncPeriod = pncPeriod.value!!.substring(4).toInt() + form.pncPeriod = pncLabelDayMap[pncPeriod.value] ?: pncPeriod.value!!.replace("Day ", "").toInt() form.pncDate = getLongFromDate(visitDate.value!!) + form.otherPlaceOfDeath=otherPlaceOfDeath.value + form.dateOfDelivery = getLongFromDate(deliveryDate.value) form.ifaTabsGiven = ifaTabsGiven.value?.takeIf { it.isNotEmpty() }?.toInt() form.anyContraceptionMethod = anyContraceptionMethod.value?.let { it == anyContraceptionMethod.entries!!.first() } - form.contraceptionMethod = contraceptionMethod.value?.takeIf { it.isNotEmpty() } + form.contraceptionMethod = getEnglishValueInArray(R.array.pnc_contraception_method_array, contraceptionMethod.value)?.takeIf { it.isNotEmpty() } form.otherPpcMethod = otherPpcMethod.value?.takeIf { it.isNotEmpty() } - form.motherDangerSign = motherDangerSign.value?.takeIf { it.isNotEmpty() } + form.motherDangerSign = getEnglishValueInArray(R.array.pnc_mother_danger_sign_array, motherDangerSign.value)?.takeIf { it.isNotEmpty() } form.otherDangerSign = otherDangerSign.value?.takeIf { it.isNotEmpty() } - form.referralFacility = referralFacility.value?.takeIf { it.isNotEmpty() } + form.referralFacility = getEnglishValueInArray(R.array.pnc_referral_facility_array, referralFacility.value)?.takeIf { it.isNotEmpty() } form.motherDeath = motherDeath.value?.let { it == motherDeath.entries!!.first() } ?: false form.deathDate = deathDate.value?.let { getLongFromDate(it) } - form.causeOfDeath = causeOfDeath.value?.takeIf { it.isNotEmpty() } + form.causeOfDeath = getEnglishValueInArray(R.array.pnc_death_cause_array, causeOfDeath.value)?.takeIf { it.isNotEmpty() } form.otherDeathCause = otherDeathCause.value?.takeIf { it.isNotEmpty() } - form.placeOfDeath = placeOfDeath.value?.takeIf { it.isNotEmpty() } + form.placeOfDeath = getEnglishValueInArray(R.array.death_place_array, placeOfDeath.value)?.takeIf { it.isNotEmpty() } form.remarks = remarks.value?.takeIf { it.isNotEmpty() } + form.deliveryDischargeSummary1 = deliveryDischargeSummary1.value?.takeIf { it.isNotEmpty() } + form.deliveryDischargeSummary2 = deliveryDischargeSummary2.value?.takeIf { it.isNotEmpty() } + form.deliveryDischargeSummary3 = deliveryDischargeSummary3.value?.takeIf { it.isNotEmpty() } + form.deliveryDischargeSummary4 = deliveryDischargeSummary4.value?.takeIf { it.isNotEmpty() } + + + } + } + + fun getIndexDeliveryDischargeSummary1 () = getIndexById(deliveryDischargeSummary1.id) + fun getIndexDeliveryDischargeSummary2 () = getIndexById(deliveryDischargeSummary2.id) + fun getIndexDeliveryDischargeSummary3 () = getIndexById(deliveryDischargeSummary3.id) + fun getIndexDeliveryDischargeSummary4 () = getIndexById(deliveryDischargeSummary4.id) + + + fun setImageUriToFormElement(lastImageFormId: Int, dpUri: Uri) { + when (lastImageFormId) { + 58 -> { + deliveryDischargeSummary1.value = dpUri.toString() + deliveryDischargeSummary1.errorText = null + } + 59 -> { + deliveryDischargeSummary2.value = dpUri.toString() + deliveryDischargeSummary2.errorText = null + } + 60 -> { + deliveryDischargeSummary3.value = dpUri.toString() + deliveryDischargeSummary3.errorText = null + } + 61 -> { + deliveryDischargeSummary4.value = dpUri.toString() + deliveryDischargeSummary4.errorText = null + } + } } + + + + + } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/PregnantWomanAncAbortionDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/PregnantWomanAncAbortionDataset.kt new file mode 100644 index 000000000..fbee50012 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/PregnantWomanAncAbortionDataset.kt @@ -0,0 +1,331 @@ +package org.piramalswasthya.sakhi.configuration + +import android.content.Context +import android.net.Uri +import android.util.Log +import org.piramalswasthya.sakhi.BuildConfig +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.helpers.Languages +import org.piramalswasthya.sakhi.helpers.getWeeksOfPregnancy +import org.piramalswasthya.sakhi.model.BenRegCache +import org.piramalswasthya.sakhi.model.FormElement +import org.piramalswasthya.sakhi.model.InputType +import org.piramalswasthya.sakhi.model.PregnantWomanAncCache +import java.util.concurrent.TimeUnit + +class PregnantWomanAncAbortionDataset( + context: Context, currentLanguage: Languages +) : Dataset(context, currentLanguage) { + + private var lastAncVisitDate: Long = 0L + private lateinit var regis: PregnantWomanAncCache + + private val visitDate = FormElement( + id = 1, + inputType = InputType.DATE_PICKER, + title = resources.getString(R.string.visit_date), + required = true, + max = System.currentTimeMillis(), + hasDependants = true, + backgroundDrawable = R.drawable.ic_bg_circular, + iconDrawableRes = R.drawable.ic_anc_date, + showDrawable = true + ) + + private val weekOfPregnancy = FormElement( + id = 2, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.weeks_of_pregnancy), + required = false, + showDrawable = true, + backgroundDrawable = R.drawable.ic_bg_circular, + iconDrawableRes = R.drawable.ic_bmi, + ) + + private val abortionDate = FormElement( + id = 3, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.abortion_date), + required = false, + showDrawable = true, + backgroundDrawable = R.drawable.ic_bg_circular, + iconDrawableRes = R.drawable.ic_bmi, + ) + + private val abortionType = FormElement( + id = 4, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.abortion_type), + required = false, + ) + + private val abortionFacility = FormElement( + id = 5, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.facility_place_of_abortion), + required = false, + ) + + private val serialNoAsPerAdmission = FormElement( + id = 6, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.serial_no_as_per_admission_evacuation_register), + arrayId = -1, + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_FLAG_SIGNED, + etMaxLength = 10, + required = false, + ) + + private val methodOfTermination = FormElement( + id = 7, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.method_of_termination), + arrayId = R.array.anc_method_of_termination, + entries = resources.getStringArray(R.array.anc_method_of_termination), + required = false, + hasDependants = true, + ) + + private val terminationDoneBy = FormElement( + id = 8, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.termination_done_by), + arrayId = R.array.anc_termination_done_by, + entries = resources.getStringArray(R.array.anc_termination_done_by), + required = false, + ) + + private val isPlanig = FormElement( + id = 25, + inputType = InputType.HEADLINE, + title = resources.getString(R.string.what_family_planning_method_has_been_chosen_after_the_abortion), + required = false, + hasDependants = false + ) + private val isPaiucd = FormElement( + id = 9, + inputType = InputType.RADIO, + title = " ", + arrayId = R.array.abortion_isplaning_array, + entries = resources.getStringArray(R.array.abortion_isplaning_array), + required = false, + orientation = 1, + hasDependants = true + ) + + private val isYesOrNo = FormElement( + id = 23, + inputType = InputType.RADIO, + title = " ", + arrayId = R.array.anc_confirmation_array, + entries = resources.getStringArray(R.array.anc_confirmation_array), + required = true, + orientation = 1, + hasDependants = true + ) + private val dateOfSterilization = FormElement( + id = 24, + inputType = InputType.DATE_PICKER, + title = resources.getString(R.string.date_of_sterilisation), + required = true, + max = System.currentTimeMillis(), + hasDependants = true, + ) + + private val remarks = FormElement( + id = 10, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.remarks), + arrayId = -1, + etMaxLength = 100, + required = false, + ) + + private val abortionDischargeSummaryImg1 = FormElement( + id = 21, + inputType = InputType.FILE_UPLOAD, + title = resources.getString(R.string.abortion_discharge_summary_1), + required = false, + ) + + private val abortionDischargeSummaryImg2 = FormElement( + id = 22, + inputType = InputType.FILE_UPLOAD, + title = resources.getString(R.string.abortion_discharge_summary_2), + required = false, + ) + + private var toggleBp = false + + fun resetBpToggle() { + toggleBp = false + } + + fun triggerBpToggle() = toggleBp + + suspend fun setUpPage( + ben: BenRegCache?, + lastAnc: PregnantWomanAncCache?, + saved: PregnantWomanAncCache? + ) { + this.regis = lastAnc!! + + val list = mutableListOf( + visitDate, + weekOfPregnancy, + abortionDate, + abortionType, + abortionFacility, + serialNoAsPerAdmission, + methodOfTermination, + terminationDoneBy, + isPlanig, + isPaiucd, + remarks, + abortionDischargeSummaryImg1, + abortionDischargeSummaryImg2, + ) + + if (BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true)) { + list.remove(abortionDischargeSummaryImg1) + list.remove(abortionDischargeSummaryImg2) + } + + + val now = System.currentTimeMillis() + val oneYearMillis = TimeUnit.DAYS.toMillis(365) + + abortionDate.min = regis.lmpDate?.plus( + TimeUnit.DAYS.toMillis(5 * 7 + 1) + ) ?: (now - oneYearMillis) + + abortionDate.max = regis.lmpDate?.let { lmp -> + minOf( + now, + lmp + TimeUnit.DAYS.toMillis(21 * 7) + ) + } ?: now + + if (saved == null) { + visitDate.min = regis.abortionDate + dateOfSterilization.min = regis.abortionDate + val woP = getWeeksOfPregnancy(regis.ancDate, regis.lmpDate) + weekOfPregnancy.value = woP.toString() + abortionType.value = getLocalValueInArray(R.array.anc_abortion_type_array, regis.abortionType) + abortionFacility.value = getLocalValueInArray(R.array.abortion_place_array, regis.abortionFacility) + abortionDate.value = regis.abortionDate?.let { getDateFromLong(it) } + } else { + isPaiucd.value = when (saved.isPaiucdId) { + 1 -> isPaiucd.entries?.getOrNull(0) + 2 -> isPaiucd.entries?.getOrNull(1) + else -> null + } + if (saved.isPaiucdId != 0) { + list.add(list.indexOf(isPaiucd) + 1, isYesOrNo) + isYesOrNo.value = getLocalValueInArray(R.array.anc_confirmation_array, if (saved.isYesOrNo == true) "Yes" else "No") + + if (saved.isPaiucdId == 2 && saved.isYesOrNo == true) { + list.add(list.indexOf(isYesOrNo) + 1, dateOfSterilization) + dateOfSterilization.value = saved.dateSterilisation?.let { getDateFromLong(it) } + } + } + + + + dateOfSterilization.min=saved.abortionDate + visitDate.min = saved.abortionDate + visitDate.value = saved.visitDate?.let { getDateFromLong(it) } + + val woP = getWeeksOfPregnancy(saved.ancDate, regis.lmpDate) + weekOfPregnancy.value = woP.toString() + abortionType.value = getLocalValueInArray(R.array.anc_abortion_type_array, saved.abortionType) + abortionFacility.value = getLocalValueInArray(R.array.abortion_place_array, saved.abortionFacility) + abortionDate.value = saved.abortionDate?.let { getDateFromLong(it) } + serialNoAsPerAdmission.value = saved.serialNo + methodOfTermination.value = getLocalValueInArray(R.array.anc_method_of_termination, saved.methodOfTermination) + terminationDoneBy.value = getLocalValueInArray(R.array.anc_termination_done_by, saved.terminationDoneBy) + remarks.value = saved.remarks + abortionDischargeSummaryImg1.value = saved.abortionImg1 + abortionDischargeSummaryImg2.value = saved.abortionImg2 + } + + setUpPage(list) + } + + override suspend fun handleListOnValueChanged(formId: Int, index: Int): Int { + + return when (formId) { + isPaiucd.id -> { + isYesOrNo.value=null + triggerDependants( + source = isPaiucd, + addItems = listOf(isYesOrNo), + removeItems = listOf(dateOfSterilization), + ) + } + + + isYesOrNo.id -> { + val isPaiucdSecondOption = (isPaiucd.entries?.indexOf(isPaiucd.value ?: "") ?: -1) == 1 + val isYes = isYesOrNo.entries?.indexOf(isYesOrNo.value ?: "") == 0 + if (isYes && isPaiucdSecondOption) { + triggerDependants( + source = isYesOrNo, + addItems = listOf(dateOfSterilization), + removeItems = emptyList() + ) + } else { + triggerDependants( + source = isYesOrNo, + addItems = emptyList(), + removeItems = listOf(dateOfSterilization) + ) + } + } + + else -> -1 + } + } + + override fun mapValues(cacheModel: FormDataModel, pageNumber: Int) { + (cacheModel as PregnantWomanAncCache).let { cache -> + cache.visitDate = visitDate.value?.let { getLongFromDate(it) } + cache.lmpDate = regis.lmpDate + cache.abortionType = abortionType.value + cache.abortionTypeId = abortionType.getPosition() + cache.abortionFacility = abortionFacility.value + cache.abortionFacilityId = abortionFacility.getPosition() + cache.abortionDate = abortionDate.value?.let { getLongFromDate(it) } + cache.serialNo = serialNoAsPerAdmission.value + cache.methodOfTermination = methodOfTermination.getEnglishStringFromPosition(methodOfTermination.getPosition()) + cache.methodOfTerminationId = methodOfTermination.getPosition().takeIf { it > 0 }?.minus(1) + cache.terminationDoneBy = terminationDoneBy.getEnglishStringFromPosition(terminationDoneBy.getPosition()) + cache.terminationDoneById = terminationDoneBy.getPosition().takeIf { it > 0 }?.minus(1) + cache.isPaiucd = isPaiucd.getEnglishStringFromPosition(isPaiucd.getPosition()) + cache.isPaiucdId = isPaiucd.getPosition() + cache.isYesOrNo = isYesOrNo.entries?.indexOf(isYesOrNo.value ?: "") == 0 + cache.remarks = remarks.value + cache.dateSterilisation = dateOfSterilization.value?.let { getLongFromDate(it) } + cache.abortionImg1 = abortionDischargeSummaryImg1.value + cache.abortionImg2 = abortionDischargeSummaryImg2.value + } + } + + fun getWeeksOfPregnancy(): Int = getIndexById(weekOfPregnancy.id) + fun getIndexOfAbortionDischarge1() = getIndexById(abortionDischargeSummaryImg1.id) + fun getIndexOfAbortionDischarge2() = getIndexById(abortionDischargeSummaryImg2.id) + + fun setImageUriToFormElement(lastImageFormId: Int, dpUri: Uri) { + when (lastImageFormId) { + 21 -> { + abortionDischargeSummaryImg1.value = dpUri.toString() + abortionDischargeSummaryImg1.errorText = null + } + + 22 -> { + abortionDischargeSummaryImg2.value = dpUri.toString() + abortionDischargeSummaryImg2.errorText = null + } + } + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/PregnantWomanAncVisitDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/PregnantWomanAncVisitDataset.kt index 08288c3a5..b93cde591 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/configuration/PregnantWomanAncVisitDataset.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/PregnantWomanAncVisitDataset.kt @@ -1,6 +1,9 @@ package org.piramalswasthya.sakhi.configuration import android.content.Context +import android.net.Uri +import android.util.Log +import org.piramalswasthya.sakhi.BuildConfig import org.piramalswasthya.sakhi.R import org.piramalswasthya.sakhi.database.room.SyncState import org.piramalswasthya.sakhi.helpers.Konstants @@ -9,6 +12,8 @@ import org.piramalswasthya.sakhi.helpers.getWeeksOfPregnancy import org.piramalswasthya.sakhi.model.BenRegCache import org.piramalswasthya.sakhi.model.FormElement import org.piramalswasthya.sakhi.model.InputType +import org.piramalswasthya.sakhi.model.InputType.DROPDOWN +import org.piramalswasthya.sakhi.model.InputType.EDIT_TEXT import org.piramalswasthya.sakhi.model.PregnantWomanAncCache import org.piramalswasthya.sakhi.model.PregnantWomanRegistrationCache import java.util.concurrent.TimeUnit @@ -25,76 +30,124 @@ class PregnantWomanAncVisitDataset( private val ancDate = FormElement( id = 1, inputType = InputType.DATE_PICKER, - title = "ANC Date", + title = resources.getString(R.string.anc_date), required = true, hasDependants = true, + backgroundDrawable = R.drawable.ic_bg_circular, + iconDrawableRes = R.drawable.ic_anc_date, + showDrawable = true ) + private val placeOfAnc = FormElement( + id = 45, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.place_of_anc), + arrayId = R.array.place_of_anc_array, + entries = resources.getStringArray(R.array.place_of_anc_array), + required = true, + hasDependants = true, + ) + private val weekOfPregnancy = FormElement( id = 2, inputType = InputType.TEXT_VIEW, - title = "Weeks of Pregnancy", + title = resources.getString(R.string.weeks_of_pregnancy), required = false, + showDrawable = true, + backgroundDrawable = R.drawable.ic_bg_circular, + iconDrawableRes = R.drawable.ic_bmi, ) private val ancVisit = FormElement( id = 3, inputType = InputType.DROPDOWN, - title = "ANC Period", + title = resources.getString(R.string.anc_period), required = true, + showDrawable = true, + backgroundDrawable = R.drawable.ic_bg_circular, + iconDrawableRes = R.drawable.ic_bmi, ) private val isAborted = FormElement( id = 4, inputType = InputType.RADIO, - title = "Abortion If Any", + title = resources.getString(R.string.abortion_if_any), arrayId = R.array.anc_confirmation_array1, entries = resources.getStringArray(R.array.anc_confirmation_array1), required = false, - hasDependants = true + hasDependants = true, ) private val abortionType = FormElement( id = 5, - inputType = InputType.RADIO, - title = "Abortion Type", + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.abortion_type), arrayId = R.array.anc_abortion_type_array, entries = resources.getStringArray(R.array.anc_abortion_type_array), required = true, - hasDependants = true + hasDependants = true, + ) + + + + private val placeOfDeath = FormElement( + id = 42, + inputType = DROPDOWN, + title = resources.getString(R.string.place_of_death), + arrayId = R.array.death_place_array, + entries = resources.getStringArray(R.array.death_place_array), + required = true, + ) + private val otherPlaceOfDeath = FormElement( + id = 43, + inputType = EDIT_TEXT, + title = resources.getString(R.string.other_place_of_death), + required = true, + hasDependants = true, ) + + private val abortionFacility = FormElement( id = 6, - inputType = InputType.RADIO, - title = "Facility", - arrayId = R.array.anc_abortion_facility_array, - entries = resources.getStringArray(R.array.anc_abortion_facility_array), + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.facility_place_of_abortion), + arrayId = R.array.abortion_place_array, + entries = resources.getStringArray(R.array.abortion_place_array), required = true, - hasDependants = true + hasDependants = true, ) private val abortionDate = FormElement( id = 7, inputType = InputType.DATE_PICKER, - title = "Abortion Date", + title = resources.getString(R.string.abortion_date), required = true, max = System.currentTimeMillis(), + showDrawable = true, + backgroundDrawable = R.drawable.ic_bg_circular, + iconDrawableRes = R.drawable.ic_bmi, ) private val weight = FormElement( id = 8, inputType = InputType.EDIT_TEXT, - title = "Weight of PW (Kg) at time Registration", + title = resources.getString(R.string.weight_of_pw_kg), arrayId = -1, etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, etMaxLength = 3, required = false, min = 30, - max = 200 + max = 200, + showDrawable = true, + backgroundDrawable = R.drawable.ic_bg_circular, + iconDrawableRes = R.drawable.ic_pw_weight, ) private val bp = FormElement( id = 9, inputType = InputType.EDIT_TEXT, - title = "BP of PW – Systolic/ Diastolic (mm Hg) ", + title = resources.getString(R.string.bp_of_pw), // etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, etMaxLength = 7, - required = false, + required = true, + showDrawable = true, + backgroundDrawable = R.drawable.ic_bg_circular, + iconDrawableRes = R.drawable.ic_blood_pressure, ) // private val bpDiastolic = FormElement( // id = 10, @@ -130,122 +183,162 @@ class PregnantWomanAncVisitDataset( private val pulseRate = FormElement( id = 11, inputType = InputType.EDIT_TEXT, - etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + title = resources.getString(R.string.pulse_rate_bpm), + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_FLAG_DECIMAL, etMaxLength = 3, - title = "Pulse Rate", + minDecimal = 40.0, + maxDecimal = 200.0, required = false, + showDrawable = true, + backgroundDrawable = R.drawable.ic_bg_circular, + iconDrawableRes = R.drawable.ic_pulse_rate, ) + private val hb = FormElement( id = 12, inputType = InputType.EDIT_TEXT, - title = "HB (gm/dl)", + title = resources.getString(R.string.hb_gm_dl), etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_FLAG_DECIMAL, etMaxLength = 4, minDecimal = 2.0, maxDecimal = 15.0, - required = false, + required = true, + showDrawable = true, + backgroundDrawable = R.drawable.ic_bg_circular, + iconDrawableRes = R.drawable.ic_blood_test, ) private val fundalHeight = FormElement( id = 13, inputType = InputType.EDIT_TEXT, - title = "Fundal Height / Size of the Uterus weeks", + title = resources.getString(R.string.fundal_height_uterus), etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, etMaxLength = 2, + min = 1, + max = 99, required = false, + showDrawable = true, + backgroundDrawable = R.drawable.ic_bg_circular, + iconDrawableRes = R.drawable.ic_fundal_height, ) private val urineAlbumin = FormElement( id = 14, inputType = InputType.RADIO, - title = "Urine Albumin", + title = resources.getString(R.string.urine_albumin), arrayId = R.array.anc_urine_albumin_array, entries = resources.getStringArray(R.array.anc_urine_albumin_array), required = false, + showDrawable = true ) private val randomBloodSugarTest = FormElement( id = 15, inputType = InputType.RADIO, - title = "Random Blood Sugar Test", + title = resources.getString(R.string.random_blood_sugar_test), arrayId = R.array.anc_random_blood_sugar_test_array, entries = resources.getStringArray(R.array.anc_random_blood_sugar_test_array), required = false, + showDrawable = true ) private val dateOfTTOrTd1 = FormElement( id = 16, inputType = InputType.DATE_PICKER, - title = "Date of Td TT (1st Dose)", + title = resources.getString(R.string.date_td_tt_1st_dose), required = false, hasDependants = true, max = System.currentTimeMillis(), + showDrawable = true, + backgroundDrawable = R.drawable.ic_bg_circular, + iconDrawableRes = R.drawable.ic_bmi, ) private val dateOfTTOrTd2 = FormElement( id = 17, inputType = InputType.DATE_PICKER, - title = "Date of Td TT (2nd Dose)", + title = resources.getString(R.string.date_td_tt_2nd_dose), required = false, max = System.currentTimeMillis(), + showDrawable = true, + backgroundDrawable = R.drawable.ic_bg_circular, + iconDrawableRes = R.drawable.ic_bmi, ) private val dateOfTTOrTdBooster = FormElement( id = 18, inputType = InputType.DATE_PICKER, - title = "Date of Td TT (Boooster Dose)", + title = resources.getString(R.string.date_td_tt_booster_dose), required = false, hasDependants = true, max = System.currentTimeMillis(), + showDrawable = true, + backgroundDrawable = R.drawable.ic_bg_circular, + iconDrawableRes = R.drawable.ic_bmi, ) private val numFolicAcidTabGiven = FormElement( id = 19, inputType = InputType.EDIT_TEXT, - title = "No. of Folic Acid Tabs given", + title = resources.getString(R.string.no_of_folic_acid_tabs), etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, etMaxLength = 2, required = false, min = 0, - max = 60 + max = 60, + showDrawable = true, + backgroundDrawable = R.drawable.ic_bg_circular, + iconDrawableRes = R.drawable.ic_tablets, ) private val numIfaAcidTabGiven = FormElement( id = 20, inputType = InputType.EDIT_TEXT, - title = "No. of IFA Tabs given", + title = resources.getString(R.string.no_of_ifa_tabs), etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, etMaxLength = 3, required = false, min = 0, - max = 400 + max = 400, + showDrawable = true, + backgroundDrawable = R.drawable.ic_bg_circular, + iconDrawableRes = R.drawable.ic_tablets, ) private val anyHighRisk = FormElement( id = 21, inputType = InputType.RADIO, - title = "Any High Risk conditions", + title = resources.getString(R.string.any_high_risk_conditions), arrayId = R.array.anc_confirmation_array1, entries = resources.getStringArray(R.array.anc_confirmation_array1), required = false, hasDependants = true ) private val highRiskCondition = FormElement( - id = 22, inputType = InputType.DROPDOWN, title = "High Risk Conditions", + id = 22, inputType = InputType.DROPDOWN, title = resources.getString(R.string.high_risk_conditions), arrayId = R.array.anc_high_risk_array, entries = resources.getStringArray(R.array.anc_high_risk_array), - required = false, hasDependants = true + required = false, hasDependants = true, + showDrawable = true, + backgroundDrawable = R.drawable.ic_bg_circular, + iconDrawableRes = R.drawable.ic_bmi, ) private val otherHighRiskCondition = FormElement( id = 23, inputType = InputType.EDIT_TEXT, - title = "Any other High Risk conditions", + title = resources.getString(R.string.any_other_high_risk_conditions), required = true, + showDrawable = true, + backgroundDrawable = R.drawable.ic_bg_circular, + iconDrawableRes = R.drawable.ic_bmi, ) private val highRiskReferralFacility = FormElement( id = 24, inputType = InputType.DROPDOWN, - title = "Referral Facility", + title = resources.getString(R.string.referral_facility), arrayId = R.array.anc_referral_facility_array, entries = resources.getStringArray(R.array.anc_referral_facility_array), required = false, + showDrawable = true, + backgroundDrawable = R.drawable.ic_bg_circular, + iconDrawableRes = R.drawable.ic_referral, ) private val hrpConfirm = FormElement( - id = 25, inputType = InputType.RADIO, title = "Is HRP Confirmed?", + id = 25, inputType = InputType.RADIO, title = resources.getString(R.string.is_hrp_confirmed), arrayId = R.array.anc_confirmation_array1, entries = resources.getStringArray(R.array.anc_confirmation_array1), required = false, hasDependants = true @@ -253,13 +346,17 @@ class PregnantWomanAncVisitDataset( private val hrpConfirmedBy = FormElement( id = 26, inputType = InputType.DROPDOWN, - title = "Who had identified as HRP?", + title = resources.getString(R.string.who_identified_hrp), arrayId = R.array.anc_confirmed_by_array, entries = resources.getStringArray(R.array.anc_confirmed_by_array), required = true, + showDrawable = true, + backgroundDrawable = R.drawable.ic_bg_circular, + iconDrawableRes = R.drawable.ic_bmi, ) private val maternalDeath = FormElement( - id = 27, inputType = InputType.RADIO, title = "Maternal Death", + id = 27, inputType = InputType.RADIO, + title = resources.getString(R.string.maternal_death), arrayId = R.array.anc_confirmation_array1, entries = resources.getStringArray(R.array.anc_confirmation_array1), required = false, hasDependants = true @@ -267,35 +364,65 @@ class PregnantWomanAncVisitDataset( private val maternalDeathProbableCause = FormElement( id = 28, inputType = InputType.DROPDOWN, - title = "Probable Cause of Death", + title = resources.getString(R.string.probable_cause_of_death), arrayId = R.array.anc_death_cause_array, entries = resources.getStringArray(R.array.anc_death_cause_array), required = true, - hasDependants = true + hasDependants = true, + showDrawable = true, + backgroundDrawable = R.drawable.ic_bg_circular, + iconDrawableRes = R.drawable.ic_bmi, ) private val otherMaternalDeathProbableCause = FormElement( id = 29, inputType = InputType.EDIT_TEXT, - title = "Other Death Cause", + title = resources.getString(R.string.other_death_cause), required = true, + showDrawable = true, + backgroundDrawable = R.drawable.ic_bg_circular, + iconDrawableRes = R.drawable.ic_bmi, ) private val maternalDateOfDeath = FormElement( id = 30, inputType = InputType.DATE_PICKER, - title = "Death Date", + title = resources.getString(R.string.death_date), required = true, max = System.currentTimeMillis(), + showDrawable = true, + backgroundDrawable = R.drawable.ic_bg_circular, + iconDrawableRes = R.drawable.ic_bmi, ) private val deliveryDone = FormElement( id = 31, inputType = InputType.RADIO, - title = "Has the pregnant woman delivered?", + title = resources.getString(R.string.has_pw_delivered), arrayId = R.array.anc_confirmation_array, entries = resources.getStringArray(R.array.anc_confirmation_array), required = false, ) + private val headLine = FormElement( + id = 34, + inputType = InputType.HEADLINE, + title = resources.getString(R.string.mcp_cards), + headingLine = false, + required = false, + ) + private val fileUploadFront = FormElement( + id = 31, + inputType = InputType.FILE_UPLOAD, + title = resources.getString(R.string.front_side), + required = false, + ) + + private val fileUploadBack = FormElement( + id = 32, + inputType = InputType.FILE_UPLOAD, + title = resources.getString(R.string.back_side), + required = false, + ) + private var toggleBp = false fun resetBpToggle() { @@ -304,27 +431,31 @@ class PregnantWomanAncVisitDataset( fun triggerBpToggle() = toggleBp - + fun getIndexOfMCPCardFrontPath() = getIndexById(fileUploadFront.id) + fun getIndexOfMCPCardBackPath() = getIndexById(fileUploadBack.id) suspend fun setUpPage( visitNumber: Int, ben: BenRegCache?, regis: PregnantWomanRegistrationCache, lastAnc: PregnantWomanAncCache?, - saved: PregnantWomanAncCache? + isFromPmsma: Boolean, + saved: PregnantWomanAncCache?, ) { this.regis = regis val list = mutableListOf( ancDate, + placeOfAnc, weekOfPregnancy, ancVisit, - isAborted, +// isAborted, + maternalDeath, weight, bp, - pulseRate, +// pulseRate, hb, - fundalHeight, - urineAlbumin, - randomBloodSugarTest, +// fundalHeight, +// urineAlbumin, +// randomBloodSugarTest, dateOfTTOrTd1, dateOfTTOrTd2, dateOfTTOrTdBooster, @@ -333,9 +464,33 @@ class PregnantWomanAncVisitDataset( anyHighRisk, highRiskReferralFacility, hrpConfirm, - maternalDeath + headLine, + fileUploadFront, + fileUploadBack, + + ) + + if (BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true)) { + list.remove(fileUploadFront) + list.remove(fileUploadBack) + list.remove(headLine) + } + if (BuildConfig.FLAVOR.contains("xushrukha", ignoreCase = true)) { + bp.required=true + hb.required=true + } + else{ + bp.required=false + hb.required=false + } + + if (isFromPmsma) { + placeOfAnc.value = placeOfAnc.entries?.get(3) + placeOfAnc.inputType = InputType.TEXT_VIEW + placeOfAnc.required = true + placeOfAnc.isEnabled=false + } - ) abortionDate.min = regis.lmpDate + TimeUnit.DAYS.toMillis(5 * 7 + 1) dateOfTTOrTd1.min = abortionDate.min dateOfTTOrTdBooster.min = abortionDate.min @@ -344,29 +499,37 @@ class PregnantWomanAncVisitDataset( dateOfTTOrTd1.max = abortionDate.max dateOfTTOrTd2.max = abortionDate.max dateOfTTOrTdBooster.max = abortionDate.max - if (lastAnc == null) list.remove(dateOfTTOrTd2) setUpTdX() ben?.let { ancDate.min = - regis.lmpDate + TimeUnit.DAYS.toMillis(7 * Konstants.minAnc1Week.toLong() + 1) - ancVisit.entries = arrayOf("1", "2", "3", "4") + regis.lmpDate + TimeUnit.DAYS.toMillis(7 * Konstants.minAnc1Week.toLong()) + ancVisit.entries = arrayOf("1", "2", "3", "4", "5", "6", "7", "8","9") lastAnc?.let { last -> ancDate.min = last.ancDate + TimeUnit.DAYS.toMillis(4 * 7) - ancVisit.entries = arrayOf(2, 3, 4).filter { + ancVisit.entries = arrayOf(2, 3, 4, 5, 6, 7, 8,9).filter { it > last.visitNumber }.map { it.toString() }.toTypedArray() lastAncVisitDate = last.ancDate } +// ancDate.max = +// minOf(getEddFromLmp(regis.lmpDate), System.currentTimeMillis()) + + /*This change is done to test working as per Madhava*/ ancDate.max = - minOf(getEddFromLmp(regis.lmpDate), System.currentTimeMillis()) - ancDate.value = getDateFromLong(ancDate.max!!) + minOf(getANCMaxFromLmp(regis.lmpDate), System.currentTimeMillis()) + +// ancDate.value = getDateFromLong(ancDate.max!!) maternalDateOfDeath.min = maxOf(regis.lmpDate, lastAncVisitDate) + TimeUnit.DAYS.toMillis(1) maternalDateOfDeath.max = minOf(getEddFromLmp(regis.lmpDate), System.currentTimeMillis()) + + isAborted.value = resources.getStringArray(R.array.yes_no)[1] + maternalDeath.value = resources.getStringArray(R.array.yes_no)[1] + } // ancDate.value = getDateFromLong(System.currentTimeMillis()) @@ -375,7 +538,8 @@ class PregnantWomanAncVisitDataset( val long = getLongFromDate(it) val weeks = getWeeksOfPregnancy(long, regis.lmpDate) if (weeks >= Konstants.minWeekToShowDelivered) { - list.add(deliveryDone) + list.add(list.indexOf(maternalDeath) + 1,deliveryDone) + } if (weeks <= 12) { list.remove(fundalHeight) @@ -390,7 +554,7 @@ class PregnantWomanAncVisitDataset( ancVisit.value = visitNumber.toString() saved?.let { savedAnc -> - + placeOfAnc.value = getLocalValueInArray(placeOfAnc.arrayId, savedAnc.placeOfAnc) val woP = getWeeksOfPregnancy(savedAnc.ancDate, regis.lmpDate) if (woP <= 12) { list.remove(fundalHeight) @@ -399,10 +563,12 @@ class PregnantWomanAncVisitDataset( list.remove(numFolicAcidTabGiven) } if (woP >= Konstants.minWeekToShowDelivered) { - if (!list.contains(deliveryDone)) list.add(deliveryDone) + if (!list.contains(deliveryDone)) list.add(list.indexOf(maternalDeath) + 1,deliveryDone) } ancDate.value = getDateFromLong(savedAnc.ancDate) weekOfPregnancy.value = woP.toString() + fileUploadFront.value = savedAnc.frontFilePath + fileUploadBack.value = savedAnc.backFilePath isAborted.value = if (savedAnc.isAborted) isAborted.entries!!.last() else isAborted.entries!!.first() if (savedAnc.isAborted) { @@ -455,8 +621,7 @@ class PregnantWomanAncVisitDataset( list.add(list.indexOf(hrpConfirm) + 1, hrpConfirmedBy) } savedAnc.maternalDeath?.let { - maternalDeath.value = - if (it) maternalDeath.entries!!.last() else maternalDeath.entries!!.first() + maternalDeath.value = if (it) maternalDeath.entries!!.last() else maternalDeath.entries!!.first() if (it) { maternalDeathProbableCause.value = getLocalValueInArray( @@ -469,6 +634,11 @@ class PregnantWomanAncVisitDataset( list.indexOf(maternalDeath) + 1, listOf(maternalDeathProbableCause, maternalDateOfDeath) ) + placeOfDeath.entries?.indexOf(savedAnc.placeOfDeath)?.takeIf { it >= 0 }?.let { index -> + if (index == 8) { + list.add(list.indexOf(placeOfDeath) + 1, otherPlaceOfDeath) + } + } otherMaternalDeathProbableCause.value = savedAnc.otherMaternalDeathProbableCause if (maternalDeathProbableCause.value == maternalDeathProbableCause.entries!!.last()) list.add( @@ -526,6 +696,21 @@ class PregnantWomanAncVisitDataset( removeItems = listOf(deliveryDone), ) } + + if (weeks > 24) { + triggerDependants( + source = ancVisit, + removeItems = listOf(isAborted,abortionDate,abortionType,abortionFacility), + addItems = emptyList() + ) + } else { + triggerDependants( + source = ancVisit, + addItems = listOf(isAborted), + removeItems = emptyList() + ) + } + weekOfPregnancy.value = weeks.toString() val calcVisitNumber = when (weeks) { in Konstants.minAnc1Week..Konstants.maxAnc1Week -> 1 @@ -534,16 +719,19 @@ class PregnantWomanAncVisitDataset( in Konstants.minAnc4Week..Konstants.maxAnc4Week -> 4 else -> 0 } - if (ancVisit.entries?.contains(calcVisitNumber.toString()) == true) { + if (isAborted.value == isAborted.entries!!.first() && + maternalDeath.value == maternalDeath.entries!!.first() + ) { ancVisit.value = calcVisitNumber.toString() - val listChanged2 = if (weeks <= 12) + + val listChanged2 = if (weeks <= 12) { triggerDependants( source = ancVisit, addItems = listOf(numFolicAcidTabGiven), removeItems = listOf(fundalHeight, numIfaAcidTabGiven), position = getIndexById(dateOfTTOrTdBooster.id) + 1 ) - else { + } else { triggerDependants( source = ancVisit, removeItems = listOf(numFolicAcidTabGiven), @@ -556,14 +744,15 @@ class PregnantWomanAncVisitDataset( addItems = listOf(numIfaAcidTabGiven), position = getIndexById(dateOfTTOrTdBooster.id) + 1 ) - } + return if (listChanged >= 0 || listChanged2 >= 0) 1 else -1 } return listChanged + } -1 } @@ -593,20 +782,64 @@ class PregnantWomanAncVisitDataset( } } - isAborted.id -> triggerDependants( - source = isAborted, - passedIndex = index, - triggerIndex = 1, - target = listOf(abortionType, abortionDate), - targetSideEffect = listOf(abortionFacility) - ) + isAborted.id -> { + val commonAddItems = listOf( + weight, + bp, +// pulseRate, + hb, +// fundalHeight, +// urineAlbumin, +// randomBloodSugarTest, + dateOfTTOrTd1, + dateOfTTOrTd2, + dateOfTTOrTdBooster, + numFolicAcidTabGiven, + numIfaAcidTabGiven, + anyHighRisk, + highRiskReferralFacility, + hrpConfirm + ) + + val isAbortedYes = isAborted.value == isAborted.entries!!.last() + val isMaternalDeathYes = maternalDeath.value == maternalDeath.entries!!.last() + val abortionFields = listOf(abortionType, abortionFacility, abortionDate) + + if (isAbortedYes) { + triggerDependants( + source = maternalDeath, + addItems = abortionFields, + removeItems = commonAddItems + deliveryDone, + position = getIndexById(isAborted.id).coerceAtLeast(0) + 1 // safe + ) + } else if (isMaternalDeathYes) { + triggerDependants( + source = maternalDeath, + addItems = emptyList(), + removeItems = abortionFields + commonAddItems + deliveryDone, + position = -1 + ) + } else { + val week = weekOfPregnancy.value?.toIntOrNull() + + val addItems = if (week != null && week >= Konstants.minWeekToShowDelivered) { + listOf(deliveryDone) + commonAddItems + } else { + commonAddItems + } + + val removeItems = abortionFields + + if (week == null || week < Konstants.minWeekToShowDelivered) listOf(deliveryDone) else emptyList() + + triggerDependants( + source = maternalDeath, + addItems = addItems, + removeItems = removeItems, + position = -1 + ) + } + } - abortionType.id -> triggerDependants( - source = abortionType, - passedIndex = index, - triggerIndex = 0, - target = abortionFacility, - ) dateOfTTOrTd1.id -> { if (dateOfTTOrTd1.value == null) @@ -630,6 +863,10 @@ class PregnantWomanAncVisitDataset( weight.id -> validateIntMinMax(weight) + fundalHeight.id -> validateIntMinMax(fundalHeight) + + pulseRate.id -> validateIntMinMax(pulseRate) + hb.id -> { validateDoubleUpto1DecimalPlaces(hb) if (hb.errorText == null) validateDoubleMinMax(hb) @@ -679,13 +916,81 @@ class PregnantWomanAncVisitDataset( target = hrpConfirmedBy, ) - maternalDeath.id -> triggerDependants( - source = maternalDeath, - passedIndex = index, - triggerIndex = 1, - target = listOf(maternalDeathProbableCause, maternalDateOfDeath), - targetSideEffect = listOf(otherMaternalDeathProbableCause) - ) + placeOfDeath.id -> { + val index = placeOfDeath.entries?.indexOf(placeOfDeath.value).takeIf { it!! >= 0 } + ?: return -1 + val triggerIndex = 8 + return triggerDependants( + source = placeOfDeath, + passedIndex = index, + triggerIndex = triggerIndex, + target = otherPlaceOfDeath + ) + } + + maternalDeath.id -> { + val commonAddItems = listOf( + weight, + bp, +// pulseRate, + hb, +// fundalHeight, +// urineAlbumin, +// randomBloodSugarTest, + dateOfTTOrTd1, + dateOfTTOrTd2, + dateOfTTOrTdBooster, + numFolicAcidTabGiven, + numIfaAcidTabGiven, + anyHighRisk, + highRiskReferralFacility, + hrpConfirm + ) + + val maternalDeathFields = listOf( + maternalDeathProbableCause, + maternalDateOfDeath, + placeOfDeath + ) + + val week = weekOfPregnancy.value?.toIntOrNull() + + val isMaternalDeathYes = maternalDeath.value == maternalDeath.entries!!.last() + val isAbortedYes = isAborted.value == isAborted.entries!!.last() + + if (isMaternalDeathYes) { + triggerDependants( + source = maternalDeath, + addItems = maternalDeathFields, + removeItems = commonAddItems + deliveryDone, + position = getIndexById(maternalDeath.id).coerceAtLeast(0) + 1 + ) + } else if (isAbortedYes) { + triggerDependants( + source = maternalDeath, + addItems = emptyList(), + removeItems = maternalDeathFields + commonAddItems + deliveryDone, + position = -1 + ) + } else { + val addItems = if (week != null && week >= Konstants.minWeekToShowDelivered) { + listOf(deliveryDone) + commonAddItems // deliveryDone first + } else { + commonAddItems + } + + val removeItems = maternalDeathFields + + if (week == null || week < Konstants.minWeekToShowDelivered) listOf(deliveryDone) else emptyList() + + triggerDependants( + source = maternalDeath, + addItems = addItems, + removeItems = removeItems, + position = -1 + ) + } + } + maternalDeathProbableCause.id -> triggerDependants( source = maternalDeathProbableCause, @@ -711,12 +1016,21 @@ class PregnantWomanAncVisitDataset( override fun mapValues(cacheModel: FormDataModel, pageNumber: Int) { (cacheModel as PregnantWomanAncCache).let { cache -> + cache.placeOfDeath = getEnglishValueInArray(placeOfDeath.arrayId, placeOfDeath.value) + cache.placeOfDeathId = placeOfDeath.entries?.indexOf(placeOfDeath.value ?: "") + ?.takeIf { it != -1 } + + cache.placeOfAnc = getEnglishValueInArray(placeOfAnc.arrayId, placeOfAnc.value) + cache.placeOfAncId = placeOfAnc.entries?.indexOf(placeOfAnc.value ?: "") + ?.takeIf { it != -1 } + cache.otherPlaceOfDeath = otherPlaceOfDeath.value + cache.lmpDate = regis.lmpDate cache.visitNumber = ancVisit.value!!.toInt() cache.ancDate = getLongFromDate(ancDate.value) cache.isAborted = isAborted.value == isAborted.entries!!.last() - cache.abortionType = abortionType.value + cache.abortionType = getEnglishValueInArray(abortionType.arrayId, abortionType.value) cache.abortionTypeId = abortionType.getPosition() - cache.abortionFacility = abortionFacility.value + cache.abortionFacility = getEnglishValueInArray(abortionFacility.arrayId, abortionFacility.value) cache.abortionFacilityId = abortionFacility.getPosition() cache.abortionDate = abortionDate.value?.let { getLongFromDate(it) } cache.weight = weight.value?.toInt() @@ -725,9 +1039,9 @@ class PregnantWomanAncVisitDataset( cache.pulseRate = pulseRate.value?.takeIf { it.isNotEmpty() } cache.hb = hb.value?.toDouble() cache.fundalHeight = fundalHeight.value?.toInt() - cache.urineAlbumin = urineAlbumin.value + cache.urineAlbumin = getEnglishValueInArray(urineAlbumin.arrayId, urineAlbumin.value) cache.urineAlbuminId = urineAlbumin.getPosition() - cache.randomBloodSugarTest = randomBloodSugarTest.value + cache.randomBloodSugarTest = getEnglishValueInArray(randomBloodSugarTest.arrayId, randomBloodSugarTest.value) cache.randomBloodSugarTestId = randomBloodSugarTest.getPosition() updateRegistrationForTdX() cache.numFolicAcidTabGiven = numFolicAcidTabGiven.value?.toInt() ?: 0 @@ -735,22 +1049,24 @@ class PregnantWomanAncVisitDataset( anyHighRisk.value?.let { cache.anyHighRisk = it == anyHighRisk.entries!!.last() } - cache.highRisk = highRiskCondition.value + cache.highRisk = getEnglishValueInArray(highRiskCondition.arrayId, highRiskCondition.value) cache.highRiskId = highRiskCondition.getPosition() cache.otherHighRisk = otherHighRiskCondition.value - cache.referralFacility = highRiskReferralFacility.value + cache.referralFacility = getEnglishValueInArray(highRiskReferralFacility.arrayId, highRiskReferralFacility.value) cache.referralFacilityId = highRiskReferralFacility.getPosition() cache.hrpConfirmed = hrpConfirm.value?.let { it == hrpConfirm.entries!!.last() } - cache.hrpConfirmedBy = hrpConfirmedBy.value + cache.hrpConfirmedBy = getEnglishValueInArray(hrpConfirmedBy.arrayId, hrpConfirmedBy.value) cache.hrpConfirmedById = hrpConfirmedBy.getPosition() cache.maternalDeath = maternalDeath.value == maternalDeath.entries!!.last() - cache.maternalDeathProbableCause = maternalDeathProbableCause.value + cache.maternalDeathProbableCause = getEnglishValueInArray(maternalDeathProbableCause.arrayId, maternalDeathProbableCause.value) cache.maternalDeathProbableCauseId = maternalDeathProbableCause.getPosition() cache.otherMaternalDeathProbableCause = otherMaternalDeathProbableCause.value cache.deathDate = maternalDateOfDeath.value?.let { getLongFromDate(it) } deliveryDone.value?.let { cache.pregnantWomanDelivered = it == deliveryDone.entries!!.first() } + cache.frontFilePath = fileUploadFront.value.toString() + cache.backFilePath = fileUploadBack.value.toString() } } @@ -784,7 +1100,7 @@ class PregnantWomanAncVisitDataset( fun updateBenRecordToDelivered(it: BenRegCache) { it.genDetails?.apply { reproductiveStatus = - englishResources.getStringArray(R.array.nbr_reproductive_status_array)[2] + englishResources.getStringArray(R.array.nbr_reproductive_status_array2)[2] reproductiveStatusId = 3 } if (it.processed != "N") it.processed = "U" @@ -794,10 +1110,24 @@ class PregnantWomanAncVisitDataset( fun updateBenRecordToEligibleCouple(it: BenRegCache) { it.genDetails?.apply { reproductiveStatus = - englishResources.getStringArray(R.array.nbr_reproductive_status_array)[0] + englishResources.getStringArray(R.array.nbr_reproductive_status_array2)[0] reproductiveStatusId = 1 } if (it.processed != "N") it.processed = "U" it.syncState = SyncState.UNSYNCED } + + + fun setImageUriToFormElement(lastImageFormId: Int, dpUri: Uri) { + if (lastImageFormId == 31) { + fileUploadFront.value = dpUri.toString() + fileUploadFront.errorText = null + + } else { + fileUploadBack.value = dpUri.toString() + fileUploadBack.errorText = null + + } + + } } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/PregnantWomanRegistrationDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/PregnantWomanRegistrationDataset.kt index 23e9f4486..3a072a122 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/configuration/PregnantWomanRegistrationDataset.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/PregnantWomanRegistrationDataset.kt @@ -14,6 +14,7 @@ import org.piramalswasthya.sakhi.model.HRPPregnantAssessCache import org.piramalswasthya.sakhi.model.InputType import org.piramalswasthya.sakhi.model.PregnantWomanRegistrationCache import org.piramalswasthya.sakhi.utils.HelperUtil.getDiffYears +import org.piramalswasthya.sakhi.utils.Log import java.util.Calendar import java.util.concurrent.TimeUnit @@ -22,11 +23,16 @@ class PregnantWomanRegistrationDataset( ) : Dataset(context, currentLanguage) { companion object { - private fun getMinLmpMillis(): Long { + var registrationDate:Long = 0 + fun getMinLmpMillis(): Long { val cal = Calendar.getInstance() - cal.add(Calendar.DAY_OF_YEAR, -1 * 280) + cal.add(Calendar.DAY_OF_YEAR, -1 * 400) //before it is 280 return cal.timeInMillis } + private fun pwRegisterBeforeoneYear(): Long { + return registrationDate-TimeUnit.DAYS.toMillis(365) + + } } private val dateOfReg = FormElement( @@ -35,6 +41,9 @@ class PregnantWomanRegistrationDataset( title = resources.getString(R.string.nbr_dor), arrayId = -1, required = true, + min = Calendar.getInstance().apply { + add(Calendar.YEAR, -1) + }.timeInMillis, max = System.currentTimeMillis(), hasDependants = true @@ -49,7 +58,7 @@ class PregnantWomanRegistrationDataset( isMobileNumber = true, etMaxLength = 12 ) - private val mcpCardNumber = FormElement( +/* private val mcpCardNumber = FormElement( id = 3, inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.pwrdst_mcp_card_no), @@ -57,7 +66,7 @@ class PregnantWomanRegistrationDataset( etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, isMobileNumber = true, etMaxLength = 12 - ) + )*/ private val name = FormElement( id = 4, inputType = InputType.TEXT_VIEW, @@ -99,7 +108,8 @@ class PregnantWomanRegistrationDataset( required = true, max = System.currentTimeMillis(), min = getMinLmpMillis(), - hasDependants = true + hasDependants = true, + isEnabled = false, ) private val weekOfPregnancy = FormElement( id = 8, @@ -144,7 +154,8 @@ class PregnantWomanRegistrationDataset( min = 50, max = 200 ) - private val vdrlrprTestResult = FormElement( + + /* private val vdrlrprTestResult = FormElement( id = 13, inputType = InputType.DROPDOWN, title = resources.getString(R.string.pwrdst_vdrl_rpr_tst_rlts), @@ -160,6 +171,7 @@ class PregnantWomanRegistrationDataset( title = resources.getString(R.string.pwrdst_vdrl_tst_done), arrayId = -1, required = false, + min= pwRegisterBeforeoneYear(), max = System.currentTimeMillis(), ) private val hivTestResult = FormElement( @@ -198,7 +210,7 @@ class PregnantWomanRegistrationDataset( required = false, max = System.currentTimeMillis(), ) - +*/ private val pastIllness = FormElement( id = 19, inputType = InputType.CHECKBOXES, @@ -206,7 +218,7 @@ class PregnantWomanRegistrationDataset( arrayId = R.array.maternal_health_past_illness, entries = resources.getStringArray(R.array.maternal_health_past_illness), required = true, - value = resources.getStringArray(R.array.maternal_health_past_illness).first(), + value = "0", hasDependants = true ) @@ -403,7 +415,7 @@ class PregnantWomanRegistrationDataset( val list = mutableListOf( dateOfReg, rchId, - mcpCardNumber, + // mcpCardNumber, name, husbandName, age, @@ -413,11 +425,11 @@ class PregnantWomanRegistrationDataset( bloodGroup, weight, height, - vdrlrprTestResult, +// vdrlrprTestResult, // dateOfVdrlTestDone, - hivTestResult, +// hivTestResult, // dateOfhivTestDone, - hbsAgTestResult, +// hbsAgTestResult, // dateOfhbsAgTestDone, pastIllness, isFirstPregnancy, @@ -435,20 +447,22 @@ class PregnantWomanRegistrationDataset( multiplePregnancy, isHrpCase ) - dateOfReg.value = getDateFromLong(System.currentTimeMillis()) + + // dateOfReg.value = getDateFromLong(System.currentTimeMillis()) dateOfReg.value?.let { val long = getLongFromDate(it) - dateOfhivTestDone.min = long - dateOfVdrlTestDone.min = long - dateOfhbsAgTestDone.min = long +// dateOfhivTestDone.min = long - TimeUnit.DAYS.toMillis(365) +// dateOfVdrlTestDone.min = long - TimeUnit.DAYS.toMillis(365) +// dateOfhbsAgTestDone.min = long - TimeUnit.DAYS.toMillis(365) } + ben?.let { - dateOfReg.min = it.regDate.also { - dateOfVdrlTestDone.min = it - dateOfhivTestDone.min = it + /* dateOfReg.min = it.regDate.also { + dateOfVdrlTestDone.min = it - TimeUnit.DAYS.toMillis(365) + dateOfhivTestDone.min = it - TimeUnit.DAYS.toMillis(365) hbsAgTestResult.min = it - } + }*/ rchId.value = ben.rchId name.value = "${ben.firstName} ${if (ben.lastName == null) "" else ben.lastName}" husbandName.value = ben.genDetails?.spouseName @@ -468,9 +482,9 @@ class PregnantWomanRegistrationDataset( )[1] } - lastTrackTimestamp?.let { + /* lastTrackTimestamp?.let { dateOfReg.min = it - } + }*/ // fetching high risk assess cache details and assigning them assess?.let { noOfDeliveries.value = getLocalValueInArray(R.array.yes_no, it.noOfDeliveries) @@ -494,7 +508,7 @@ class PregnantWomanRegistrationDataset( multiplePregnancy.value = getLocalValueInArray(R.array.yes_no, it.multiplePregnancy) // if there is valid lmp date auto populating edd and weeks of pregnancy - lmp.value = getDateFromLong(it.lmpDate) +// lmp.value = getDateFromLong(it.lmpDate) if (it.lmpDate > 0) { val weekOfPreg = @@ -502,20 +516,20 @@ class PregnantWomanRegistrationDataset( saved?.dateOfRegistration ?: Calendar.getInstance().timeInMillis, it.lmpDate ) - weekOfPregnancy.value = - weekOfPreg.toString() + /* weekOfPregnancy.value = + weekOfPreg.toString()*/ // if (weekOfPreg > 26) - lmp.inputType = InputType.TEXT_VIEW - edd.value = getDateFromLong(getEddFromLmp(it.lmpDate)) - ben?.regDate?.let { it1 -> - if (lastTrackTimestamp != null) { - dateOfReg.min = maxOf(it.lmpDate, it1, lastTrackTimestamp) - } else { - dateOfReg.min = maxOf(it.lmpDate, it1) - } - } - dateOfReg.max = minOf(it.edd, System.currentTimeMillis()) - +// lmp.inputType = InputType.TEXT_VIEW +// edd.value = getDateFromLong(getEddFromLmp(it.lmpDate)) + /* ben?.regDate?.let { it1 -> + if (lastTrackTimestamp != null) { + dateOfReg.min = maxOf(it.lmpDate, it1, lastTrackTimestamp) + } else { + dateOfReg.min = maxOf(it.lmpDate, it1) + } + } + dateOfReg.max = minOf(it.edd, System.currentTimeMillis()) + */ } childInfoLabel.showHighRisk = ( @@ -545,22 +559,25 @@ class PregnantWomanRegistrationDataset( dateOfReg.apply { value = getDateFromLong(it.dateOfRegistration) inputType = InputType.TEXT_VIEW + } + it.dateOfRegistration.let { lmp.max = it - lmp.min = it - TimeUnit.DAYS.toMillis(280) - dateOfVdrlTestDone.min = it - dateOfhivTestDone.min = it - dateOfhbsAgTestDone.min = it + lmp.min = it - TimeUnit.DAYS.toMillis(400) + /* dateOfVdrlTestDone.min = it - TimeUnit.DAYS.toMillis(365) + dateOfhivTestDone.min = it - TimeUnit.DAYS.toMillis(365) + dateOfhbsAgTestDone.min = it - TimeUnit.DAYS.toMillis(365) +*/ } val eddFromLmp = getEddFromLmp(it.lmpDate) minOf(System.currentTimeMillis(), eddFromLmp).let { maxDate -> - dateOfVdrlTestDone.max = maxDate + /* dateOfVdrlTestDone.max = maxDate dateOfhivTestDone.max = maxDate - dateOfhbsAgTestDone.max = maxDate + dateOfhbsAgTestDone.max = maxDate*/ } - mcpCardNumber.value = it.mcpCardNumber.toString() + // mcpCardNumber.value = it.mcpCardNumber.toString() lmp.value = getDateFromLong(it.lmpDate) if (it.lmpDate > 0) { lmp.isEnabled = false @@ -574,7 +591,7 @@ class PregnantWomanRegistrationDataset( bloodGroup.value = getLocalValueInArray(bloodGroup.arrayId, saved.bloodGroup) weight.value = it.weight?.toString() height.value = it.height?.toString() - vdrlrprTestResult.value = + /* vdrlrprTestResult.value = getLocalValueInArray(vdrlrprTestResult.arrayId, saved.vdrlRprTestResult) it.vdrlRprTestResult?.let { it1 -> if (it1 == vdrlrprTestResult.entries?.get(0) || it1 == vdrlrprTestResult.entries?.get( @@ -601,10 +618,12 @@ class PregnantWomanRegistrationDataset( } hivTestResult.value = getLocalValueInArray(hivTestResult.arrayId, saved.hivTestResult) hbsAgTestResult.value = - getLocalValueInArray(hbsAgTestResult.arrayId, saved.hbsAgTestResult) - pastIllness.value = getLocalValueInArray(pastIllness.arrayId, saved.pastIllness) + getLocalValueInArray(hbsAgTestResult.arrayId, saved.hbsAgTestResult)*/ + pastIllness.value = getCheckboxIndexesFromValues(pastIllness.arrayId, saved.pastIllness) ?: "0" otherPastIllness.value = it.otherPastIllness - if (pastIllness.value == pastIllness.entries!!.last()) + val otherIndex = pastIllness.entries!!.size - 1 + val selectedIndexes = pastIllness.value?.split("|")?.mapNotNull { it.trim().toIntOrNull() } ?: emptyList() + if (selectedIndexes.contains(otherIndex)) list.add(list.indexOf(pastIllness) + 1, otherPastIllness) isFirstPregnancy.value = isFirstPregnancy.getStringFromPosition(if (it.is1st) 1 else 2) if (isFirstPregnancy.value == isFirstPregnancy.entries!!.last()) { @@ -635,11 +654,11 @@ class PregnantWomanRegistrationDataset( } ?: run { assess?.let { // if there is no lmp date from pwr saved form or hrp assessment form then disabling these fields - if (it.lmpDate <= 0L) { + /* if (it.lmpDate <= 0L) { vdrlrprTestResult.inputType = InputType.TEXT_VIEW hivTestResult.inputType = InputType.TEXT_VIEW hbsAgTestResult.inputType = InputType.TEXT_VIEW - } + }*/ } } @@ -672,32 +691,50 @@ class PregnantWomanRegistrationDataset( // list.add(assignedAsHrpBy) // } // } + + + noOfDeliveries.isEnabled=false + timeLessThan18m.isEnabled=false setUpPage(list) } fun getIndexOfEdd() = getIndexById(edd.id) + fun getIndexOfLmp() = getIndexById(lmp.id) fun getIndexOfWeeksPregnancy() = getIndexById(weekOfPregnancy.id) fun getIndexOfPastIllness() = getIndexById(pastIllness.id) - fun getIndexOfVdrlTestResult() = getIndexById(vdrlrprTestResult.id) +// fun getIndexOfVdrlTestResult() = getIndexById(vdrlrprTestResult.id) override suspend fun handleListOnValueChanged(formId: Int, index: Int): Int { return when (formId) { rchId.id -> validateRchIdOnEditText(rchId) - mcpCardNumber.id -> validateMcpOnEditText(mcpCardNumber) + // mcpCardNumber.id -> validateMcpOnEditText(mcpCardNumber) dateOfReg.id -> { - dateOfReg.value?.let { - val dateOfRegLong = getLongFromDate(it) + val dateStr = dateOfReg.value + + if (dateOfReg.value != null) { + Log.e("DATKHJH","Coming") + triggerDependants( + source = age, + passedIndex = index, + triggerIndex = index, + target = listOf(lmp) + ) + val dateOfRegLong = getLongFromDate(dateStr) + + lmp.isEnabled = true lmp.max = dateOfRegLong lmp.min = getMinFromMaxForLmp(lmp.max!!) - dateOfVdrlTestDone.min = dateOfRegLong - dateOfhivTestDone.min = dateOfRegLong - dateOfhbsAgTestDone.min = dateOfRegLong + updateAgeCheck(dateOfBirth, dateOfRegLong) + + + return handleListOnValueChanged(isHrpCase.id, 0) } + -1 } @@ -712,7 +749,7 @@ class PregnantWomanRegistrationDataset( weekOfPregnancy.value = bracket.toString() } } - vdrlrprTestResult.let { + /* vdrlrprTestResult.let { if (it.inputType != InputType.DROPDOWN) it.inputType = InputType.DROPDOWN } hivTestResult.let { @@ -720,18 +757,18 @@ class PregnantWomanRegistrationDataset( } hbsAgTestResult.let { if (it.inputType != InputType.DROPDOWN) it.inputType = InputType.DROPDOWN - } + }*/ edd.value = eddLong?.let { getDateFromLong(it) } regLong?.let { - dateOfhivTestDone.min = it - dateOfVdrlTestDone.min = it - dateOfhbsAgTestDone.min = it + /* dateOfhivTestDone.min = it - TimeUnit.DAYS.toMillis(365) + dateOfVdrlTestDone.min = it - TimeUnit.DAYS.toMillis(365) + dateOfhbsAgTestDone.min = it - TimeUnit.DAYS.toMillis(365)*/ } eddLong?.let { val max = minOf(it, System.currentTimeMillis()) - dateOfVdrlTestDone.max = max + /* dateOfVdrlTestDone.max = max dateOfhivTestDone.max = max - dateOfhbsAgTestDone.max = max + dateOfhbsAgTestDone.max = max*/ } -1 } @@ -754,7 +791,7 @@ class PregnantWomanRegistrationDataset( -1 } - vdrlrprTestResult.id -> { + /* vdrlrprTestResult.id -> { triggerDependantsReverse( source = vdrlrprTestResult, passedIndex = index, @@ -779,29 +816,44 @@ class PregnantWomanRegistrationDataset( triggerIndex = 2, target = listOf(dateOfhbsAgTestDone) ) - } + }*/ pastIllness.id -> { val entries = pastIllness.entries!! - pastIllness.value?.takeIf { it.isNotEmpty() }?.let { - if (index == 0) pastIllness.value = pastIllness.entries!!.first() - else pastIllness.value = it.replace(entries.first(), "") - } ?: run { - pastIllness.value = pastIllness.entries!!.first() + val noneIndex = 0 + val otherIndex = entries.size - 1 + val selectedIndexes = pastIllness.value + ?.split("|") + ?.mapNotNull { it.trim().toIntOrNull() } + ?.toMutableSet() + ?: mutableSetOf() + + if (index == noneIndex) { + // "None" selected: clear all others, keep only None + pastIllness.value = "0" + } else { + // Other option selected: remove None + selectedIndexes.remove(noneIndex) + pastIllness.value = + if (selectedIndexes.isEmpty()) "0" + else selectedIndexes.sorted().joinToString("|") } - if (pastIllness.value?.contains(entries.last()) == true) { + + if (selectedIndexes.contains(otherIndex)) { triggerDependants( source = pastIllness, passedIndex = index, triggerIndex = index, target = otherPastIllness ) - } else triggerDependants( - source = pastIllness, - passedIndex = index, - triggerIndex = -230, - target = otherPastIllness - ) + } else { + triggerDependants( + source = pastIllness, + passedIndex = index, + triggerIndex = -230, + target = otherPastIllness + ) + } } otherPastIllness.id -> { @@ -929,7 +981,7 @@ class PregnantWomanRegistrationDataset( override fun mapValues(cacheModel: FormDataModel, pageNumber: Int) { (cacheModel as PregnantWomanRegistrationCache).let { form -> form.dateOfRegistration = getLongFromDate(dateOfReg.value) - form.mcpCardNumber = mcpCardNumber.value?.takeIf { it.isNotEmpty() }?.toLong() ?: 0 + // form.mcpCardNumber = mcpCardNumber.value?.takeIf { it.isNotEmpty() }?.toLong() ?: 0 form.rchId = rchId.value?.takeIf { it.isNotEmpty() }?.toLong() ?: 0 form.lmpDate = getLongFromDate(lmp.value) form.bloodGroup = @@ -937,7 +989,7 @@ class PregnantWomanRegistrationDataset( form.bloodGroupId = bloodGroup.getPosition() form.weight = weight.value?.toInt() form.height = height.value?.toInt() - form.vdrlRprTestResult = + /* form.vdrlRprTestResult = getEnglishValueInArray(R.array.maternal_health_test_result, vdrlrprTestResult.value) form.vdrlRprTestResultId = vdrlrprTestResult.getPosition() form.dateOfVdrlRprTest = @@ -951,9 +1003,9 @@ class PregnantWomanRegistrationDataset( getEnglishValueInArray(R.array.maternal_health_test_result_2, hbsAgTestResult.value) form.hbsAgTestResultId = hbsAgTestResult.getPosition() form.dateOfHbsAgTest = - dateOfhbsAgTestDone.value?.let { getLongFromDate(dateOfhbsAgTestDone.value) } + dateOfhbsAgTestDone.value?.let { getLongFromDate(dateOfhbsAgTestDone.value) }*/ form.pastIllness = - getEnglishValueInArray(R.array.maternal_health_past_illness, pastIllness.value) + getEnglishCheckboxValues(R.array.maternal_health_past_illness, pastIllness.value) form.otherPastIllness = otherPastIllness.value form.is1st = isFirstPregnancy.value == isFirstPregnancy.entries!!.first() form.numPrevPregnancy = totalNumberOfPreviousPregnancy.value?.toInt() diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/SaasBahuSamelanDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/SaasBahuSamelanDataset.kt new file mode 100644 index 000000000..6837ccb90 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/SaasBahuSamelanDataset.kt @@ -0,0 +1,139 @@ +package org.piramalswasthya.sakhi.configuration + +import android.content.Context +import org.piramalswasthya.sakhi.BuildConfig +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.helpers.Languages +import org.piramalswasthya.sakhi.model.FormElement +import org.piramalswasthya.sakhi.model.InputType +import org.piramalswasthya.sakhi.model.SaasBahuSammelanCache +import java.util.Calendar +import org.piramalswasthya.sakhi.utils.HelperUtil.getDateStringFromLong + + +class SaasBahuSamelanDataset(context: Context, language: Languages) : Dataset(context, language) { + + private fun getTwoMonthsBackDate(): Long { + val cal = Calendar.getInstance() + cal.add(Calendar.MONTH, -2) + return cal.timeInMillis + } + + private val dateD = FormElement( + id = 1, + inputType = InputType.DATE_PICKER, + title = resources.getString(R.string.saas_date), + required = true, + min = getTwoMonthsBackDate(), + max = System.currentTimeMillis() + ) + + private val place = FormElement( + id = 2, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.place), + entries = resources.getStringArray(R.array.place_array), + arrayId = R.array.place_array, + required = false, + hasDependants = false + ) + private val noOfParticipante = FormElement( + id = 3, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.no_participent), + arrayId = -1, + required = true, + hasDependants = true, + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + etMaxLength = 3, + max = 999, + min = 0, + ) + + val upload1 = FormElement( + id = 10, + inputType = InputType.FILE_UPLOAD, + title = "Sammelan Photos / MoM (1)", + required = false, + ) + val upload2 = upload1.copy(id = 11, title = "Sammelan Photos / MoM (2)") + val upload3 = upload1.copy(id = 12, title = "Sammelan Photos / MoM (3)") + val upload4 = upload1.copy(id = 13, title = "Sammelan Photos / MoM (4)") + val upload5 = upload1.copy(id = 14, title = "Sammelan Photos / MoM (5)") + + + suspend fun setUpPage( + saasBahu: SaasBahuSammelanCache?, + recordExists: Boolean + + ) { + val list = mutableListOf( + dateD, + place, + noOfParticipante, + ) + + val uploadList = listOf(upload1, upload2, upload3, upload4, upload5) + + if (recordExists) { + + dateD.value = getDateStringFromLong(saasBahu?.date) + place.value = getLocalValueInArray(R.array.place_array, saasBahu?.place) + noOfParticipante.value = saasBahu?.participants?.toString() + val imgs = saasBahu?.sammelanImages ?: emptyList() + upload1.value = imgs.getOrNull(0) + upload2.value = imgs.getOrNull(1) + upload3.value = imgs.getOrNull(2) + upload4.value = imgs.getOrNull(3) + upload5.value = imgs.getOrNull(4) + val filledUploads = uploadList.filter { !it.value.isNullOrEmpty() } + + if (filledUploads.isEmpty()) { + list.addAll(uploadList) + } else { + list.addAll(filledUploads) + } + + + } else { + list.addAll(uploadList) + } + + setUpPage(list) + + } + + override suspend fun handleListOnValueChanged( + formId: Int, + index: Int + ): Int { + return when (formId) { + noOfParticipante.id -> { + validateAllDigitOnEditText(noOfParticipante) + } + + else -> -1 + } + + } + + override fun mapValues( + cacheModel: FormDataModel, + pageNumber: Int + ) { + + } + + fun mapSaasBahuValues( + cacheModel: SaasBahuSammelanCache, + ) { + cacheModel.place = getEnglishValueInArray(R.array.place_array, place.value) + cacheModel.sammelanImages = listOfNotNull(upload1.value, upload2.value, upload3.value, upload4.value, upload5.value) + cacheModel.participants = noOfParticipante.value?.toInt() + + cacheModel.date = getLongFromDate(dateD.value.toString()) + + } + + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/SuspectedTBDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/SuspectedTBDataset.kt index 112087eba..9adaaa1af 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/configuration/SuspectedTBDataset.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/SuspectedTBDataset.kt @@ -22,9 +22,64 @@ class SuspectedTBDataset( hasDependants = true ) + private val visitLabel = FormElement( + id = 2, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.visits), + arrayId = -1, + required = false, + hasDependants = false + ) + + private val typeOfTBCase = FormElement( + id = 3, + inputType = InputType.RADIO, + title = resources.getString(R.string.type_of_tb_case), + arrayId = R.array.type_of_tb_case, + entries = resources.getStringArray(R.array.type_of_tb_case), + required = true, + hasDependants = true + ) + + private val reasonForSuspicion = FormElement( + id = 4, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.reason_for_suspicion), + arrayId = R.array.reason_for_suspicion, + entries = resources.getStringArray(R.array.reason_for_suspicion), + required = true, + hasDependants = true + ) + + private val hasSymptoms = FormElement( + id = 5, + inputType = InputType.RADIO, + title = resources.getString(R.string.has_symptoms), + entries = resources.getStringArray(R.array.yes_no), + required = true, + hasDependants = true + ) + private val isChestXRayDone = FormElement( + id = 6, + inputType = InputType.RADIO, + title = resources.getString(R.string.is_chest_xray_done), + entries = resources.getStringArray(R.array.yes_no), + required = false, + hasDependants = true + ) + + private val chestXRayResult = FormElement( + id = 7, + inputType = InputType.RADIO, + title = resources.getString(R.string.chest_xray_result), + arrayId = R.array.chest_xray_result, + entries = resources.getStringArray(R.array.chest_xray_result), + required = false, + hasDependants = false + ) private val isSputumCollected = FormElement( - id = 2, + id = 8, inputType = InputType.RADIO, title = resources.getString(R.string.is_sputum_sample_collected), entries = resources.getStringArray(R.array.yes_no), @@ -32,43 +87,67 @@ class SuspectedTBDataset( hasDependants = true ) - private var sputumSubmittedAt = FormElement( - id = 3, - inputType = InputType.RADIO, + + private val sputumSubmittedAt = FormElement( + id = 9, + inputType = InputType.DROPDOWN, title = resources.getString(R.string.sputum_sample_submitted_at), - entries = resources.getStringArray(R.array.tb_submitted_yet), + arrayId = R.array.tb_sputum_sample_submitted_at, + entries = resources.getStringArray(R.array.tb_sputum_sample_submitted_at), required = false, hasDependants = false ) - private var nikshayId = FormElement( - id = 4, + + private val nikshayId = FormElement( + id = 10, inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.nikshay_id), required = false, hasDependants = false ) - private var sputumTestResult = FormElement( - id = 5, + private val sputumTestResult = FormElement( + id = 11, inputType = InputType.RADIO, title = resources.getString(R.string.sputum_test_result), + arrayId = R.array.tb_test_result, entries = resources.getStringArray(R.array.tb_test_result), required = false, hasDependants = false ) - private var referred = FormElement( - id = 6, + + + private val referralFacility = FormElement( + id = 12, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.referral_facility), + arrayId = R.array.referral_facility, + entries = resources.getStringArray(R.array.referral_facility), + required = true, + hasDependants = false + ) + + private val isTBConfirmed = FormElement( + id = 13, inputType = InputType.RADIO, - title = resources.getString(R.string.referred_to_facility), + title = resources.getString(R.string.is_tb_confirmed), + entries = resources.getStringArray(R.array.yes_no), + required = true, + hasDependants = true + ) + private val isDRTBConfirmed = FormElement( + id = 14, + inputType = InputType.RADIO, + title = resources.getString(R.string.is_drtb_confirmed), entries = resources.getStringArray(R.array.yes_no), required = true, hasDependants = false ) private var followUps = FormElement( - id = 7, + id = 15, inputType = InputType.EDIT_TEXT, title = resources.getString(R.string.facility_referral_follow_ups), entries = resources.getStringArray(R.array.yes_no), @@ -77,61 +156,100 @@ class SuspectedTBDataset( ) suspend fun setUpPage(ben: BenRegCache?, saved: TBSuspectedCache?) { - var list = mutableListOf( - dateOfVisit, - isSputumCollected, -// sputumSubmittedAt, -// nikshayId, -// sputumTestResult, - referred, - followUps - ) + visitLabel.value = resources.getString(R.string.visit_format, 1) + var list = mutableListOf() + if (saved == null) { dateOfVisit.value = getDateFromLong(System.currentTimeMillis()) - } else { + typeOfTBCase.value = null + hasSymptoms.value = resources.getStringArray(R.array.yes_no)[0] + + list.addAll(listOf( + dateOfVisit, + visitLabel, + typeOfTBCase, + + )) + + if (hasSymptoms.value == resources.getStringArray(R.array.yes_no)[0]) { + isSputumCollected.value = resources.getStringArray(R.array.yes_no)[1] + list.add(isSputumCollected) + } else { + isChestXRayDone.value = resources.getStringArray(R.array.yes_no)[1] + list.add(isChestXRayDone) + } + + list.addAll(listOf( + referralFacility, + )) + } + else { + dateOfVisit.value = getDateFromLong(saved.visitDate) + visitLabel.value = resources.getString(R.string.visit_format, 1) + typeOfTBCase.value = getLocalValueInArray(R.array.type_of_tb_case, saved.typeOfTBCase) + hasSymptoms.value = saved.hasSymptoms?.let { + if (it) resources.getStringArray(R.array.yes_no)[0] else resources.getStringArray(R.array.yes_no)[1] + } + isSputumCollected.value = saved.isSputumCollected?.let { + if (it) resources.getStringArray(R.array.yes_no)[0] else resources.getStringArray(R.array.yes_no)[1] + } + + list.addAll(listOf( + dateOfVisit, + visitLabel, + typeOfTBCase + )) + + if (typeOfTBCase.value in listOf( + resources.getStringArray(R.array.type_of_tb_case)[1], + resources.getStringArray(R.array.type_of_tb_case)[2] + )) { + reasonForSuspicion.value = getLocalValueInArray(R.array.reason_for_suspicion, saved.reasonForSuspicion) + list.add(reasonForSuspicion) + } + + //list.add(hasSymptoms) + list.add(isSputumCollected) + if (saved.isSputumCollected == true) { - list = mutableListOf( - dateOfVisit, - isSputumCollected, + sputumSubmittedAt.value = getLocalValueInArray(R.array.tb_sputum_sample_submitted_at, saved.sputumSubmittedAt) + nikshayId.value = saved.nikshayId + sputumTestResult.value = getLocalValueInArray(R.array.tb_test_result, saved.sputumTestResult) + + list.addAll(listOf( sputumSubmittedAt, nikshayId, - sputumTestResult, - referred, - followUps - ) - dateOfVisit.value = getDateFromLong(saved.visitDate) - isSputumCollected.value = - if (saved.isSputumCollected == true) resources.getStringArray(R.array.yes_no)[0] else resources.getStringArray( - R.array.yes_no - )[1] - sputumSubmittedAt.value = - if (saved.sputumSubmittedAt == null) null else resources.getStringArray(R.array.tb_submitted_yet)[englishResources.getStringArray( - R.array.tb_submitted_yet - ).indexOf(saved.sputumSubmittedAt)] - nikshayId.value = saved.nikshayId - sputumTestResult.value = - if (saved.sputumTestResult == null) null else resources.getStringArray(R.array.tb_test_result)[englishResources.getStringArray( - R.array.tb_test_result - ).indexOf(saved.sputumTestResult)] - referred.value = - if (saved.referred == true) resources.getStringArray(R.array.yes_no)[0] else resources.getStringArray( - R.array.yes_no - )[1] - followUps.value = saved.followUps - } else { - dateOfVisit.value = getDateFromLong(saved.visitDate) - isSputumCollected.value = - if (saved.isSputumCollected == true) resources.getStringArray(R.array.yes_no)[0] else resources.getStringArray( - R.array.yes_no - )[1] - referred.value = - if (saved.referred == true) resources.getStringArray(R.array.yes_no)[0] else resources.getStringArray( - R.array.yes_no - )[1] - followUps.value = saved.followUps + sputumTestResult + )) + } + + + referralFacility.value = getLocalValueInArray(R.array.referral_facility, saved.referralFacility) + + if (typeOfTBCase.value == typeOfTBCase.entries!![0]) { + isTBConfirmed.value = saved.isTBConfirmed?.let { + if (it) resources.getStringArray(R.array.yes_no)[0] else resources.getStringArray(R.array.yes_no)[1] + } + list.add(isTBConfirmed) + } else if (typeOfTBCase.value in listOf( + typeOfTBCase.entries!![1], + typeOfTBCase.entries!![2] + )) { + isDRTBConfirmed.value = saved.isDRTBConfirmed?.let { + if (it) resources.getStringArray(R.array.yes_no)[0] else resources.getStringArray(R.array.yes_no)[1] + } + list.add(isDRTBConfirmed) } + + list.addAll(listOf( + referralFacility, + + )) } + + + ben?.let { dateOfVisit.min = it.regDate @@ -142,6 +260,76 @@ class SuspectedTBDataset( override suspend fun handleListOnValueChanged(formId: Int, index: Int): Int { return when (formId) { +/* + typeOfTBCase.id ->{ + if (typeOfTBCase.value != resources.getStringArray(R.array.type_of_tb_case)[0]) + { + triggerDependants( + source = typeOfTBCase, + addItems = listOf(reasonForSuspicion), + removeItems = listOf(), + ) + triggerDependants(source = referralFacility, + addItems = listOf(isTBConfirmed), removeItems = listOf(), position = -2) + } + else + { + triggerDependants( + source = typeOfTBCase, + addItems = listOf(), + removeItems = listOf(reasonForSuspicion), + ) + triggerDependants(source = referralFacility, + addItems = listOf(), removeItems = listOf(isTBConfirmed), position = 12) + } + }*/ + typeOfTBCase.id -> { + val currentValue = typeOfTBCase.value + + if (currentValue == typeOfTBCase.entries!![0]) { + triggerDependants( + source = typeOfTBCase, + addItems = listOf(), + removeItems = listOf(reasonForSuspicion, isDRTBConfirmed), + ) + + return triggerDependants( + source = typeOfTBCase, + addItems = listOf(isTBConfirmed), + removeItems = listOf(), + position = -2 + ) + } else if (currentValue == typeOfTBCase.entries!![1] || currentValue == typeOfTBCase.entries!![2]) { + val result1 = triggerDependants( + source = typeOfTBCase, + addItems = listOf(reasonForSuspicion), + removeItems = listOf(), + ) + + triggerDependants( + source = typeOfTBCase, + addItems = listOf(isDRTBConfirmed), + removeItems = listOf(), + position = -2 + ) + + triggerDependants( + source = typeOfTBCase, + addItems = listOf(), + removeItems = listOf(isTBConfirmed), + ) + + return result1 + } else { + triggerDependants( + source = typeOfTBCase, + addItems = listOf(), + removeItems = listOf(reasonForSuspicion, isTBConfirmed, isDRTBConfirmed), + ) + return -1 + } + } + isSputumCollected.id -> { triggerDependants( source = isSputumCollected, @@ -163,6 +351,8 @@ class SuspectedTBDataset( ) } + + else -> -1 } } @@ -170,19 +360,33 @@ class SuspectedTBDataset( override fun mapValues(cacheModel: FormDataModel, pageNumber: Int) { (cacheModel as TBSuspectedCache).let { form -> form.visitDate = getLongFromDate(dateOfVisit.value) - form.isSputumCollected = - isSputumCollected.value == resources.getStringArray(R.array.yes_no)[0] - form.sputumSubmittedAt = - if (sputumSubmittedAt.value == null) null else englishResources.getStringArray(R.array.tb_submitted_yet)[sputumSubmittedAt.entries!!.indexOf( - sputumSubmittedAt.value - )] + form.visitLabel = visitLabel.value + form.typeOfTBCase = getEnglishValueInArray(R.array.type_of_tb_case, typeOfTBCase.value) + form.reasonForSuspicion = getEnglishValueInArray(R.array.reason_for_suspicion, reasonForSuspicion.value) + form.isChestXRayDone = isChestXRayDone.value?.let { + it == isChestXRayDone.entries!![0] + } + form.chestXRayResult = getEnglishValueInArray(R.array.chest_xray_result, chestXRayResult.value) + + form.isSputumCollected = isSputumCollected.value?.let { + it == isSputumCollected.entries!![0] + } + form.sputumSubmittedAt = getEnglishValueInArray(R.array.tb_sputum_sample_submitted_at, sputumSubmittedAt.value) form.nikshayId = nikshayId.value - form.sputumTestResult = - if (sputumTestResult.value == null) null else englishResources.getStringArray(R.array.tb_test_result)[sputumTestResult.entries!!.indexOf( - sputumTestResult.value - )] - form.referred = referred.value == resources.getStringArray(R.array.yes_no)[0] - form.followUps = followUps.value + form.sputumTestResult = getEnglishValueInArray(R.array.tb_test_result, sputumTestResult.value) + + form.referralFacility = getEnglishValueInArray(R.array.referral_facility, referralFacility.value) + form.isTBConfirmed = isTBConfirmed.value?.let { + it == isTBConfirmed.entries!![0] + } + form.isDRTBConfirmed = isDRTBConfirmed.value?.let { + it == isDRTBConfirmed.entries!![0] + } + + + if (form.isTBConfirmed == true || form.isDRTBConfirmed == true) { + form.isConfirmed = true + } } } diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/TBConfirmedDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/TBConfirmedDataset.kt new file mode 100644 index 000000000..20668637b --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/TBConfirmedDataset.kt @@ -0,0 +1,652 @@ +package org.piramalswasthya.sakhi.configuration + +import android.content.Context +import android.util.Range +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.helpers.Languages +import org.piramalswasthya.sakhi.model.BenRegCache +import org.piramalswasthya.sakhi.model.FormElement +import org.piramalswasthya.sakhi.model.InputType +import org.piramalswasthya.sakhi.model.TBConfirmedTreatmentCache +import org.piramalswasthya.sakhi.model.TBSuspectedCache +import java.util.Calendar +import java.util.Locale +import kotlinx.coroutines.flow.first + +class TBConfirmedDataset( + context: Context, currentLanguage: Languages +) : Dataset(context, currentLanguage) { + + private fun getOneYearBeforeCurrentDate(): Long { + val calendar = Calendar.getInstance() + calendar.add(Calendar.YEAR, -1) + calendar.set(Calendar.HOUR_OF_DAY, 0) + calendar.set(Calendar.MINUTE, 0) + calendar.set(Calendar.SECOND, 0) + calendar.set(Calendar.MILLISECOND, 0) + return calendar.timeInMillis + } + + private var treatmentStartDateLong: Long = 0L + private var lastFollowUpDateLong: Long = 0L + private var nextFollowUpMonthStart: Long = 0L + private var nextFollowUpMonthEnd: Long = 0L + + private val regimenType = FormElement( + id = 1, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.regimen_type), + arrayId = R.array.tb_regimen_types, + entries = resources.getStringArray(R.array.tb_regimen_types), + required = true, + hasDependants = true, + isEnabled = true + ) + + private val treatmentStartDate = FormElement( + id = 2, + inputType = InputType.DATE_PICKER, + title = resources.getString(R.string.treatment_start_date), + arrayId = -1, + required = true, + max = System.currentTimeMillis(), + hasDependants = true, + isEnabled = true + + ) + + private val expectedTreatmentCompletionDate = FormElement( + id = 3, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.expected_treatment_completion_date), + arrayId = -1, + required = false, + hasDependants = true, + isEnabled = true, + + ) + + private val followUpDate = FormElement( + id = 4, + inputType = InputType.DATE_PICKER, + title = resources.getString(R.string.follow_up_date), + arrayId = -1, + required = true, + max = System.currentTimeMillis(), + min = System.currentTimeMillis() - (90L * 24 * 60 * 60 * 1000), + hasDependants = true, + isEnabled = false + + ) + + private val monthlyFollowUpDone = FormElement( + id = 5, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.monthly_follow_up_done), + arrayId = -1, + required = false, + hasDependants = false, + isEnabled = true + + ) + + private val adherenceToMedicines = FormElement( + id = 6, + inputType = InputType.RADIO, + title = resources.getString(R.string.adherence_to_medicines), + arrayId = R.array.adherence_options, + entries = resources.getStringArray(R.array.adherence_options), + required = true, + hasDependants = false, + isEnabled = true + + ) + + private val anyDiscomfort = FormElement( + id = 7, + inputType = InputType.RADIO, + title = resources.getString(R.string.any_discomfort), + entries = resources.getStringArray(R.array.yes_no), + required = true, + hasDependants = false, + isEnabled = true + + ) + + private val treatmentCompleted = FormElement( + id = 8, + inputType = InputType.RADIO, + title = resources.getString(R.string.treatment_completed), + entries = resources.getStringArray(R.array.yes_no), + required = false, + hasDependants = true, + isEnabled = true + + ) + + private val actualTreatmentCompletionDate = FormElement( + id = 9, + inputType = InputType.DATE_PICKER, + title = resources.getString(R.string.actual_treatment_completion_date), + arrayId = -1, + max = System.currentTimeMillis(), + required = false, + hasDependants = false, + isEnabled = true + + ) + + private val treatmentOutcome = FormElement( + id = 10, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.treatment_outcome), + arrayId = R.array.tb_treatment_outcomes, + entries = resources.getStringArray(R.array.tb_treatment_outcomes), + required = false, + hasDependants = true, + isEnabled = true + + ) + + private val dateOfDeath = FormElement( + id = 11, + inputType = InputType.DATE_PICKER, + title = resources.getString(R.string.date_of_death), + arrayId = -1, + required = false, + max = System.currentTimeMillis(), + hasDependants = false, + isEnabled = true + + ) + + private val placeOfDeath = FormElement( + id = 12, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.place_of_death), + arrayId = R.array.place_of_death, + entries = resources.getStringArray(R.array.place_of_death), + required = false, + hasDependants = false, + isEnabled = true + + ) + + private val reasonForDeath = FormElement( + id = 13, + inputType = InputType.TEXT_VIEW, + title = resources.getString(R.string.reason_for_death), + required = false, + hasDependants = false, + isEnabled = true + + ) + + private val reasonForNotCompleting = FormElement( + id = 14, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.reason_for_not_completing), + required = false, + hasDependants = false, + isEnabled = true + + ) + + + + private var lastFollowUpDate: Long? = null + private var followUpCount = 0 + private var treatmentStartDateValue: Long? = null + private var regimenTypeValue: String? = null + private var isNewRecord = true + + suspend fun setUpPage( + ben: BenRegCache?, + saved: TBConfirmedTreatmentCache?, + suspectedTb: TBSuspectedCache? + ) { + val baseList = mutableListOf() + + if (saved == null) { + isNewRecord = true + treatmentStartDate.value = getDateFromLong(System.currentTimeMillis()) + reasonForDeath.value = resources.getString(R.string.tuberculosis) + + baseList.addAll(listOf( + regimenType, + treatmentStartDate, + expectedTreatmentCompletionDate, + followUpDate, + monthlyFollowUpDone, + adherenceToMedicines, + anyDiscomfort, + // treatmentCompleted, + + )) + + treatmentStartDate.max = System.currentTimeMillis() + treatmentStartDate.min = suspectedTb?.visitDate + ?.takeIf { it > 0 } + ?: getOneYearBeforeCurrentDate() + + // Enable follow-up date since treatment start date is pre-filled + val todayStart = Calendar.getInstance().apply { + set(Calendar.HOUR_OF_DAY, 0) + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + }.timeInMillis + treatmentStartDateValue = todayStart + treatmentStartDateLong = todayStart + followUpDate.min = todayStart + followUpDate.isEnabled = true + + + } else + { + isNewRecord = false + regimenType.value = getLocalValueInArray(R.array.tb_regimen_types, saved.regimenType) + followUpDate.isEnabled =true + treatmentStartDateLong = saved?.treatmentStartDate ?: 0L + lastFollowUpDateLong = saved?.followUpDate ?: 0L + + if (!saved.regimenType.isNullOrBlank()) { + regimenType.isEnabled = false + } + + saved.treatmentStartDate + ?.takeIf { it > 0 } + ?.let { + treatmentStartDate.value = getDateFromLong(it) + treatmentStartDate.isEnabled = false + updateFollowUpDateConstraints() + } + expectedTreatmentCompletionDate.value = saved.expectedTreatmentCompletionDate?.let { + getDateFromLong(it) + } + followUpDate.value = saved.followUpDate?.let { getDateFromLong(it) } + monthlyFollowUpDone.value = saved.monthlyFollowUpDone + adherenceToMedicines.value = getLocalValueInArray(R.array.adherence_options, saved.adherenceToMedicines) + anyDiscomfort.value = saved.anyDiscomfort?.let { + if (it) resources.getStringArray(R.array.yes_no)[0] + else resources.getStringArray(R.array.yes_no)[1] + } + treatmentCompleted.value = saved.treatmentCompleted?.let { + if (it) resources.getStringArray(R.array.yes_no)[0] + else resources.getStringArray(R.array.yes_no)[1] + } + actualTreatmentCompletionDate.value = saved.actualTreatmentCompletionDate?.let { + getDateFromLong(it) + } + treatmentOutcome.value = getLocalValueInArray(R.array.tb_treatment_outcomes, saved.treatmentOutcome) + dateOfDeath.value = saved.dateOfDeath?.let { getDateFromLong(it) } + placeOfDeath.value = getLocalValueInArray(R.array.place_of_death, saved.placeOfDeath) + reasonForDeath.value = saved.reasonForDeath + reasonForNotCompleting.value = saved.reasonForNotCompleting + + treatmentStartDateValue = saved.treatmentStartDate + regimenTypeValue = saved.regimenType + lastFollowUpDate = saved.followUpDate + baseList.addAll(listOf( + regimenType, + treatmentStartDate, + expectedTreatmentCompletionDate + )) + + if (saved.followUpDate != null) { + baseList.addAll(listOf( + followUpDate, + monthlyFollowUpDone, + adherenceToMedicines, + anyDiscomfort + )) + } else { + baseList.add(followUpDate) + } + + if (saved.treatmentCompleted != null) { + baseList.add(treatmentCompleted) + + if (saved.treatmentCompleted == true) { + baseList.add(actualTreatmentCompletionDate) + + if (saved.treatmentOutcome != null) { + baseList.add(treatmentOutcome) + + if (treatmentOutcome.value == treatmentOutcome.entries!![3]) { + baseList.addAll(listOf( + dateOfDeath, + placeOfDeath, + reasonForDeath + )) + } + } + } else { + baseList.add(reasonForNotCompleting) + } + } + + treatmentOutcome.value?.let { outcome -> + if (outcome == treatmentOutcome.entries!![0] || outcome == treatmentOutcome.entries!!.last()) { + baseList.forEach { it.isEnabled = false } + } + } + + } + + /* ben?.let { + treatmentStartDate.min = suspectedTb?.visitDate ?: it.regDate + }*/ + + setUpPage(baseList) + } + + override suspend fun handleListOnValueChanged(formId: Int, index: Int): Int { + when (formId) + { + regimenType.id -> { + regimenTypeValue = regimenType.value + calculateExpectedCompletionDate() + checkAndEnableTreatmentCompletion() + + } + + treatmentStartDate.id -> { + val dateLong = getLongFromDate(treatmentStartDate.value) + treatmentStartDateValue = dateLong + followUpDate.min = dateLong + followUpDate.isEnabled = true + calculateExpectedCompletionDate() + + } + + followUpDate.id -> { + val dateLong = getLongFromDate(followUpDate.value) + lastFollowUpDate = dateLong + updateMonthlyFollowUpCount() + val error = validateFollowUpDate(dateLong) + + followUpDate.errorText = error + checkAndEnableTreatmentCompletion() + + + + } + + treatmentCompleted.id -> { + val yesNoArray = resources.getStringArray(R.array.yes_no) + + return if (index == 0) { + + triggerDependants( + source = treatmentCompleted, + removeItems = listOf(reasonForNotCompleting), + addItems = listOf(actualTreatmentCompletionDate, treatmentOutcome), + position = -2 + ) + } else { + triggerDependants( + source = treatmentCompleted, + removeItems = listOf(actualTreatmentCompletionDate, treatmentOutcome, + dateOfDeath, placeOfDeath, reasonForDeath), + addItems = listOf(reasonForNotCompleting), + position = -2 + ) + } + } + + treatmentOutcome.id -> { + val treatmentOutcomes = resources.getStringArray(R.array.tb_treatment_outcomes) + + return if (treatmentOutcome.value == treatmentOutcomes[3]) { + triggerDependants( + source = treatmentOutcome, + removeItems = listOf(), + addItems = listOf(dateOfDeath, placeOfDeath, reasonForDeath), + position = -2 + ) + } else { + triggerDependants( + source = treatmentOutcome, + removeItems = listOf(dateOfDeath, placeOfDeath, reasonForDeath), + addItems = listOf(), + position = -2 + ) + } + } + + + } + return 0 + } + + private fun getFirstDayOfNextMonth(date: Long): Long { + val calendar = Calendar.getInstance() + calendar.timeInMillis = date + calendar.set(Calendar.DAY_OF_MONTH, 1) + calendar.add(Calendar.MONTH, 1) + calendar.set(Calendar.HOUR_OF_DAY, 0) + calendar.set(Calendar.MINUTE, 0) + calendar.set(Calendar.SECOND, 0) + calendar.set(Calendar.MILLISECOND, 0) + return calendar.timeInMillis + } + + private fun getLastDayOfNextMonth(date: Long): Long { + val calendar = Calendar.getInstance() + calendar.timeInMillis = date + calendar.set(Calendar.DAY_OF_MONTH, 1) + calendar.add(Calendar.MONTH, 2) + calendar.add(Calendar.DAY_OF_MONTH, -1) + calendar.set(Calendar.HOUR_OF_DAY, 23) + calendar.set(Calendar.MINUTE, 59) + calendar.set(Calendar.SECOND, 59) + calendar.set(Calendar.MILLISECOND, 999) + return calendar.timeInMillis + } + + private fun isSameMonth(date1: Long, date2: Long): Boolean { + val cal1 = Calendar.getInstance() + val cal2 = Calendar.getInstance() + cal1.timeInMillis = date1 + cal2.timeInMillis = date2 + return cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) && + cal1.get(Calendar.MONTH) == cal2.get(Calendar.MONTH) + } + + + + private fun updateFollowUpDateConstraints() { + if (treatmentStartDateLong == 0L) { + followUpDate.isEnabled = false + return + } + + followUpDate.max = System.currentTimeMillis() + + if (lastFollowUpDateLong == 0L) { + followUpDate.min = treatmentStartDateLong + followUpDate.isEnabled = true + } else { + nextFollowUpMonthStart = getFirstDayOfNextMonth(lastFollowUpDateLong) + nextFollowUpMonthEnd = getLastDayOfNextMonth(lastFollowUpDateLong) + + followUpDate.min = nextFollowUpMonthStart + followUpDate.isEnabled = System.currentTimeMillis() >= nextFollowUpMonthStart + } + } + + fun validateAllFields(): Boolean { + + val followUpDateValid = validateCurrentFollowUpDate() + if (regimenType.required && (regimenType.value == null || regimenType.value.isNullOrEmpty())) { + regimenType.errorText = "Regimen type is required" + return false + } + if (treatmentStartDate.required && (treatmentStartDate.value == null || treatmentStartDate.value.isNullOrEmpty())) { + treatmentStartDate.errorText = "Treatment start date is required" + return false + } + + return followUpDateValid + } + fun validateCurrentFollowUpDate(): Boolean { + val dateString = followUpDate.value + if (dateString == null || dateString.isEmpty()) { + followUpDate.errorText = "Follow-up date is required" + return false + } + + val dateLong = getLongFromDate(dateString) + val error = validateFollowUpDate(dateLong) + followUpDate.errorText = error + return error == null + } + + private fun validateFollowUpDate(selectedDate: Long): String? { + + if (selectedDate > System.currentTimeMillis()) { + return "Follow-up date cannot be in the future" + } + + if (selectedDate < treatmentStartDateLong) { + return "Follow-up date cannot be before treatment start date" + } + + if (lastFollowUpDateLong > 0) { + if (selectedDate <= lastFollowUpDateLong) { + return "Follow-up date must be after last follow-up date" + } + + if (isSameMonth(selectedDate, lastFollowUpDateLong)) { + return "Only one follow-up is allowed per month" + } + } + + return null + } + + + private fun calculateExpectedCompletionDate() { + if (regimenTypeValue == null || treatmentStartDateValue == null) return + + val calendar = Calendar.getInstance().apply { + timeInMillis = treatmentStartDateValue!! + } + + when (regimenTypeValue) { + resources.getStringArray(R.array.tb_regimen_types)[0], + resources.getStringArray(R.array.tb_regimen_types)[3], + resources.getStringArray(R.array.tb_regimen_types)[4] + -> { + calendar.add(Calendar.MONTH, 6) + expectedTreatmentCompletionDate.value = getDateFromLong(calendar.timeInMillis) + } + resources.getStringArray(R.array.tb_regimen_types)[1] -> { + val minDate = Calendar.getInstance().apply { + timeInMillis = treatmentStartDateValue!! + add(Calendar.MONTH, 9) + }.timeInMillis + val maxDate = Calendar.getInstance().apply { + timeInMillis = treatmentStartDateValue!! + add(Calendar.MONTH, 12) + }.timeInMillis + expectedTreatmentCompletionDate.value = "${getDateFromLong(minDate)} to ${getDateFromLong(maxDate)}" + } + resources.getStringArray(R.array.tb_regimen_types)[2] -> { // Longer Regimen (18–24 Months) + val minDate = Calendar.getInstance().apply { + timeInMillis = treatmentStartDateValue!! + add(Calendar.MONTH, 18) + }.timeInMillis + val maxDate = Calendar.getInstance().apply { + timeInMillis = treatmentStartDateValue!! + add(Calendar.MONTH, 24) + }.timeInMillis + expectedTreatmentCompletionDate.value = "${getDateFromLong(minDate)} to ${getDateFromLong(maxDate)}" + } + } + } + + private fun checkAndEnableTreatmentCompletion() { + if (regimenTypeValue == null || followUpCount == 0) return + + val requiredFollowUps = when (regimenTypeValue) { + resources.getStringArray(R.array.tb_regimen_types)[0], + resources.getStringArray(R.array.tb_regimen_types)[3], + resources.getStringArray(R.array.tb_regimen_types)[4] + -> 5 + resources.getStringArray(R.array.tb_regimen_types)[1] -> 9 + resources.getStringArray(R.array.tb_regimen_types)[2] -> 18 + else -> 0 + } + + if (followUpCount >= requiredFollowUps) { + treatmentCompleted.isEnabled = true + triggerDependants(source = followUpDate, addItems = listOf(treatmentCompleted),removeItems = listOf(), position = -2) + } else { + treatmentCompleted.isEnabled = false + treatmentCompleted.value = null + + triggerDependants( + source = treatmentCompleted, + removeItems = listOf(actualTreatmentCompletionDate, treatmentOutcome, + reasonForNotCompleting, dateOfDeath, placeOfDeath, reasonForDeath), + addItems = listOf(), + position = -2 + ) + } + } + + private fun updateMonthlyFollowUpCount() { + if (lastFollowUpDate == null || treatmentStartDateValue == null) + { monthlyFollowUpDone.value = resources.getString(R.string.month_format, followUpCount) + return} + + + val calendarStart = Calendar.getInstance().apply { + timeInMillis = treatmentStartDateValue!! + } + val calendarFollowUp = Calendar.getInstance().apply { + timeInMillis = lastFollowUpDate!! + } + + val yearDiff = calendarFollowUp.get(Calendar.YEAR) - calendarStart.get(Calendar.YEAR) + val monthDiff = calendarFollowUp.get(Calendar.MONTH) - calendarStart.get(Calendar.MONTH) + + followUpCount = (yearDiff * 12) + monthDiff + 1 + + monthlyFollowUpDone.value = resources.getString(R.string.month_format, followUpCount) + + checkAndEnableTreatmentCompletion() + } + + private fun validateMonthlyFollowUp(newDate: Long): Boolean { + val newCalendar = Calendar.getInstance().apply { timeInMillis = newDate } + val newMonth = newCalendar.get(Calendar.MONTH) + val newYear = newCalendar.get(Calendar.YEAR) + + + return true + } + + override fun mapValues(cacheModel: FormDataModel, pageNumber: Int) { + (cacheModel as TBConfirmedTreatmentCache).let { form -> + form.regimenType = getEnglishValueInArray(R.array.tb_regimen_types, regimenType.value) + form.treatmentStartDate = getLongFromDate(treatmentStartDate.value) + form.expectedTreatmentCompletionDate = getLongFromDate(expectedTreatmentCompletionDate.value) + form.followUpDate = getLongFromDate(followUpDate.value) + form.monthlyFollowUpDone = monthlyFollowUpDone.value + form.adherenceToMedicines = getEnglishValueInArray(R.array.adherence_options, adherenceToMedicines.value) + form.anyDiscomfort = anyDiscomfort.value == anyDiscomfort.entries!![0] + form.treatmentCompleted = treatmentCompleted.value?.let { + it == treatmentCompleted.entries!![0] + } + form.actualTreatmentCompletionDate = getLongFromDate(actualTreatmentCompletionDate.value) + form.treatmentOutcome = getEnglishValueInArray(R.array.tb_treatment_outcomes, treatmentOutcome.value) + form.dateOfDeath = getLongFromDate(dateOfDeath.value) + form.placeOfDeath = getEnglishValueInArray(R.array.place_of_death, placeOfDeath.value) + form.reasonForDeath = reasonForDeath.value ?: resources.getString(R.string.tuberculosis) + form.reasonForNotCompleting = reasonForNotCompleting.value + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/TBScreeningDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/TBScreeningDataset.kt index 300c4b33b..a373a2f92 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/configuration/TBScreeningDataset.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/TBScreeningDataset.kt @@ -12,6 +12,19 @@ class TBScreeningDataset( context: Context, currentLanguage: Languages ) : Dataset(context, currentLanguage) { + private val symptomaticLabel = FormElement( + id = 14, + inputType = InputType.HEADLINE, + title = resources.getString(R.string.symptomatic_tb_screening), + required = false + ) + + private val checkSymptomsLabel = FormElement( + id = 14, + inputType = InputType.HEADLINE, + title = resources.getString(R.string.check_if_the_person_has_any_of_these_symptoms), + required = false + ) private val dateOfVisit = FormElement( id = 1, inputType = InputType.DATE_PICKER, @@ -97,8 +110,98 @@ class TBScreeningDataset( hasDependants = false ) + private var riseOfFever = FormElement( + id = 7, + inputType = InputType.RADIO, + title = resources.getString(R.string.tb_rise_of_fever), + entries = resources.getStringArray(R.array.yes_no), + doubleStar = true, + required = true, + hasDependants = false + ) + + + private var lossOfAppetite = FormElement( + id = 7, + inputType = InputType.RADIO, + title = resources.getString(R.string.tb_loss_of_appetite), + entries = resources.getStringArray(R.array.yes_no), + doubleStar = true, + required = true, + hasDependants = false + ) + private val aSymptomaticLabel = FormElement( + id = 14, + inputType = InputType.HEADLINE, + title = resources.getString(R.string.tb_asymptomatic_symptoms), + required = false + ) + private val checkSymptomsLabel1 = FormElement( + id = 14, + inputType = InputType.HEADLINE, + title = resources.getString(R.string.check_if_the_person_has_any_of_these_symptoms), + required = false + ) + private var age = FormElement( + id = 7, + inputType = InputType.RADIO, + title = resources.getString(R.string.tb_age_more_than_60), + entries = resources.getStringArray(R.array.yes_no), + doubleStar = true, + required = true, + hasDependants = false + ) + private var diabetic = FormElement( + id = 7, + inputType = InputType.RADIO, + title = resources.getString(R.string.tb_diabetic), + entries = resources.getStringArray(R.array.yes_no), + doubleStar = true, + required = true, + hasDependants = false + ) + private var tobaccoUser = FormElement( + id = 7, + inputType = InputType.RADIO, + title = resources.getString(R.string.tb_tobacco_user), + entries = resources.getStringArray(R.array.yes_no), + doubleStar = true, + required = true, + hasDependants = false + ) + private var bmi = FormElement( + id = 7, + inputType = InputType.RADIO, + title = resources.getString(R.string.tb_bmi_less_than_18_5), + entries = resources.getStringArray(R.array.yes_no), + doubleStar = true, + required = true, + hasDependants = false + ) + private var contactWithTBPatient = FormElement( + id = 7, + inputType = InputType.RADIO, + title = resources.getString(R.string.tb_contact_with_patient_on_treatment), + entries = resources.getStringArray(R.array.yes_no), + doubleStar = true, + required = true, + hasDependants = false + ) + private var historyOfTBInLastFiveYrs = FormElement( + id = 7, + inputType = InputType.RADIO, + title = resources.getString(R.string.tb_history_last_5_years), + entries = resources.getStringArray(R.array.yes_no), + doubleStar = true, + required = true, + hasDependants = false + ) + + suspend fun setUpPage(ben: BenRegCache?, saved: TBScreeningCache?) { val list = mutableListOf( + symptomaticLabel, + checkSymptomsLabel, dateOfVisit, isCoughing, bloodInSputum, @@ -107,7 +210,17 @@ class TBScreeningDataset( nightSweats, historyOfTB, currentlyTakingDrugs, - familyHistoryTB + familyHistoryTB, + riseOfFever, + lossOfAppetite, + aSymptomaticLabel, + checkSymptomsLabel1, + age, + diabetic, + tobaccoUser, + bmi, + contactWithTBPatient, + historyOfTBInLastFiveYrs ) if (saved == null) { dateOfVisit.value = getDateFromLong(System.currentTimeMillis()) @@ -145,6 +258,41 @@ class TBScreeningDataset( if (saved.familySufferingFromTB == true) resources.getStringArray(R.array.yes_no)[0] else resources.getStringArray( R.array.yes_no )[1] + + riseOfFever.value = + if (saved.riseOfFever == true) resources.getStringArray(R.array.yes_no)[0] else resources.getStringArray( + R.array.yes_no + )[1] + + lossOfAppetite.value = + if (saved.lossOfAppetite == true) resources.getStringArray(R.array.yes_no)[0] else resources.getStringArray( + R.array.yes_no + )[1] + age.value = + if (saved.age == true) resources.getStringArray(R.array.yes_no)[0] else resources.getStringArray( + R.array.yes_no + )[1] + diabetic.value = + if (saved.diabetic == true) resources.getStringArray(R.array.yes_no)[0] else resources.getStringArray( + R.array.yes_no + )[1] + tobaccoUser.value = + if (saved.tobaccoUser == true) resources.getStringArray(R.array.yes_no)[0] else resources.getStringArray( + R.array.yes_no + )[1] + bmi.value = + if (saved.bmi == true) resources.getStringArray(R.array.yes_no)[0] else resources.getStringArray( + R.array.yes_no + )[1] + contactWithTBPatient.value = + if (saved.contactWithTBPatient == true) resources.getStringArray(R.array.yes_no)[0] else resources.getStringArray( + R.array.yes_no + )[1] + + historyOfTBInLastFiveYrs.value = + if (saved.historyOfTBInLastFiveYrs == true) resources.getStringArray(R.array.yes_no)[0] else resources.getStringArray( + R.array.yes_no + )[1] } @@ -175,6 +323,33 @@ class TBScreeningDataset( currentlyTakingDrugs.value == resources.getStringArray(R.array.yes_no)[0] form.familySufferingFromTB = familyHistoryTB.value == resources.getStringArray(R.array.yes_no)[0] + form.riseOfFever = riseOfFever.value == resources.getStringArray(R.array.yes_no)[0] + form.lossOfAppetite = + lossOfAppetite.value == resources.getStringArray(R.array.yes_no)[0] + form.age = + age.value == resources.getStringArray(R.array.yes_no)[0] + form.diabetic = + diabetic.value == resources.getStringArray(R.array.yes_no)[0] + form.tobaccoUser = + tobaccoUser.value == resources.getStringArray(R.array.yes_no)[0] + form.bmi = + bmi.value == resources.getStringArray(R.array.yes_no)[0] + form.contactWithTBPatient = + contactWithTBPatient.value == resources.getStringArray(R.array.yes_no)[0] + form.historyOfTBInLastFiveYrs = + historyOfTBInLastFiveYrs.value == resources.getStringArray(R.array.yes_no)[0] + form.sympotomatic = isSymptomatic() + form.asymptomatic = isAsymptomatic() + + if (isSymptomatic()=="Yes" && isAsymptomatic() =="No"){ + form.recommandateTest = "Sputum Test" + }else if(isSymptomatic() == "No"&& isAsymptomatic() =="Yes"){ + form.recommandateTest = "Chest X-Ray" + }else if(isSymptomatic() == "Yes"&& isAsymptomatic() =="Yes"){ + form.recommandateTest = "Both" + }else{ + form.recommandateTest = "None" + } } } @@ -182,19 +357,73 @@ class TBScreeningDataset( fun updateBen(benRegCache: BenRegCache) { benRegCache.genDetails?.let { it.reproductiveStatus = - englishResources.getStringArray(R.array.nbr_reproductive_status_array)[1] + englishResources.getStringArray(R.array.nbr_reproductive_status_array2)[1] it.reproductiveStatusId = 2 } if (benRegCache.processed != "N") benRegCache.processed = "U" } + fun referHwcFacility():String?{ + return if (isCoughing.value == resources.getStringArray(R.array.yes_no)[0] || + bloodInSputum.value == resources.getStringArray(R.array.yes_no)[0] || + isFever.value == resources.getStringArray(R.array.yes_no)[0] || + nightSweats.value == resources.getStringArray(R.array.yes_no)[0] || + lossOfWeight.value == resources.getStringArray(R.array.yes_no)[0] || + historyOfTB.value == resources.getStringArray(R.array.yes_no)[0]|| + + riseOfFever.value == resources.getStringArray(R.array.yes_no)[0] || + lossOfAppetite.value == resources.getStringArray(R.array.yes_no)[0] || + age.value == resources.getStringArray(R.array.yes_no)[0] || + diabetic.value == resources.getStringArray(R.array.yes_no)[0] || + tobaccoUser.value == resources.getStringArray(R.array.yes_no)[0] || + bmi.value == resources.getStringArray(R.array.yes_no)[0] || + contactWithTBPatient.value == resources.getStringArray(R.array.yes_no)[0] || + historyOfTBInLastFiveYrs.value == resources.getStringArray(R.array.yes_no)[0] + ) + resources.getString(R.string.refer_to_hwc_facility_alert) else null + + } + + fun isSymptomatic():String{ + return if (isCoughing.value == resources.getStringArray(R.array.yes_no)[0] || + bloodInSputum.value == resources.getStringArray(R.array.yes_no)[0] || + isFever.value == resources.getStringArray(R.array.yes_no)[0] || + nightSweats.value == resources.getStringArray(R.array.yes_no)[0] || + lossOfWeight.value == resources.getStringArray(R.array.yes_no)[0] || + historyOfTB.value == resources.getStringArray(R.array.yes_no)[0]|| + riseOfFever.value == resources.getStringArray(R.array.yes_no)[0] || + lossOfAppetite.value == resources.getStringArray(R.array.yes_no)[0] + ) + "Yes" else "No" + } + fun isAsymptomatic():String{ + return if ( + age.value == resources.getStringArray(R.array.yes_no)[0] || + diabetic.value == resources.getStringArray(R.array.yes_no)[0] || + tobaccoUser.value == resources.getStringArray(R.array.yes_no)[0] || + bmi.value == resources.getStringArray(R.array.yes_no)[0] || + contactWithTBPatient.value == resources.getStringArray(R.array.yes_no)[0] || + historyOfTBInLastFiveYrs.value == resources.getStringArray(R.array.yes_no)[0] + ) + "Yes" else "No" + } + fun isTbSuspected(): String? { return if (isCoughing.value == resources.getStringArray(R.array.yes_no)[0] || bloodInSputum.value == resources.getStringArray(R.array.yes_no)[0] || isFever.value == resources.getStringArray(R.array.yes_no)[0] || nightSweats.value == resources.getStringArray(R.array.yes_no)[0] || lossOfWeight.value == resources.getStringArray(R.array.yes_no)[0] || - historyOfTB.value == resources.getStringArray(R.array.yes_no)[0] + historyOfTB.value == resources.getStringArray(R.array.yes_no)[0]|| + + riseOfFever.value == resources.getStringArray(R.array.yes_no)[0] || + lossOfAppetite.value == resources.getStringArray(R.array.yes_no)[0] || + age.value == resources.getStringArray(R.array.yes_no)[0] || + diabetic.value == resources.getStringArray(R.array.yes_no)[0] || + tobaccoUser.value == resources.getStringArray(R.array.yes_no)[0] || + bmi.value == resources.getStringArray(R.array.yes_no)[0] || + contactWithTBPatient.value == resources.getStringArray(R.array.yes_no)[0] || + historyOfTBInLastFiveYrs.value == resources.getStringArray(R.array.yes_no)[0] ) resources.getString(R.string.tb_suspected_alert) else null } diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/UWINDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/UWINDataset.kt new file mode 100644 index 000000000..2c8423d07 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/UWINDataset.kt @@ -0,0 +1,154 @@ +package org.piramalswasthya.sakhi.configuration + +import android.content.Context +import android.net.Uri +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.helpers.Languages +import org.piramalswasthya.sakhi.model.FormElement +import org.piramalswasthya.sakhi.model.InputType +import org.piramalswasthya.sakhi.model.UwinCache +import java.util.Calendar + +class UWINDataset(context: Context, language: Languages) : Dataset(context, language) { + + + companion object { + + private fun getMinDate(monthsBack: Int = 2): Long { + return Calendar.getInstance().apply { + add(Calendar.MONTH, -monthsBack) + }.timeInMillis + } + } + + private val uWinSessionDate = FormElement( + id = 117, + inputType = InputType.DATE_PICKER, + title = resources.getString(R.string.u_win_session_date), + max = System.currentTimeMillis(), + min = getMinDate(2), + required = true, + isEnabled = true + ) + + private val place = FormElement( + id = 118, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.place), + arrayId = R.array.place_of_delivery_options, + entries = resources.getStringArray(R.array.place_of_delivery_options), + required = true, + ) + + private val participant = FormElement( + id = 119, + inputType = InputType.EDIT_TEXT, + title = resources.getString(R.string.no_of_participants_attended), + required = true, + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + etMaxLength = 3, + isEnabled = true + ) + + private val uploadSummary1 = FormElement( + id = 120, + inputType = InputType.FILE_UPLOAD, + title = resources.getString(R.string.file_upload_n, 1), + required = false + ) + + private val uploadSummary2 = FormElement( + id = 121, + inputType = InputType.FILE_UPLOAD, + title = resources.getString(R.string.file_upload_n, 2), + required = false + ) + + suspend fun setFirstPage(saved: Boolean, cache: UwinCache?) { + val list = listOf( + uWinSessionDate, + place, + participant, + uploadSummary1, + uploadSummary2 + ) + + if (saved && cache != null) { + uWinSessionDate.value = getDateFromLong(cache.sessionDate) + place.value = getLocalValueInArray(R.array.place_of_delivery_options, cache.place) + participant.value = cache.participantsCount.toString() + uploadSummary1.value = cache.uploadedFiles1 + uploadSummary2.value = cache.uploadedFiles2 + + + uWinSessionDate.isEnabled = false + place.isEnabled = false + participant.isEnabled = false + } else { + + uWinSessionDate.value = null + place.value = null + participant.value = null + uploadSummary1.value = null + uploadSummary2.value = null + + + uWinSessionDate.isEnabled = true + place.isEnabled = true + participant.isEnabled = true + } + + setUpPage(list) + } + + override suspend fun handleListOnValueChanged(formId: Int, index: Int): Int { + when (formId) { + uWinSessionDate.id -> { + uWinSessionDate.errorText = ((if (uWinSessionDate.value.isNullOrEmpty()) + emitAlertErrorMessage(R.string.form_input_empty_error) + else null) as String?) + } + + + participant.id -> { + participant.errorText = (if (participant.value.isNullOrEmpty()) + emitAlertErrorMessage(R.string.form_input_empty_error) + else null) as String? + } + + + } + + + return -1 + } + + override fun mapValues(cacheModel: FormDataModel, pageNumber: Int) { + (cacheModel as UwinCache).let { + it.sessionDate = uWinSessionDate.value?.let { getLongFromDate(it) }!! + it.place = place.getEnglishStringFromPosition(place.getPosition()) + it.participantsCount = participant.value!!.toInt() + it.uploadedFiles1 = uploadSummary1.value?.takeIf { it.isNotEmpty() } + it.uploadedFiles2 = uploadSummary2.value?.takeIf { it.isNotEmpty() } + } + } + + fun getUwinFileIndex1() = getIndexById(uploadSummary1.id) + fun getUwinFileIndex2() = getIndexById(uploadSummary2.id) + + + fun setImageUriToFormElement(lastImageFormId: Int, dpUri: Uri) { + when (lastImageFormId) { + 120 -> { + uploadSummary1.value = dpUri.toString() + uploadSummary1.errorText = null + } + + 121 -> { + uploadSummary2.value = dpUri.toString() + uploadSummary2.errorText = null + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/VHNCDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/VHNCDataset.kt new file mode 100644 index 000000000..2e6cdf53f --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/VHNCDataset.kt @@ -0,0 +1,310 @@ +package org.piramalswasthya.sakhi.configuration + +import android.content.Context +import android.net.Uri +import android.util.Log +import org.piramalswasthya.sakhi.BuildConfig +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.helpers.Konstants +import org.piramalswasthya.sakhi.helpers.Languages +import org.piramalswasthya.sakhi.model.BenRegCache +import org.piramalswasthya.sakhi.model.FormElement +import org.piramalswasthya.sakhi.model.InputType +import org.piramalswasthya.sakhi.model.InputType.DATE_PICKER +import org.piramalswasthya.sakhi.model.InputType.EDIT_TEXT +import org.piramalswasthya.sakhi.model.InputType.IMAGE_VIEW +import org.piramalswasthya.sakhi.model.InputType.RADIO +import org.piramalswasthya.sakhi.model.VHNCCache +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Locale + +class VHNCDataset( + context: Context, currentLanguage: Languages, +) : Dataset(context, currentLanguage) { + + companion object { + private fun getCurrentDateString(): String { + val calendar = Calendar.getInstance() + val mdFormat = SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH) + return mdFormat.format(calendar.time) + } + private fun getTwoMonthsBackMillis(): Long { + return Calendar.getInstance().apply { + add(Calendar.MONTH, -2) + }.timeInMillis + } + } + + + private val vhncDate = FormElement( + id = 2, + inputType = DATE_PICKER, + title = resources.getString(R.string.vhsnc_meeting_date), + arrayId = -1, + required = true, + min = getTwoMonthsBackMillis(), + max = System.currentTimeMillis() + ) + private val place = FormElement( + id = 3, + inputType = InputType.DROPDOWN, + title = context.getString(R.string.place_of_vhsnc_meeting), + entries = resources.getStringArray(R.array.place_of_vhsnc), + arrayId = R.array.place_of_vhsnc, + required = true, + ) + + private val villageName = FormElement( + id = 9, + inputType = EDIT_TEXT, + title = resources.getString(R.string.village_name), + arrayId = -1, + required = false, + etInputType = android.text.InputType.TYPE_CLASS_TEXT or android.text.InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS + ) + + private val noOfBeneficiariesAttended = FormElement( + id = 4, + inputType = EDIT_TEXT, + title = resources.getString(R.string.no_b_attended), + arrayId = -1, + value = "0", + required = false, + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + isMobileNumber = true, + etMaxLength = 3, + max = 999, + min = 1 + ) + + private val noOfANM = FormElement( + id = 11, + inputType = EDIT_TEXT, + title = resources.getString(R.string.number_of_anm_mpw_attended), + arrayId = -1, + value = "0", + required = false, + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + isMobileNumber = true, + etMaxLength = 3, + max = 999, + min = 1 + ) + private val noOfAWW = FormElement( + id = 12, + inputType = EDIT_TEXT, + title = resources.getString(R.string.number_of_aww_attended), + arrayId = -1, + value = "0", + required = false, + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + isMobileNumber = true, + etMaxLength = 3, + max = 999, + min = 1 + ) + private val noOfPW = FormElement( + id = 13, + inputType = EDIT_TEXT, + title = resources.getString(R.string.number_of_pregnant_woman), + arrayId = -1, + value = "0", + required = false, + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + isMobileNumber = true, + etMaxLength = 3, + max = 999, + min = 1 + ) + private val noOfLM = FormElement( + id = 14, + inputType = EDIT_TEXT, + title = resources.getString(R.string.number_of_lactating_mothers), + arrayId = -1, + value = "0", + required = false, + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + isMobileNumber = true, + etMaxLength = 3, + max = 999, + min = 1 + ) + private val noOfCommentte = FormElement( + id = 15, + inputType = EDIT_TEXT, + title = resources.getString(R.string.number_of_committee_members_present), + arrayId = -1, + value = "0", + required = false, + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + isMobileNumber = true, + etMaxLength = 3, + max = 999, + min = 1 + ) + + private val followupPrevius = FormElement( + id = 16, + inputType = RADIO, + title = resources.getString(R.string.follow_up_the_previous_meeting_minutes), + arrayId = R.array.yes_no, + entries = resources.getStringArray(R.array.yes_no), + required = false, + hasDependants = true, + ) + private val pic1 = FormElement( + id = 1, + inputType = InputType.FILE_UPLOAD, + title = resources.getString(R.string.upload_image), + arrayId = -1, + required = false, + ) + private val pic2 = FormElement( + id = 2, + inputType = InputType.FILE_UPLOAD, + title = resources.getString(R.string.upload_image), + arrayId = -1, + required = false, + ) + + suspend fun setUpPage( vhnd: VHNCCache?) { + + + if (pic1.value.isNullOrBlank()) { + pic1.value = "default" + } + + if (pic2.value.isNullOrBlank()) { + pic2.value = "default" + } + val list = mutableListOf( + vhncDate, + place, + ) + + + if (BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true)) { + list.add(villageName) + list.add(noOfBeneficiariesAttended) + list.add(noOfANM) + list.add(noOfAWW) + list.add(noOfPW) + list.add(noOfLM) + list.add(noOfCommentte) + list.add(followupPrevius) + list.add(pic1) + list.add(pic2) + + noOfBeneficiariesAttended.title= + resources.getString(R.string.number_of_mitanins_participated_in_vhsnc_meeting) + + } + else{ + list.add(noOfBeneficiariesAttended) + list.add(pic1) + list.add(pic2) + noOfBeneficiariesAttended.title= resources.getString(R.string.number_of_asha_participated_in_vhsnc_meeting) + + } + vhncDate.value = getCurrentDateString() + vhnd?.let { + vhncDate.value = it.vhncDate + pic1.value = it.image1 + pic2.value = it.image2 + place.value = getLocalValueInArray(R.array.place_of_vhsnc, it.place) + villageName.value = it.villageName?.takeIf { v -> v.isNotEmpty() && v != "null" } ?: "" + noOfBeneficiariesAttended.value = (it.noOfBeneficiariesAttended ?: 0).toString() + noOfANM.value = (it.anm ?: 0).toString() + noOfAWW.value = (it.aww ?: 0).toString() + noOfPW.value = (it.noOfPragnentWoment ?: 0).toString() + noOfLM.value = (it.noOfLactingMother ?: 0).toString() + noOfCommentte.value = (it.noOfCommittee ?: 0).toString() + followupPrevius.value = it.followupPrevius?.let { value -> + val options = resources.getStringArray(R.array.yes_no) + if (value) options[0] else options[1] + } + } + setUpPage(list) + } + + + override suspend fun handleListOnValueChanged(formId: Int, index: Int): Int { + return when (formId) { + place.id -> { + validateEmptyOnEditText(place) + validateAllAlphabetsSpecialAndNumericOnEditText(place) + -1 + } + + noOfBeneficiariesAttended.id -> { + validateEmptyOnEditText(noOfBeneficiariesAttended) + -1 + } + + else -> { + -1 + } + } + } + + override fun mapValues(cacheModel: FormDataModel, pageNumber: Int) { + (cacheModel as VHNCCache).let { form -> + + form.vhncDate = vhncDate.value!! + form.place = getEnglishValueInArray(R.array.place_of_vhsnc, place.value) + form.noOfBeneficiariesAttended = noOfBeneficiariesAttended.value!!.toInt() + form.image1 = pic1.value?.takeIf { it.isNotEmpty() } + form.image2 = pic2.value?.takeIf { it.isNotEmpty() } + form.villageName = villageName.value + form.anm = noOfANM.value?.trim()?.toIntOrNull() ?: 0 + form.aww = noOfAWW.value?.trim()?.toIntOrNull() ?: 0 + form.noOfPragnentWoment = noOfPW.value?.trim()?.toIntOrNull() ?: 0 + form.noOfLactingMother = noOfLM.value?.trim()?.toIntOrNull() ?: 0 + form.noOfCommittee = noOfCommentte.value?.trim()?.toIntOrNull() ?: 0 + form.followupPrevius= followupPrevius.value?.let { value -> + val options = resources.getStringArray(R.array.yes_no) + when (value.trim()) { + options[0] -> true + options[1] -> false + else -> null + } + } + + } + + } + + fun getFileIndex1() = getIndexById(pic1.id) + fun getFileIndex2() = getIndexById(pic2.id) + + +// fun setImageUriToFormElement(lastImageFormId: Int, dpUri: Uri) { +// when (lastImageFormId) { +// 1 -> { +// pic1.value = dpUri.toString() +// pic1.errorText = null +// } +// +// 2-> { +// pic2.value = dpUri.toString() +// pic2.errorText = null +// } +// } +// } + + fun setImageUriToFormElement(lastImageFormId: Int, dpUri: Uri) { + when (lastImageFormId) { + pic1.id -> { + pic1.value = dpUri.toString() + pic1.errorText = null + } + pic2.id -> { + pic2.value = dpUri.toString() + pic2.errorText = null + } + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/VHNDDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/VHNDDataset.kt new file mode 100644 index 000000000..801931c05 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/VHNDDataset.kt @@ -0,0 +1,361 @@ +package org.piramalswasthya.sakhi.configuration + +import android.content.Context +import android.net.Uri +import org.piramalswasthya.sakhi.BuildConfig +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.helpers.Languages +import org.piramalswasthya.sakhi.model.FormElement +import org.piramalswasthya.sakhi.model.InputType +import org.piramalswasthya.sakhi.model.InputType.DATE_PICKER +import org.piramalswasthya.sakhi.model.InputType.EDIT_TEXT +import org.piramalswasthya.sakhi.model.InputType.IMAGE_VIEW +import org.piramalswasthya.sakhi.model.VHNDCache +import org.piramalswasthya.sakhi.utils.HelperUtil.parseSelections +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Locale + +class VHNDDataset( + context: Context, currentLanguage: Languages +) : Dataset(context, currentLanguage) { + + companion object { + private fun getCurrentDateString(): String { + val calendar = Calendar.getInstance() + val mdFormat = SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH) + return mdFormat.format(calendar.time) + } + + } + + + private val vhndDate = FormElement( + id = 2, + inputType = DATE_PICKER, + title = resources.getString(R.string.vhnd_date), + arrayId = -1, + required = true, + min = System.currentTimeMillis() - (60L * 24 * 60 * 60 * 1000), + max = System.currentTimeMillis() + ) + private val place = FormElement( + id = 3, + inputType = InputType.DROPDOWN, + title = resources.getString(R.string.place_of_vhnd), + entries = resources.getStringArray(R.array.place_of_vhsnc), + arrayId = R.array.place_of_vhsnc, + required = true, + allCaps = true, + ) + + private val noOfBeneficiariesAttended = FormElement( + id = 4, + inputType = EDIT_TEXT, + title = resources.getString(R.string.total_no_b_attended), + arrayId = -1, + required = true, + value = "0", + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + isMobileNumber = true, + etMaxLength = 3, + max = 999, + min = 0 + ) + + private val noOfPWAttended = FormElement( + id = 5, + inputType = EDIT_TEXT, + title = resources.getString(R.string.total_no_pw_attended), + arrayId = -1, + required = false, + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + isMobileNumber = true, + etMaxLength = 3, + max = 999, + min = 0 + ) + private val noOflactingMotherAttended = FormElement( + id = 6, + inputType = EDIT_TEXT, + title = resources.getString(R.string.total_no_lacting_mother_attended), + arrayId = -1, + required = false, + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + etMaxLength = 3, + max = 999, + min = 0 + ) + private val noOfchildrenAttended = FormElement( + id = 7, + inputType = EDIT_TEXT, + title = resources.getString(R.string.total_no_children_attended), + arrayId = -1, + required = false, + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + etMaxLength = 3, + max = 999, + min = 0 + ) + private val heading = FormElement( + id = 14, + inputType = InputType.HEADLINE, + title = resources.getString(R.string.health_nutrition), + arrayId = -1, + required = false, + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + isMobileNumber = true, + etMaxLength = 3, + max = 999, + min = 0 + ) + + private val knowOfBalanceDiet = FormElement( + id = 8, + inputType = InputType.CHECKBOXES, + title = resources.getString(R.string.know_balance_diet), + arrayId = -1, + required = false, + entries = resources.getStringArray(R.array.diet_plane), + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + ) + private val careDuringPregnancy = FormElement( + id = 9, + inputType = InputType.CHECKBOXES, + title = resources.getString(R.string.care_d_pregnancy), + arrayId = -1, + required = false, + entries = resources.getStringArray(R.array.care_pregnancy), + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + ) + private val importBreastFeeding = FormElement( + id = 10, + inputType = InputType.CHECKBOXES, + title = resources.getString(R.string.imp_brestfeeding_title), + arrayId = -1, + required = false, + entries = resources.getStringArray(R.array.import_brestfeeding), + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + ) + private val complementFeeding = FormElement( + id = 11, + inputType = InputType.CHECKBOXES, + title = resources.getString(R.string.com_feeding_title), + arrayId = -1, + required = false, + entries = resources.getStringArray(R.array.complementary_feeding), + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + ) + private val hygieneSenitation = FormElement( + id = 12, + inputType = InputType.CHECKBOXES, + title = resources.getString(R.string.hygiene_title), + arrayId = -1, + required = false, + entries = resources.getStringArray(R.array.hygiene_senetigation), + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + ) + private val fPlanning = FormElement( + id = 13, + inputType = InputType.CHECKBOXES, + title = resources.getString(R.string.family_planning_title), + arrayId = -1, + required = false, + entries = resources.getStringArray(R.array.family_planning_health), + etInputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL, + ) + + private val pic1 = FormElement( + id = 1, + inputType = InputType.FILE_UPLOAD, + title = resources.getString(R.string.upload_image), + arrayId = -1, + required = false, + ) + private val pic2 = FormElement( + id = 2, + inputType = InputType.FILE_UPLOAD, + title = resources.getString(R.string.upload_image), + arrayId = -1, + required = false, + ) + + suspend fun setUpPage(vhnd: VHNDCache?) { + + + if (pic1.value.isNullOrBlank()) { + pic1.value = "default" + } + + if (pic2.value.isNullOrBlank()) { + pic2.value = "default" + } + + val list = mutableListOf( + vhndDate, + place, + noOfBeneficiariesAttended + ) + + if (BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true)) { + list.addAll( + listOf( + noOfPWAttended, + noOflactingMotherAttended, + noOfchildrenAttended, + heading, + knowOfBalanceDiet, + careDuringPregnancy, + importBreastFeeding, + complementFeeding, + hygieneSenitation, + fPlanning + + ) + ) + } + + list.addAll( + listOf( + pic1, + pic2 + ) + ) + + + vhndDate.value = getCurrentDateString() + vhnd?.let { + vhndDate.value = it.vhndDate + pic1.value = it.image1 + pic2.value = it.image2 + place.value = getLocalValueInArray(R.array.place_of_vhsnc, it.place) + noOfBeneficiariesAttended.value = (it.noOfBeneficiariesAttended ?: 0).toString() + if (BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true)) { + noOfPWAttended.value = it.pregnantWomenAnc?.takeIf { v -> v.isNotEmpty() && v != "null" } ?: "0" + noOflactingMotherAttended.value = it.lactatingMothersPnc?.takeIf { v -> v.isNotEmpty() && v != "null" } ?: "0" + noOfchildrenAttended.value = it.childrenImmunization?.takeIf { v -> v.isNotEmpty() && v != "null" } ?: "0" + val parsedKBDList = parseSelections(it.knowledgeBalancedDiet, knowOfBalanceDiet.entries!!) + knowOfBalanceDiet.value = if (parsedKBDList.isNotEmpty()) { + parsedKBDList.joinToString("|") + } else { + it.knowledgeBalancedDiet ?: "" + } + val parsedCDPList = parseSelections(it.careDuringPregnancy, careDuringPregnancy.entries!!) + careDuringPregnancy.value = if (parsedCDPList.isNotEmpty()) { + parsedCDPList.joinToString("|") + } else { + it.careDuringPregnancy ?: "" + } + val parsedBrestFeedingList = parseSelections(it.importanceBreastfeeding, importBreastFeeding.entries!!) + importBreastFeeding.value = if (parsedBrestFeedingList.isNotEmpty()) { + parsedBrestFeedingList.joinToString("|") + } else { + it.importanceBreastfeeding ?: "" + } + + val parsedComplementaryFeedingList = parseSelections(it.complementaryFeeding, complementFeeding.entries!!) + complementFeeding.value = if (parsedComplementaryFeedingList.isNotEmpty()) { + parsedComplementaryFeedingList.joinToString("|") + } else { + it.complementaryFeeding ?: "" + } + + val parsedHygieneSanitiList = parseSelections(it.hygieneSanitation, hygieneSenitation.entries!!) + hygieneSenitation.value = if (parsedHygieneSanitiList.isNotEmpty()) { + parsedHygieneSanitiList.joinToString("|") + } else { + it.hygieneSanitation ?: "" + } + + val parsedFamilyPlanList = parseSelections(it.familyPlanningHealthcare, fPlanning.entries!!) + fPlanning.value = if (parsedFamilyPlanList.isNotEmpty()) { + parsedFamilyPlanList.joinToString("|") + } else { + it.familyPlanningHealthcare ?: "" + } + + } + + } + setUpPage(list) + } + + + override suspend fun handleListOnValueChanged(formId: Int, index: Int): Int { + return when (formId) { + place.id -> { + validateEmptyOnEditText(place) + validateAllAlphabetsSpecialAndNumericOnEditText(place) + -1 + } + + noOfBeneficiariesAttended.id -> { + validateEmptyOnEditText(noOfBeneficiariesAttended) + -1 + } + noOfPWAttended.id -> { + validateEmptyOnEditText(noOfPWAttended) + -1 + } + noOflactingMotherAttended.id -> { + validateEmptyOnEditText(noOflactingMotherAttended) + -1 + } + noOfchildrenAttended.id -> { + validateEmptyOnEditText(noOfchildrenAttended) + -1 + } + + + else -> { + -1 + } + } + } + + + + override fun mapValues(cacheModel: FormDataModel, pageNumber: Int) { + (cacheModel as VHNDCache).let { form -> + + form.vhndDate = vhndDate.value!! + form.place = getEnglishValueInArray(R.array.place_of_vhsnc, place.value) + form.vhndPlaceId = place.getPosition() + form.noOfBeneficiariesAttended = noOfBeneficiariesAttended.value!!.toInt() + if (BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true)) { + form.pregnantWomenAnc = noOfPWAttended.value?.takeUnless { it.isBlank() } ?: "0" + form.lactatingMothersPnc = noOflactingMotherAttended.value?.takeUnless { it.isBlank() } ?: "0" + form.childrenImmunization = noOfchildrenAttended.value?.takeUnless { it.isBlank() } ?: "0" + form.knowledgeBalancedDiet = toCsv(knowOfBalanceDiet.value,knowOfBalanceDiet.entries!!) + form.careDuringPregnancy = toCsv(careDuringPregnancy.value,careDuringPregnancy.entries!!) + form.importanceBreastfeeding = toCsv(importBreastFeeding.value,importBreastFeeding.entries!!) + form.complementaryFeeding = toCsv(complementFeeding.value,complementFeeding.entries!!) + form.hygieneSanitation = toCsv(hygieneSenitation.value,hygieneSenitation.entries!!) + form.familyPlanningHealthcare = toCsv(fPlanning.value,fPlanning.entries!!) + + } + form.image1 = pic1.value + form.image2 = pic2.value + + } + + } + + fun setImageUriToFormElement(lastImageFormId: Int, dpUri: Uri) { + when (lastImageFormId) { + pic1.id -> { + pic1.value = dpUri.toString() + pic1.errorText = null + } + + pic2.id -> { + pic2.value = dpUri.toString() + pic2.errorText = null + } + } + + } + private fun toCsv(rawValue: String?, entries: Array): String { + return parseSelections(rawValue, entries).joinToString("|") + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/dynamicDataSet/FormField.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/dynamicDataSet/FormField.kt new file mode 100644 index 000000000..65447d403 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/dynamicDataSet/FormField.kt @@ -0,0 +1,44 @@ +package org.piramalswasthya.sakhi.configuration.dynamicDataSet + +import org.piramalswasthya.sakhi.model.dynamicEntity.OptionItem + +data class FormField( + val fieldId: String, + val label: String, + val type: String, + var defaultValue: String? = null, + val isRequired: Boolean, + val options: List? = null, + var value: Any? = null, + var visible: Boolean = true, + val conditional: ConditionalLogic? = null, + var errorMessage: String? = null, + val placeholder: String? = null, + val validation: FieldValidation? = null, + var isEditable: Boolean = true +) + +data class ConditionalLogic( + val dependsOn: String, + val expectedValue: String +) + +data class FieldValidation( + val min: Float? = null, + val max: Float? = null, + var minDate: String? = null, + val maxDate: String? = null, + + val maxLength: Int? = null, + val regex: String? = null, + val errorMessage: String? = null, + + val decimalPlaces: Int? = null, + + val maxSizeMB: Int? = null, + + val afterField: String? = null, + val beforeField: String? = null +) + + diff --git a/app/src/main/java/org/piramalswasthya/sakhi/configuration/dynamicDataSet/ReferalFormDataset.kt b/app/src/main/java/org/piramalswasthya/sakhi/configuration/dynamicDataSet/ReferalFormDataset.kt new file mode 100644 index 000000000..0173c6c2a --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/configuration/dynamicDataSet/ReferalFormDataset.kt @@ -0,0 +1,112 @@ +package org.piramalswasthya.sakhi.configuration.dynamicDataSet +import android.content.Context +import android.text.InputType +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.configuration.Dataset +import org.piramalswasthya.sakhi.configuration.FormDataModel +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao +import org.piramalswasthya.sakhi.helpers.Languages +import org.piramalswasthya.sakhi.model.FormElement +import org.piramalswasthya.sakhi.model.InputType.DATE_PICKER +import org.piramalswasthya.sakhi.model.InputType.DROPDOWN +import org.piramalswasthya.sakhi.model.ReferalCache + +class ReferalFormDataset(context: Context, language: Languages,var preferenceDao: PreferenceDao) : Dataset(context, language) { + + private val healthCenter = FormElement( + id = 1, + inputType = DROPDOWN, + title = resources.getString(R.string.higher_healthcare_center), + arrayId = R.array.referal_health_center_array, + entries = resources.getStringArray(R.array.referal_health_center_array), + required = true, + hasDependants = true + ) + private val reasonForReferal = FormElement( + id = 2, + inputType = org.piramalswasthya.sakhi.model.InputType.TEXT_VIEW, + title = resources.getString(R.string.referral_reason), + arrayId = -1, + required = false, + + ) + + private val additionalService = FormElement( + id = 3, + inputType = org.piramalswasthya.sakhi.model.InputType.TEXT_VIEW, + title = resources.getString(R.string.additional_services), + value = "FLW", + arrayId = -1, + required = true, + etInputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL, + ) + + private val referDate = FormElement( + id = 4, + inputType = DATE_PICKER, + title = resources.getString(R.string.refer_date), + arrayId = -1, + required = true, + isEnabled = false, + min = System.currentTimeMillis() - (90L * 24 * 60 * 60 * 1000), + max = System.currentTimeMillis(), + ) + + var referralTypes = "" + suspend fun setUpPage(referral : String , referralType : String) { + val list = mutableListOf( + healthCenter, + reasonForReferal, + referDate + ) + referralTypes = referralType + referDate.value = getDateFromLong(System.currentTimeMillis()) + reasonForReferal.value = referral + setUpPage(list) + } + + override suspend fun handleListOnValueChanged( + formId: Int, + index: Int + ): Int { + return when (formId) { + reasonForReferal.id -> { + validateEmptyOnEditText(reasonForReferal) + } + healthCenter.id -> { + validateEmptyOnEditText(healthCenter) + } + + else -> { + -1 + } + } + + + + } + + override fun mapValues( + cacheModel: FormDataModel, + pageNumber: Int + ) { + (cacheModel as ReferalCache).let { form -> + form.revisitDate = getLongFromDate(referDate.value) + form.referralReason = reasonForReferal.value + form.refrredToAdditionalServiceList = listOf("FLW") + form.referredToInstituteID = healthCenter.getPosition() + form.referredToInstituteName = healthCenter.getEnglishStringFromPosition(healthCenter.getPosition()) + form.createdBy = preferenceDao.getLoggedInUser()?.userName + form.vanID = preferenceDao.getLoggedInUser()?.vanId + form.providerServiceMapID = preferenceDao.getLoggedInUser()?.serviceMapId + form.parkingPlaceID = preferenceDao.getLoggedInUser()?.serviceMapId + form.type = referralTypes + + + + + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/crypt/CryptoUtil.kt b/app/src/main/java/org/piramalswasthya/sakhi/crypt/CryptoUtil.kt index 9ea31d40d..aa7c04e36 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/crypt/CryptoUtil.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/crypt/CryptoUtil.kt @@ -1,6 +1,7 @@ package org.piramalswasthya.sakhi.crypt import android.util.Base64 +import org.piramalswasthya.sakhi.utils.KeyUtils import java.util.Random import javax.crypto.Cipher import javax.crypto.SecretKeyFactory @@ -13,7 +14,7 @@ class CryptoUtil { private val keySize = 256 private val ivSize = 128 private val iterationCount = 1989 - private val passPhrase = "Piramal12Piramal" + private val passPhrase = KeyUtils.encryptedPassKey() private fun generateKey( salt: String, diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/converters/StringListConverter.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/converters/StringListConverter.kt new file mode 100644 index 000000000..b650c8d0f --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/converters/StringListConverter.kt @@ -0,0 +1,19 @@ +package org.piramalswasthya.sakhi.database.converters + +import androidx.room.TypeConverter + +class StringListConverter { + @TypeConverter + fun fromList(list: List?): String? { + if (list == null) return null + return list.joinToString(separator = "|||") + } + + @TypeConverter + fun toList(value: String?): List? { + if (value.isNullOrBlank()) return null + return value.split("|||") + } +} + + diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/converters/SyncStateConverter.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/converters/SyncStateConverter.kt index 1525fc0be..7e05104c4 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/database/converters/SyncStateConverter.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/converters/SyncStateConverter.kt @@ -14,4 +14,14 @@ object SyncStateConverter { fun fromInt(value: Int): SyncState { return SyncState.values()[value] } + + @TypeConverter + fun toSyncState(value: String?): SyncState? { + return value?.let { enumValueOf(it) } + } + + @TypeConverter + fun fromSyncState(state: SyncState?): String? { + return state?.name + } } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/InAppDb.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/InAppDb.kt index d093ebf3a..06fbf67c6 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/database/room/InAppDb.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/InAppDb.kt @@ -1,13 +1,21 @@ package org.piramalswasthya.sakhi.database.room import android.content.Context +import android.util.Log import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase import androidx.room.TypeConverters import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase +import net.zetetic.database.sqlcipher.SQLiteDatabase +import net.zetetic.database.sqlcipher.SupportOpenHelperFactory import org.piramalswasthya.sakhi.database.converters.LocationEntityListConverter +import org.piramalswasthya.sakhi.database.converters.StringListConverter import org.piramalswasthya.sakhi.database.converters.SyncStateConverter +import org.piramalswasthya.sakhi.database.room.dao.ABHAGenratedDao +import org.piramalswasthya.sakhi.database.room.dao.AdolescentHealthDao +import org.piramalswasthya.sakhi.database.room.dao.AesDao import org.piramalswasthya.sakhi.database.room.dao.BenDao import org.piramalswasthya.sakhi.database.room.dao.BeneficiaryIdsAvailDao import org.piramalswasthya.sakhi.database.room.dao.CbacDao @@ -15,7 +23,9 @@ import org.piramalswasthya.sakhi.database.room.dao.CdrDao import org.piramalswasthya.sakhi.database.room.dao.ChildRegistrationDao import org.piramalswasthya.sakhi.database.room.dao.DeliveryOutcomeDao import org.piramalswasthya.sakhi.database.room.dao.EcrDao +import org.piramalswasthya.sakhi.database.room.dao.FilariaDao import org.piramalswasthya.sakhi.database.room.dao.FpotDao +import org.piramalswasthya.sakhi.database.room.dao.GeneralOpdDao import org.piramalswasthya.sakhi.database.room.dao.HbncDao import org.piramalswasthya.sakhi.database.room.dao.HbycDao import org.piramalswasthya.sakhi.database.room.dao.HouseholdDao @@ -23,22 +33,53 @@ import org.piramalswasthya.sakhi.database.room.dao.HrpDao import org.piramalswasthya.sakhi.database.room.dao.ImmunizationDao import org.piramalswasthya.sakhi.database.room.dao.IncentiveDao import org.piramalswasthya.sakhi.database.room.dao.InfantRegDao +import org.piramalswasthya.sakhi.database.room.dao.KalaAzarDao +import org.piramalswasthya.sakhi.database.room.dao.LeprosyDao +import org.piramalswasthya.sakhi.database.room.dao.MaaMeetingDao +import org.piramalswasthya.sakhi.database.room.dao.MalariaDao import org.piramalswasthya.sakhi.database.room.dao.MaternalHealthDao import org.piramalswasthya.sakhi.database.room.dao.MdsrDao +import org.piramalswasthya.sakhi.database.room.dao.MosquitoNetFormResponseDao import org.piramalswasthya.sakhi.database.room.dao.PmjayDao import org.piramalswasthya.sakhi.database.room.dao.PmsmaDao import org.piramalswasthya.sakhi.database.room.dao.PncDao +import org.piramalswasthya.sakhi.database.room.dao.ProfileDao +import org.piramalswasthya.sakhi.database.room.dao.SaasBahuSammelanDao import org.piramalswasthya.sakhi.database.room.dao.SyncDao import org.piramalswasthya.sakhi.database.room.dao.TBDao +import org.piramalswasthya.sakhi.database.room.dao.UwinDao +import org.piramalswasthya.sakhi.database.room.dao.VLFDao +import org.piramalswasthya.sakhi.database.room.dao.dynamicSchemaDao.BenIfaFormResponseJsonDao +import org.piramalswasthya.sakhi.database.room.dao.dynamicSchemaDao.CUFYFormResponseDao +import org.piramalswasthya.sakhi.database.room.dao.dynamicSchemaDao.CUFYFormResponseJsonDao +import org.piramalswasthya.sakhi.database.room.dao.dynamicSchemaDao.EyeSurgeryFormResponseJsonDao +import org.piramalswasthya.sakhi.database.room.dao.dynamicSchemaDao.FilariaMDAFormResponseJsonDao +import org.piramalswasthya.sakhi.database.room.dao.dynamicSchemaDao.FormResponseDao +import org.piramalswasthya.sakhi.database.room.dao.dynamicSchemaDao.FormResponseJsonDao +import org.piramalswasthya.sakhi.database.room.dao.dynamicSchemaDao.FormResponseJsonDaoHBYC +import org.piramalswasthya.sakhi.database.room.dao.dynamicSchemaDao.FormSchemaDao +import org.piramalswasthya.sakhi.database.room.dao.dynamicSchemaDao.InfantDao +import org.piramalswasthya.sakhi.model.ABHAModel +import org.piramalswasthya.sakhi.helpers.DatabaseKeyManager +import org.piramalswasthya.sakhi.helpers.RoomDbEncryptionHelper +import org.piramalswasthya.sakhi.database.room.dao.dynamicSchemaDao.FilariaMdaCampaignJsonDao +import org.piramalswasthya.sakhi.database.room.dao.dynamicSchemaDao.NCDReferalFormResponseJsonDao +import org.piramalswasthya.sakhi.database.room.dao.dynamicSchemaDao.FormResponseANCJsonDao +import org.piramalswasthya.sakhi.model.AHDCache +import org.piramalswasthya.sakhi.model.AESScreeningCache +import org.piramalswasthya.sakhi.model.AdolescentHealthCache import org.piramalswasthya.sakhi.model.BenBasicCache import org.piramalswasthya.sakhi.model.BenRegCache import org.piramalswasthya.sakhi.model.CDRCache import org.piramalswasthya.sakhi.model.CbacCache import org.piramalswasthya.sakhi.model.ChildRegCache import org.piramalswasthya.sakhi.model.DeliveryOutcomeCache +import org.piramalswasthya.sakhi.model.DewormingCache import org.piramalswasthya.sakhi.model.EligibleCoupleRegCache import org.piramalswasthya.sakhi.model.EligibleCoupleTrackingCache import org.piramalswasthya.sakhi.model.FPOTCache +import org.piramalswasthya.sakhi.model.FilariaScreeningCache +import org.piramalswasthya.sakhi.model.GeneralOPEDBeneficiary import org.piramalswasthya.sakhi.model.HBNCCache import org.piramalswasthya.sakhi.model.HBYCCache import org.piramalswasthya.sakhi.model.HRPMicroBirthPlanCache @@ -47,19 +88,48 @@ import org.piramalswasthya.sakhi.model.HRPNonPregnantTrackCache import org.piramalswasthya.sakhi.model.HRPPregnantAssessCache import org.piramalswasthya.sakhi.model.HRPPregnantTrackCache import org.piramalswasthya.sakhi.model.HouseholdCache +import org.piramalswasthya.sakhi.model.IRSRoundScreening import org.piramalswasthya.sakhi.model.ImmunizationCache import org.piramalswasthya.sakhi.model.IncentiveActivityCache import org.piramalswasthya.sakhi.model.IncentiveRecordCache import org.piramalswasthya.sakhi.model.InfantRegCache +import org.piramalswasthya.sakhi.model.KalaAzarScreeningCache +import org.piramalswasthya.sakhi.model.LeprosyFollowUpCache +import org.piramalswasthya.sakhi.model.LeprosyScreeningCache import org.piramalswasthya.sakhi.model.MDSRCache +import org.piramalswasthya.sakhi.model.MalariaConfirmedCasesCache +import org.piramalswasthya.sakhi.model.MalariaScreeningCache +import org.piramalswasthya.sakhi.model.PHCReviewMeetingCache import org.piramalswasthya.sakhi.model.PMJAYCache import org.piramalswasthya.sakhi.model.PMSMACache import org.piramalswasthya.sakhi.model.PNCVisitCache import org.piramalswasthya.sakhi.model.PregnantWomanAncCache import org.piramalswasthya.sakhi.model.PregnantWomanRegistrationCache +import org.piramalswasthya.sakhi.model.ProfileActivityCache +import org.piramalswasthya.sakhi.model.ReferalCache +import org.piramalswasthya.sakhi.model.SaasBahuSammelanCache import org.piramalswasthya.sakhi.model.TBScreeningCache import org.piramalswasthya.sakhi.model.TBSuspectedCache +import org.piramalswasthya.sakhi.model.UwinCache +import org.piramalswasthya.sakhi.model.MaaMeetingEntity +import org.piramalswasthya.sakhi.model.TBConfirmedTreatmentCache import org.piramalswasthya.sakhi.model.Vaccine +import org.piramalswasthya.sakhi.model.PulsePolioCampaignCache +import org.piramalswasthya.sakhi.model.ORSCampaignCache +import org.piramalswasthya.sakhi.model.VHNCCache +import org.piramalswasthya.sakhi.model.dynamicEntity.CUFYFormResponseJsonEntity +import org.piramalswasthya.sakhi.model.dynamicEntity.FilariaMDA.FilariaMDAFormResponseJsonEntity +import org.piramalswasthya.sakhi.model.dynamicEntity.FormResponseJsonEntity +import org.piramalswasthya.sakhi.model.dynamicEntity.FormSchemaEntity +import org.piramalswasthya.sakhi.model.dynamicEntity.InfantEntity +import org.piramalswasthya.sakhi.model.dynamicEntity.hbyc.FormResponseJsonEntityHBYC +import org.piramalswasthya.sakhi.model.VHNDCache +import org.piramalswasthya.sakhi.model.dynamicEntity.NCDReferalFormResponseJsonEntity +import org.piramalswasthya.sakhi.model.dynamicEntity.anc.ANCFormResponseJsonEntity +import org.piramalswasthya.sakhi.model.dynamicEntity.ben_ifa.BenIfaFormResponseJsonEntity +import org.piramalswasthya.sakhi.model.dynamicEntity.eye_surgery.EyeSurgeryFormResponseJsonEntity +import org.piramalswasthya.sakhi.model.dynamicEntity.filariaaMdaCampaign.FilariaMDACampaignFormResponseJsonEntity +import org.piramalswasthya.sakhi.model.dynamicEntity.mosquitonetEntity.MosquitoNetFormResponseJsonEntity @Database( entities = [ @@ -94,18 +164,60 @@ import org.piramalswasthya.sakhi.model.Vaccine //INCENTIVES IncentiveActivityCache::class, IncentiveRecordCache::class, + VHNDCache::class, + VHNCCache::class, + PHCReviewMeetingCache::class, + AHDCache::class, + DewormingCache::class, + PulsePolioCampaignCache::class, + ORSCampaignCache::class, + MalariaScreeningCache::class, + AESScreeningCache::class, + KalaAzarScreeningCache::class, + FilariaScreeningCache::class, + LeprosyScreeningCache::class, + LeprosyFollowUpCache::class, + MalariaConfirmedCasesCache::class, + IRSRoundScreening::class, + ProfileActivityCache::class, + AdolescentHealthCache::class, + ABHAModel::class, + //Dynamic Data + InfantEntity::class, + FormSchemaEntity::class, + SaasBahuSammelanCache::class, + MaaMeetingEntity::class, + FormResponseJsonEntity::class, + FormResponseJsonEntityHBYC::class, + CUFYFormResponseJsonEntity::class, + NCDReferalFormResponseJsonEntity::class, + GeneralOPEDBeneficiary::class, + ReferalCache::class, + UwinCache::class, + EyeSurgeryFormResponseJsonEntity::class, + BenIfaFormResponseJsonEntity::class, + MosquitoNetFormResponseJsonEntity::class, + FilariaMDAFormResponseJsonEntity::class, + ANCFormResponseJsonEntity::class, + FilariaMDACampaignFormResponseJsonEntity::class, + TBConfirmedTreatmentCache::class ], views = [BenBasicCache::class], - version = 14, exportSchema = false + version = 57, exportSchema = false ) -@TypeConverters(LocationEntityListConverter::class, SyncStateConverter::class) +@TypeConverters( + LocationEntityListConverter::class, + SyncStateConverter::class, + StringListConverter::class +) abstract class InAppDb : RoomDatabase() { abstract val benIdGenDao: BeneficiaryIdsAvailDao abstract val householdDao: HouseholdDao abstract val benDao: BenDao + abstract val adolescentHealthDao: AdolescentHealthDao abstract val cbacDao: CbacDao abstract val cdrDao: CdrDao abstract val mdsrDao: MdsrDao @@ -124,6 +236,36 @@ abstract class InAppDb : RoomDatabase() { abstract val infantRegDao: InfantRegDao abstract val childRegistrationDao: ChildRegistrationDao abstract val incentiveDao: IncentiveDao + abstract val vlfDao: VLFDao + abstract val malariaDao: MalariaDao + abstract val aesDao: AesDao + abstract val kalaAzarDao: KalaAzarDao + abstract val leprosyDao: LeprosyDao + abstract val filariaDao: FilariaDao + abstract val profileDao: ProfileDao + abstract val abhaGenratedDao: ABHAGenratedDao + abstract val saasBahuSammelanDao: SaasBahuSammelanDao + abstract val generalOpdDao: GeneralOpdDao + abstract val maaMeetingDao: MaaMeetingDao + abstract val uwinDao: UwinDao + + abstract val referalDao: NcdReferalDao + + abstract fun infantDao(): InfantDao + abstract fun formSchemaDao(): FormSchemaDao + abstract fun formResponseDao(): FormResponseDao + abstract fun CUFYFormResponseDao(): CUFYFormResponseDao + abstract fun CUFYFormResponseJsonDao(): CUFYFormResponseJsonDao + abstract fun NCDReferalFormResponseJsonDao(): NCDReferalFormResponseJsonDao + abstract fun formResponseJsonDao(): FormResponseJsonDao + abstract fun formResponseJsonDaoHBYC(): FormResponseJsonDaoHBYC + + abstract fun formResponseJsonDaoANC() : FormResponseANCJsonDao + abstract fun formResponseJsonDaoEyeSurgery(): EyeSurgeryFormResponseJsonDao + abstract fun formResponseJsonDaoBenIfa(): BenIfaFormResponseJsonDao + abstract fun formResponseMosquitoNetJsonDao(): MosquitoNetFormResponseDao + abstract fun formResponseFilariaMDAJsonDao(): FilariaMDAFormResponseJsonDao + abstract fun formResponseFilariaMDACampaignJsonDao(): FilariaMdaCampaignJsonDao abstract val syncDao: SyncDao @@ -131,10 +273,2658 @@ abstract class InAppDb : RoomDatabase() { @Volatile private var INSTANCE: InAppDb? = null + fun tableExists(db: SupportSQLiteDatabase, tableName: String): Boolean { + val cursor = db.query( + "SELECT name FROM sqlite_master WHERE type='table' AND name=?", + arrayOf(tableName) + ) + val exists = cursor.count > 0 + cursor.close() + return exists + } + + fun columnExists( + db: SupportSQLiteDatabase, + tableName: String, + columnName: String + ): Boolean { + val cursor = db.query("PRAGMA table_info($tableName)") + while (cursor.moveToNext()) { + if (cursor.getString(cursor.getColumnIndexOrThrow("name")) == columnName) { + cursor.close() + return true + } + } + cursor.close() + return false + } + + fun getInstance(appContext: Context): InAppDb { - val MIGRATION_1_2 = Migration(1, 2, migrate = { -// it.execSQL("select count(*) from beneficiary") + val MIGRATION_1_2 = Migration(18, 19, migrate = { + it.execSQL("alter table BEN_BASIC_CACHE add column isConsent BOOL") + it.execSQL("ALTER TABLE BENEFICIARY ADD COLUMN newColumn TEXT DEFAULT 'undefined'") + it.execSQL( + "ALTER TABLE BENEFICIARY ADD COLUMN kid_isConsent INTEGER DEFAULT 0" + ) + + }) + + val MIGRATION_56_57 = object : Migration(56, 57) { + override fun migrate(database: SupportSQLiteDatabase) { + val householdLocColumns = listOf( + "loc_country_id INTEGER NOT NULL DEFAULT 0", + "loc_country_name TEXT NOT NULL DEFAULT ''", + "loc_country_nameHindi TEXT", + "loc_country_nameAssamese TEXT", + "loc_state_id INTEGER NOT NULL DEFAULT 0", + "loc_state_name TEXT NOT NULL DEFAULT ''", + "loc_state_nameHindi TEXT", + "loc_state_nameAssamese TEXT", + "loc_district_id INTEGER NOT NULL DEFAULT 0", + "loc_district_name TEXT NOT NULL DEFAULT ''", + "loc_district_nameHindi TEXT", + "loc_district_nameAssamese TEXT", + "loc_block_id INTEGER NOT NULL DEFAULT 0", + "loc_block_name TEXT NOT NULL DEFAULT ''", + "loc_block_nameHindi TEXT", + "loc_block_nameAssamese TEXT", + "loc_village_id INTEGER NOT NULL DEFAULT 0", + "loc_village_name TEXT NOT NULL DEFAULT ''", + "loc_village_nameHindi TEXT", + "loc_village_nameAssamese TEXT" + ) + for (column in householdLocColumns) { + val columnName = column.split(" ")[0] + if (!columnExists(database, "HOUSEHOLD", columnName)) { + database.execSQL("ALTER TABLE HOUSEHOLD ADD COLUMN $column") + } + } + if (!columnExists(database, "HOUSEHOLD", "isDeactivate")) { + database.execSQL("ALTER TABLE HOUSEHOLD ADD COLUMN isDeactivate INTEGER NOT NULL DEFAULT 0") + } + + if (!columnExists(database, "BENEFICIARY", "isSpouseAdded")) { + database.execSQL("ALTER TABLE BENEFICIARY ADD COLUMN isSpouseAdded INTEGER NOT NULL DEFAULT 0") + } + if (!columnExists(database, "BENEFICIARY", "isChildrenAdded")) { + database.execSQL("ALTER TABLE BENEFICIARY ADD COLUMN isChildrenAdded INTEGER NOT NULL DEFAULT 0") + } + if (!columnExists(database, "BENEFICIARY", "isMarried")) { + database.execSQL("ALTER TABLE BENEFICIARY ADD COLUMN isMarried INTEGER NOT NULL DEFAULT 0") + } + if (!columnExists(database, "BENEFICIARY", "noOfChildren")) { + database.execSQL("ALTER TABLE BENEFICIARY ADD COLUMN noOfChildren INTEGER NOT NULL DEFAULT 0") + } + if (!columnExists(database, "BENEFICIARY", "noOfAliveChildren")) { + database.execSQL("ALTER TABLE BENEFICIARY ADD COLUMN noOfAliveChildren INTEGER NOT NULL DEFAULT 0") + } + if (!columnExists(database, "BENEFICIARY", "doYouHavechildren")) { + database.execSQL("ALTER TABLE BENEFICIARY ADD COLUMN doYouHavechildren INTEGER NOT NULL DEFAULT 0") + } + if (!columnExists(database, "BENEFICIARY", "isDeactivate")) { + database.execSQL("ALTER TABLE BENEFICIARY ADD COLUMN isDeactivate INTEGER NOT NULL DEFAULT 0") + } + + // Update isMarried based on existing gen_maritalStatusId data. + database.execSQL(""" + UPDATE BENEFICIARY + SET isMarried = CASE + WHEN gen_maritalStatusId = 2 THEN 1 + ELSE 0 + END + WHERE isMarried = 0 + """.trimIndent()) + + // ======================================================== + // 2b. PREGNANCY_ANC: add placeOfAnc columns + // (added to MIGRATION_45_46 after release-2.7, + // so users upgrading from v46 never got them) + // ======================================================== + if (!columnExists(database, "PREGNANCY_ANC", "placeOfAnc")) { + database.execSQL("ALTER TABLE PREGNANCY_ANC ADD COLUMN placeOfAnc TEXT") + } + if (!columnExists(database, "PREGNANCY_ANC", "placeOfAncId")) { + database.execSQL("ALTER TABLE PREGNANCY_ANC ADD COLUMN placeOfAncId INTEGER") + } + + // ======================================================== + // 3. CREATE new tables + // ======================================================== + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS `PulsePolioCampaign` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + `formDataJson` TEXT, + `syncState` INTEGER NOT NULL DEFAULT 0 + ) + """.trimIndent() + ) + + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS `ORSCampaign` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + `formDataJson` TEXT, + `syncState` INTEGER NOT NULL DEFAULT 0 + ) + """.trimIndent() + ) + + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS `TB_CONFIRMED_TREATMENT` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + `benId` INTEGER NOT NULL, + `regimenType` TEXT, + `treatmentStartDate` INTEGER NOT NULL, + `expectedTreatmentCompletionDate` INTEGER, + `followUpDate` INTEGER, + `monthlyFollowUpDone` TEXT, + `adherenceToMedicines` TEXT, + `anyDiscomfort` INTEGER, + `treatmentCompleted` INTEGER, + `actualTreatmentCompletionDate` INTEGER, + `treatmentOutcome` TEXT, + `dateOfDeath` INTEGER, + `placeOfDeath` TEXT, + `reasonForDeath` TEXT NOT NULL DEFAULT 'Tuberculosis', + `reasonForNotCompleting` TEXT, + `syncState` INTEGER NOT NULL DEFAULT 0, + `createdAt` INTEGER NOT NULL, + `updatedAt` INTEGER NOT NULL, + FOREIGN KEY(`benId`) REFERENCES `BENEFICIARY`(`beneficiaryId`) ON UPDATE CASCADE ON DELETE CASCADE + ) + """.trimIndent() + ) + database.execSQL("CREATE INDEX IF NOT EXISTS `ind_tb_confirmed` ON `TB_CONFIRMED_TREATMENT` (`benId`)") + + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS `ALL_VISIT_HISTORY_ANC` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + `benId` INTEGER NOT NULL, + `visitDay` TEXT NOT NULL, + `visitDate` TEXT NOT NULL, + `formId` TEXT NOT NULL, + `version` INTEGER NOT NULL, + `formDataJson` TEXT NOT NULL, + `isSynced` INTEGER NOT NULL DEFAULT 0, + `createdAt` INTEGER NOT NULL, + `syncedAt` INTEGER + ) + """.trimIndent() + ) + database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_ALL_VISIT_HISTORY_ANC_benId_visitDay_visitDate_formId` ON `ALL_VISIT_HISTORY_ANC` (`benId`, `visitDay`, `visitDate`, `formId`)") + + // ======================================================== + // 4. TB_SCREENING: add 11 new columns + // ======================================================== + val tbScreeningColumns = listOf( + "riseOfFever INTEGER", + "lossOfAppetite INTEGER", + "age INTEGER", + "diabetic INTEGER", + "tobaccoUser INTEGER", + "bmi INTEGER", + "contactWithTBPatient INTEGER", + "historyOfTBInLastFiveYrs INTEGER", + "sympotomatic TEXT", + "asymptomatic TEXT", + "recommandateTest TEXT" + ) + for (column in tbScreeningColumns) { + val columnName = column.split(" ")[0] + if (!columnExists(database, "TB_SCREENING", columnName)) { + database.execSQL("ALTER TABLE TB_SCREENING ADD COLUMN $column") + } + } + + // ======================================================== + // 5. VHNC: recreate table to fix wrong-name columns + // MIGRATION_48_49 added noOfPregnantWomen, + // noOfLactatingMother, followupPrevious (wrong names). + // Entity expects noOfPragnentWoment, noOfLactingMother, + // followupPrevius. Room requires exact column match, + // so extra columns cause failure. Recreate the table. + // ======================================================== + if (tableExists(database, "VHNC")) { + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS `VHNC_new` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + `vhncDate` TEXT NOT NULL, + `place` TEXT, + `noOfBeneficiariesAttended` INTEGER, + `image1` TEXT, + `image2` TEXT, + `villageName` TEXT, + `anm` INTEGER, + `aww` INTEGER, + `noOfPragnentWoment` INTEGER DEFAULT 0, + `noOfLactingMother` INTEGER DEFAULT 0, + `noOfCommittee` INTEGER DEFAULT 0, + `followupPrevius` INTEGER, + `syncState` INTEGER NOT NULL DEFAULT 0 + ) + """.trimIndent() + ) + // Copy data, preferring correctly-named columns if they exist, + // falling back to wrong-named columns from MIGRATION_48_49 + val hasCorrectPragnent = columnExists(database, "VHNC", "noOfPragnentWoment") + val hasWrongPregnant = columnExists(database, "VHNC", "noOfPregnantWomen") + val hasCorrectLacting = columnExists(database, "VHNC", "noOfLactingMother") + val hasWrongLactating = columnExists(database, "VHNC", "noOfLactatingMother") + val hasCorrectFollowup = columnExists(database, "VHNC", "followupPrevius") + val hasWrongFollowup = columnExists(database, "VHNC", "followupPrevious") + + val pragnentSrc = when { + hasCorrectPragnent -> "`noOfPragnentWoment`" + hasWrongPregnant -> "`noOfPregnantWomen`" + else -> "0" + } + val lactingSrc = when { + hasCorrectLacting -> "`noOfLactingMother`" + hasWrongLactating -> "`noOfLactatingMother`" + else -> "0" + } + val followupSrc = when { + hasCorrectFollowup -> "`followupPrevius`" + hasWrongFollowup -> "`followupPrevious`" + else -> "NULL" + } + + database.execSQL( + """ + INSERT INTO `VHNC_new` + (`id`, `vhncDate`, `place`, `noOfBeneficiariesAttended`, + `image1`, `image2`, `villageName`, `anm`, `aww`, + `noOfPragnentWoment`, `noOfLactingMother`, `noOfCommittee`, + `followupPrevius`, `syncState`) + SELECT `id`, `vhncDate`, `place`, `noOfBeneficiariesAttended`, + `image1`, `image2`, + ${if (columnExists(database, "VHNC", "villageName")) "`villageName`" else "NULL"}, + ${if (columnExists(database, "VHNC", "anm")) "`anm`" else "NULL"}, + ${if (columnExists(database, "VHNC", "aww")) "`aww`" else "NULL"}, + $pragnentSrc, + $lactingSrc, + ${if (columnExists(database, "VHNC", "noOfCommittee")) "`noOfCommittee`" else "0"}, + $followupSrc, + `syncState` + FROM `VHNC` + """.trimIndent() + ) + database.execSQL("DROP TABLE `VHNC`") + database.execSQL("ALTER TABLE `VHNC_new` RENAME TO `VHNC`") + } else { + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS `VHNC` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + `vhncDate` TEXT NOT NULL, + `place` TEXT, + `noOfBeneficiariesAttended` INTEGER, + `image1` TEXT, + `image2` TEXT, + `villageName` TEXT, + `anm` INTEGER, + `aww` INTEGER, + `noOfPragnentWoment` INTEGER DEFAULT 0, + `noOfLactingMother` INTEGER DEFAULT 0, + `noOfCommittee` INTEGER DEFAULT 0, + `followupPrevius` INTEGER, + `syncState` INTEGER NOT NULL DEFAULT 0 + ) + """.trimIndent() + ) + } + + // ======================================================== + // 6. NCD_REFER: fix unique index (benId) -> (benId, referralReason) + // ======================================================== + database.execSQL("DROP INDEX IF EXISTS `ind_refcache`") + database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `ind_refcache` ON `NCD_REFER` (`benId`, `referralReason`)") + + // ======================================================== + // 7. ncd_referal_all_visit: fix index names + uniqueness + // (MIGRATION_49_50 used wrong names and missed UNIQUE) + // ======================================================== + if (tableExists(database, "ncd_referal_all_visit")) { + database.execSQL("DROP INDEX IF EXISTS `index_ncd_visit_ben_hh`") + database.execSQL("DROP INDEX IF EXISTS `index_ncd_visit_followup`") + database.execSQL("DROP INDEX IF EXISTS `index_ncd_referal_all_visit_benId_hhId`") + database.execSQL("DROP INDEX IF EXISTS `index_ncd_referal_all_visit_benId_hhId_visitNo_followUpNo`") + database.execSQL("CREATE INDEX IF NOT EXISTS `index_ncd_referal_all_visit_benId_hhId` ON `ncd_referal_all_visit` (`benId`, `hhId`)") + database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_ncd_referal_all_visit_benId_hhId_visitNo_followUpNo` ON `ncd_referal_all_visit` (`benId`, `hhId`, `visitNo`, `followUpNo`)") + } + + // ======================================================== + // 8. MAA_MEETING: add 4 missing columns + // (MIGRATION_51_52 used CREATE TABLE IF NOT EXISTS which + // is a no-op since the table already existed from v29) + // ======================================================== + if (tableExists(database, "MAA_MEETING")) { + if (!columnExists(database, "MAA_MEETING", "villageName")) { + database.execSQL("ALTER TABLE MAA_MEETING ADD COLUMN villageName TEXT") + } + if (!columnExists(database, "MAA_MEETING", "mitaninActivityCheckList")) { + database.execSQL("ALTER TABLE MAA_MEETING ADD COLUMN mitaninActivityCheckList TEXT") + } + if (!columnExists(database, "MAA_MEETING", "noOfPragnentWomen")) { + database.execSQL("ALTER TABLE MAA_MEETING ADD COLUMN noOfPragnentWomen TEXT") + } + if (!columnExists(database, "MAA_MEETING", "noOfLactingMother")) { + database.execSQL("ALTER TABLE MAA_MEETING ADD COLUMN noOfLactingMother TEXT") + } + } + + // ======================================================== + // 9. FILARIA_MDA_CAMPAIGN_HISTORY: fix column types + // (MIGRATION_52_53 used syncState TEXT instead of INTEGER, + // and isSynced nullable instead of NOT NULL) + // ======================================================== + if (tableExists(database, "FILARIA_MDA_CAMPAIGN_HISTORY")) { + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS `FILARIA_MDA_CAMPAIGN_HISTORY_new` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + `visitDate` TEXT NOT NULL, + `visitYear` TEXT NOT NULL, + `formId` TEXT NOT NULL, + `version` INTEGER NOT NULL, + `formDataJson` TEXT NOT NULL, + `isSynced` INTEGER NOT NULL DEFAULT 0, + `syncState` INTEGER NOT NULL DEFAULT 0, + `createdAt` INTEGER NOT NULL, + `syncedAt` TEXT + ) + """.trimIndent() + ) + database.execSQL( + """ + INSERT OR IGNORE INTO `FILARIA_MDA_CAMPAIGN_HISTORY_new` + (`id`, `visitDate`, `visitYear`, `formId`, `version`, `formDataJson`, + `isSynced`, `syncState`, `createdAt`, `syncedAt`) + SELECT `id`, `visitDate`, `visitYear`, `formId`, `version`, `formDataJson`, + COALESCE(`isSynced`, 0), + CASE WHEN typeof(`syncState`) = 'text' THEN + CASE WHEN `syncState` = 'SYNCED' THEN 2 + WHEN `syncState` = 'SYNCING' THEN 1 + ELSE 0 END + ELSE COALESCE(`syncState`, 0) END, + `createdAt`, `syncedAt` + FROM `FILARIA_MDA_CAMPAIGN_HISTORY` + """.trimIndent() + ) + database.execSQL("DROP TABLE `FILARIA_MDA_CAMPAIGN_HISTORY`") + database.execSQL("ALTER TABLE `FILARIA_MDA_CAMPAIGN_HISTORY_new` RENAME TO `FILARIA_MDA_CAMPAIGN_HISTORY`") + } else { + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS `FILARIA_MDA_CAMPAIGN_HISTORY` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + `visitDate` TEXT NOT NULL, + `visitYear` TEXT NOT NULL, + `formId` TEXT NOT NULL, + `version` INTEGER NOT NULL, + `formDataJson` TEXT NOT NULL, + `isSynced` INTEGER NOT NULL DEFAULT 0, + `syncState` INTEGER NOT NULL DEFAULT 0, + `createdAt` INTEGER NOT NULL, + `syncedAt` TEXT + ) + """.trimIndent() + ) + } + database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_FILARIA_MDA_CAMPAIGN_HISTORY_formId_visitYear` ON `FILARIA_MDA_CAMPAIGN_HISTORY` (`formId`, `visitYear`)") + database.execSQL("CREATE INDEX IF NOT EXISTS `index_FILARIA_MDA_CAMPAIGN_HISTORY_visitDate` ON `FILARIA_MDA_CAMPAIGN_HISTORY` (`visitDate`)") + + // ======================================================== + // 10. BEN_BASIC_CACHE: drop and recreate view + // (MIGRATION_45_46 reverted 6 columns from 44_45, + // and isDeactivate was never added to the view) + // ======================================================== + database.execSQL("DROP VIEW IF EXISTS `BEN_BASIC_CACHE`") + database.execSQL( + """CREATE VIEW `BEN_BASIC_CACHE` AS SELECT b.beneficiaryId as benId,b.isMarried,b.noOfAliveChildren, b.noOfChildren, b.doYouHavechildren ,b.isConsent as isConsent, b.motherName as motherName, b.householdId as hhId, b.regDate, b.firstName as benName, b.lastName as benSurname, b.gender, b.dob as dob,b.isDeactivate, b.isDeath,b.isDeathValue,b.dateOfDeath,b.timeOfDeath,b.reasonOfDeath,b.reasonOfDeathId,b.placeOfDeath,b.placeOfDeathId,b.otherPlaceOfDeath,b.isSpouseAdded,b.isChildrenAdded, b.familyHeadRelationPosition as relToHeadId, b.contactNumber as mobileNo, b.fatherName,h.fam_familyHeadName as familyHeadName, b.gen_spouseName as spouseName, b.rchId, b.gen_lastMenstrualPeriod as lastMenstrualPeriod, b.isHrpStatus as hrpStatus, b.syncState, b.gen_reproductiveStatusId as reproductiveStatusId, b.isKid, b.immunizationStatus, b.loc_village_id as villageId, b.abha_healthIdNumber as abhaId, b.isNewAbha, IFNULL(cbac.benId IS NOT NULL, 0) as cbacFilled, cbac.syncState as cbacSyncState, IFNULL(cdr.benId IS NOT NULL, 0) as cdrFilled, cdr.syncState as cdrSyncState, IFNULL(mdsr.benId IS NOT NULL, 0) as mdsrFilled, mdsr.syncState as mdsrSyncState, IFNULL(pmsma.benId IS NOT NULL, 0) as pmsmaFilled, pmsma.syncState as pmsmaSyncState, IFNULL(hbnc.benId IS NOT NULL, 0) as hbncFilled, IFNULL(hbyc.benId IS NOT NULL, 0) as hbycFilled, IFNULL(pwr.benId IS NOT NULL, 0) as pwrFilled, pwr.syncState as pwrSyncState, IFNULL(pwa.pregnantWomanDelivered, 0) as isDelivered, IFNULL(pwa.hrpConfirmed, 0) as pwHrp, IFNULL(ecr.benId IS NOT NULL, 0) as ecrFilled, IFNULL(ect.benId IS NOT NULL, 0) as ectFilled, IFNULL((pwa.maternalDeath OR do.complication = 'DEATH' OR pnc.motherDeath), 0) as isMdsr, IFNULL(tbsn.benId IS NOT NULL, 0) as tbsnFilled, tbsn.syncState as tbsnSyncState, IFNULL(tbsp.benId IS NOT NULL, 0) as tbspFilled, tbsp.syncState as tbspSyncState, IFNULL(ir.motherBenId IS NOT NULL, 0) as irFilled, ir.syncState as irSyncState, IFNULL(cr.motherBenId IS NOT NULL, 0) as crFilled, cr.syncState as crSyncState, IFNULL(do.benId IS NOT NULL, 0) as doFilled, do.syncState as doSyncState, IFNULL((hrppa.benId IS NOT NULL AND hrppa.noOfDeliveries IS NOT NULL AND hrppa.timeLessThan18m IS NOT NULL AND hrppa.heightShort IS NOT NULL AND hrppa.age IS NOT NULL AND hrppa.rhNegative IS NOT NULL AND hrppa.homeDelivery IS NOT NULL AND hrppa.badObstetric IS NOT NULL AND hrppa.multiplePregnancy IS NOT NULL), 0) as hrppaFilled, hrppa.syncState as hrppaSyncState, IFNULL((hrpnpa.benId IS NOT NULL AND hrpnpa.noOfDeliveries IS NOT NULL AND hrpnpa.timeLessThan18m IS NOT NULL AND hrpnpa.heightShort IS NOT NULL AND hrpnpa.age IS NOT NULL AND hrpnpa.misCarriage IS NOT NULL AND hrpnpa.homeDelivery IS NOT NULL AND hrpnpa.medicalIssues IS NOT NULL AND hrpnpa.pastCSection IS NOT NULL), 0) as hrpnpaFilled, hrpnpa.syncState as hrpnpaSyncState, IFNULL(hrpmbp.benId IS NOT NULL, 0) as hrpmbpFilled, hrpmbp.syncState as hrpmbpSyncState, IFNULL(hrpt.benId IS NOT NULL, 0) as hrptFilled, IFNULL(((count(distinct hrpt.id) > 3) OR (((JulianDay('now')) - JulianDay(date(max(hrpt.visitDate)/1000,'unixepoch','localtime'))) < 1)), 0) as hrptrackingDone, hrpt.syncState as hrptSyncState, IFNULL(hrnpt.benId IS NOT NULL, 0) as hrnptFilled, IFNULL(((JulianDay('now') - JulianDay(date(max(hrnpt.visitDate)/1000,'unixepoch','localtime'))) < 1), 0) as hrnptrackingDone, hrnpt.syncState as hrnptSyncState FROM BENEFICIARY b JOIN HOUSEHOLD h ON b.householdId = h.householdId LEFT OUTER JOIN CBAC cbac ON b.beneficiaryId = cbac.benId LEFT OUTER JOIN CDR cdr ON b.beneficiaryId = cdr.benId LEFT OUTER JOIN MDSR mdsr ON b.beneficiaryId = mdsr.benId LEFT OUTER JOIN PMSMA pmsma ON b.beneficiaryId = pmsma.benId LEFT OUTER JOIN HBNC hbnc ON b.beneficiaryId = hbnc.benId LEFT OUTER JOIN HBYC hbyc ON b.beneficiaryId = hbyc.benId LEFT OUTER JOIN PREGNANCY_REGISTER pwr ON b.beneficiaryId = pwr.benId LEFT OUTER JOIN PREGNANCY_ANC pwa ON b.beneficiaryId = pwa.benId LEFT OUTER JOIN pnc_visit pnc ON b.beneficiaryId = pnc.benId LEFT OUTER JOIN ELIGIBLE_COUPLE_REG ecr ON b.beneficiaryId = ecr.benId LEFT OUTER JOIN ELIGIBLE_COUPLE_TRACKING ect ON (b.beneficiaryId = ect.benId AND CAST((strftime('%s','now') - ect.visitDate/1000)/60/60/24 AS INTEGER) < 30) LEFT OUTER JOIN TB_SCREENING tbsn ON b.beneficiaryId = tbsn.benId LEFT OUTER JOIN TB_SUSPECTED tbsp ON b.beneficiaryId = tbsp.benId LEFT OUTER JOIN MALARIA_SCREENING masp on b.beneficiaryId = masp.benId LEFT OUTER JOIN MALARIA_CONFIRMED macp on b.beneficiaryId = macp.benId LEFT OUTER JOIN HRP_PREGNANT_ASSESS hrppa ON b.beneficiaryId = hrppa.benId LEFT OUTER JOIN HRP_NON_PREGNANT_ASSESS hrpnpa ON b.beneficiaryId = hrpnpa.benId LEFT OUTER JOIN HRP_MICRO_BIRTH_PLAN hrpmbp ON b.beneficiaryId = hrpmbp.benId LEFT OUTER JOIN HRP_NON_PREGNANT_TRACK hrnpt ON b.beneficiaryId = hrnpt.benId LEFT OUTER JOIN HRP_PREGNANT_TRACK hrpt ON b.beneficiaryId = hrpt.benId LEFT OUTER JOIN DELIVERY_OUTCOME do ON b.beneficiaryId = do.benId LEFT OUTER JOIN INFANT_REG ir ON b.beneficiaryId = ir.motherBenId LEFT OUTER JOIN CHILD_REG cr ON b.beneficiaryId = cr.motherBenId WHERE b.isDraft = 0 GROUP BY b.beneficiaryId ORDER BY b.updatedDate DESC""" + ) + } + } + + val MIGRATION_55_56 = object : Migration(55, 56) { + override fun migrate(database: SupportSQLiteDatabase) { + if (!columnExists(database, "INCENTIVE_RECORD", "isEligible")) { + database.execSQL( + """ + ALTER TABLE INCENTIVE_RECORD + ADD COLUMN isEligible INTEGER NOT NULL DEFAULT 0 + """.trimIndent() + ) + } + } + } + + + val MIGRATION_54_55 = object : Migration(54, 55) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL( + """ + ALTER TABLE ELIGIBLE_COUPLE_TRACKING + ADD COLUMN dateOfSterilisation INTEGER NOT NULL DEFAULT 0 + """.trimIndent() + ) + } + } + val MIGRATION_53_54 = object : Migration(53, 54) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE TB_SUSPECTED ADD COLUMN visitLabel TEXT") + database.execSQL("ALTER TABLE TB_SUSPECTED ADD COLUMN typeOfTBCase TEXT") + database.execSQL("ALTER TABLE TB_SUSPECTED ADD COLUMN reasonForSuspicion TEXT") + + database.execSQL("ALTER TABLE TB_SUSPECTED ADD COLUMN hasSymptoms INTEGER NOT NULL DEFAULT 0") + + database.execSQL("ALTER TABLE TB_SUSPECTED ADD COLUMN isChestXRayDone INTEGER") + database.execSQL("ALTER TABLE TB_SUSPECTED ADD COLUMN chestXRayResult TEXT") + database.execSQL("ALTER TABLE TB_SUSPECTED ADD COLUMN referralFacility TEXT") + + database.execSQL("ALTER TABLE TB_SUSPECTED ADD COLUMN isTBConfirmed INTEGER") + database.execSQL("ALTER TABLE TB_SUSPECTED ADD COLUMN isDRTBConfirmed INTEGER") + + database.execSQL("ALTER TABLE TB_SUSPECTED ADD COLUMN isConfirmed INTEGER NOT NULL DEFAULT 0") + } + } + + val MIGRATION_52_53 = object : Migration(52, 53) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL( + """ + CREATE UNIQUE INDEX IF NOT EXISTS index_DewormingMeeting_dewormingDate + ON DewormingMeeting(dewormingDate) + """.trimIndent() + ) + + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS FILARIA_MDA_CAMPAIGN_HISTORY ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + visitDate TEXT NOT NULL, + visitYear TEXT NOT NULL, + formId TEXT NOT NULL, + version INTEGER NOT NULL, + formDataJson TEXT NOT NULL, + isSynced INTEGER, + createdAt INTEGER NOT NULL, + syncedAt TEXT, + syncState TEXT NOT NULL DEFAULT 'UNSYNCED' + ) + """.trimIndent() + ) + + database.execSQL( + """ + CREATE UNIQUE INDEX IF NOT EXISTS + index_FILARIA_MDA_CAMPAIGN_HISTORY_formId_visitYear + ON FILARIA_MDA_CAMPAIGN_HISTORY(formId, visitYear) + """.trimIndent() + ) + + database.execSQL( + """ + CREATE INDEX IF NOT EXISTS + index_FILARIA_MDA_CAMPAIGN_HISTORY_visitDate + ON FILARIA_MDA_CAMPAIGN_HISTORY(visitDate) + """.trimIndent() + ) + } + + + } + + + + val MIGRATION_51_52 = object : Migration(51, 52) { + override fun migrate(database: SupportSQLiteDatabase) { + + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS MAA_MEETING ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + meetingDate TEXT, + place TEXT, + villageName TEXT, + mitaninActivityCheckList TEXT, + noOfPragnentWomen TEXT, + noOfLactingMother TEXT, + participants INTEGER, + ashaId INTEGER, + meetingImages TEXT, + createdAt INTEGER NOT NULL DEFAULT 0, + updatedAt INTEGER NOT NULL DEFAULT 0, + syncState TEXT NOT NULL DEFAULT 'UNSYNCED' + ) + """.trimIndent() + ) + + database.execSQL( + "CREATE UNIQUE INDEX IF NOT EXISTS index_MAA_MEETING_id ON MAA_MEETING(id)" + ) + } + } + + + val MIGRATION_50_51 = Migration(50, 51, migrate = { + it.execSQL("alter table PHCReviewMeeting add column villageName TEXT") + it.execSQL("alter table PHCReviewMeeting add column mitaninHistory TEXT") + it.execSQL("alter table PHCReviewMeeting add column mitaninActivityCheckList TEXT") + it.execSQL("alter table PHCReviewMeeting add column placeId INTEGER DEFAULT 0") + it.execSQL( + """ + CREATE UNIQUE INDEX IF NOT EXISTS index_PHCReviewMeeting_id + ON PHCReviewMeeting(id) + """.trimIndent() + ) + + }) + + + + val MIGRATION_49_50 = object : Migration(49, 50) { + override fun migrate(database: SupportSQLiteDatabase) { + + database.execSQL( + "ALTER TABLE VHND ADD COLUMN vhndPlaceId INTEGER DEFAULT 0" + ) + + database.execSQL( + "ALTER TABLE VHND ADD COLUMN pregnantWomenAnc TEXT" + ) + + database.execSQL( + "ALTER TABLE VHND ADD COLUMN lactatingMothersPnc TEXT" + ) + + database.execSQL( + "ALTER TABLE VHND ADD COLUMN childrenImmunization TEXT" + ) + + database.execSQL( + "ALTER TABLE VHND ADD COLUMN knowledgeBalancedDiet TEXT" + ) + + database.execSQL( + "ALTER TABLE VHND ADD COLUMN careDuringPregnancy TEXT" + ) + + database.execSQL( + "ALTER TABLE VHND ADD COLUMN importanceBreastfeeding TEXT" + ) + + database.execSQL( + "ALTER TABLE VHND ADD COLUMN complementaryFeeding TEXT" + ) + + database.execSQL( + "ALTER TABLE VHND ADD COLUMN hygieneSanitation TEXT" + ) + + database.execSQL( + "ALTER TABLE VHND ADD COLUMN familyPlanningHealthcare TEXT" + ) + + database.execSQL( + "ALTER TABLE VHND ADD COLUMN selectAllEducation INTEGER DEFAULT 0" + ) + + // ncd_refer + + database.execSQL("DROP TABLE IF EXISTS ncd_referal_all_visit") + + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS `ncd_referal_all_visit` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + `benId` INTEGER NOT NULL, + `hhId` INTEGER NOT NULL, + `visitNo` INTEGER NOT NULL, + `followUpNo` INTEGER NOT NULL, + `treatmentStartDate` TEXT NOT NULL, + `followUpDate` TEXT, + `diagnosisCodes` TEXT, + `formId` TEXT NOT NULL, + `version` INTEGER NOT NULL, + `formDataJson` TEXT NOT NULL, + `isSynced` INTEGER NOT NULL DEFAULT 0, + `createdAt` INTEGER NOT NULL, + `updatedAt` INTEGER NOT NULL, + `syncedAt` INTEGER + ) + """.trimIndent() + ) + + database.execSQL( + """ + CREATE INDEX IF NOT EXISTS `index_ncd_visit_ben_hh` + ON `ncd_referal_all_visit` (`benId`, `hhId`) + """.trimIndent() + ) + + database.execSQL( + """ + CREATE INDEX IF NOT EXISTS `index_ncd_visit_followup` + ON `ncd_referal_all_visit` (`benId`, `hhId`, `visitNo`, `followUpNo`) + """.trimIndent() + ) + } + } + + + val MIGRATION_48_49 = object : Migration(48, 49) { + override fun migrate(db: SupportSQLiteDatabase) { + + val columns = listOf( + "villageName TEXT", + "anm INTEGER DEFAULT 0", + "aww INTEGER DEFAULT 0", + "noOfPregnantWomen INTEGER DEFAULT 0", + "noOfLactatingMother INTEGER DEFAULT 0", + "noOfCommittee INTEGER DEFAULT 0", + "followupPrevious INTEGER" + ) + + columns.forEach { columnDef -> + db.execSQL("ALTER TABLE VHNC ADD COLUMN $columnDef") + } + } + } + + + + val MIGRATION_47_48 = Migration(47, 48) { + it.execSQL("ALTER TABLE LEPROSY_SCREENING ADD COLUMN recurrentUlcerationId INTEGER DEFAULT 1") + it.execSQL("ALTER TABLE LEPROSY_SCREENING ADD COLUMN recurrentTinglingId INTEGER DEFAULT 1") + it.execSQL("ALTER TABLE LEPROSY_SCREENING ADD COLUMN hypopigmentedPatchId INTEGER DEFAULT 1") + it.execSQL("ALTER TABLE LEPROSY_SCREENING ADD COLUMN thickenedSkinId INTEGER DEFAULT 1") + it.execSQL("ALTER TABLE LEPROSY_SCREENING ADD COLUMN skinNodulesId INTEGER DEFAULT 1") + it.execSQL("ALTER TABLE LEPROSY_SCREENING ADD COLUMN skinPatchDiscolorationId INTEGER DEFAULT 1") + it.execSQL("ALTER TABLE LEPROSY_SCREENING ADD COLUMN recurrentNumbnessId INTEGER DEFAULT 1") + it.execSQL("ALTER TABLE LEPROSY_SCREENING ADD COLUMN clawingFingersId INTEGER DEFAULT 1") + it.execSQL("ALTER TABLE LEPROSY_SCREENING ADD COLUMN tinglingNumbnessExtremitiesId INTEGER DEFAULT 1") + it.execSQL("ALTER TABLE LEPROSY_SCREENING ADD COLUMN inabilityCloseEyelidId INTEGER DEFAULT 1") + it.execSQL("ALTER TABLE LEPROSY_SCREENING ADD COLUMN difficultyHoldingObjectsId INTEGER DEFAULT 1") + it.execSQL("ALTER TABLE LEPROSY_SCREENING ADD COLUMN weaknessFeetId INTEGER DEFAULT 1") + + // ===== Symptom String fields ===== + it.execSQL("ALTER TABLE LEPROSY_SCREENING ADD COLUMN recurrentUlceration TEXT") + it.execSQL("ALTER TABLE LEPROSY_SCREENING ADD COLUMN recurrentTingling TEXT") + it.execSQL("ALTER TABLE LEPROSY_SCREENING ADD COLUMN hypopigmentedPatch TEXT") + it.execSQL("ALTER TABLE LEPROSY_SCREENING ADD COLUMN thickenedSkin TEXT") + it.execSQL("ALTER TABLE LEPROSY_SCREENING ADD COLUMN skinNodules TEXT") + it.execSQL("ALTER TABLE LEPROSY_SCREENING ADD COLUMN skinPatchDiscoloration TEXT") + it.execSQL("ALTER TABLE LEPROSY_SCREENING ADD COLUMN recurrentNumbness TEXT") + it.execSQL("ALTER TABLE LEPROSY_SCREENING ADD COLUMN clawingFingers TEXT") + it.execSQL("ALTER TABLE LEPROSY_SCREENING ADD COLUMN tinglingNumbnessExtremities TEXT") + it.execSQL("ALTER TABLE LEPROSY_SCREENING ADD COLUMN inabilityCloseEyelid TEXT") + it.execSQL("ALTER TABLE LEPROSY_SCREENING ADD COLUMN difficultyHoldingObjects TEXT") + it.execSQL("ALTER TABLE LEPROSY_SCREENING ADD COLUMN weaknessFeet TEXT") + + } + val MIGRATION_46_47 = Migration(46, 47) { + it.execSQL( + """ALTER TABLE NCD_REFER + ADD COLUMN type TEXT + """.trimIndent() + ) + + } + + val MIGRATION_45_46 = Migration(45, 46) { + it.execSQL("DROP VIEW IF EXISTS `BEN_BASIC_CACHE`") + it.execSQL( + """ + CREATE VIEW `BEN_BASIC_CACHE` AS SELECT b.beneficiaryId as benId, b.isConsent as isConsent, b.motherName as motherName, b.householdId as hhId, b.regDate, b.firstName as benName, b.lastName as benSurname, b.gender, b.dob as dob, b.isDeath,b.isDeathValue,b.dateOfDeath,b.timeOfDeath,b.reasonOfDeath,b.reasonOfDeathId,b.placeOfDeath,b.placeOfDeathId,b.otherPlaceOfDeath, b.familyHeadRelationPosition as relToHeadId, b.contactNumber as mobileNo, b.fatherName, h.fam_familyHeadName as familyHeadName, b.gen_spouseName as spouseName, b.rchId, b.gen_lastMenstrualPeriod as lastMenstrualPeriod, b.isHrpStatus as hrpStatus, b.syncState, b.gen_reproductiveStatusId as reproductiveStatusId, b.isKid, b.immunizationStatus, b.loc_village_id as villageId, b.abha_healthIdNumber as abhaId, b.isNewAbha, IFNULL(cbac.benId IS NOT NULL, 0) as cbacFilled, cbac.syncState as cbacSyncState, IFNULL(cdr.benId IS NOT NULL, 0) as cdrFilled, cdr.syncState as cdrSyncState, IFNULL(mdsr.benId IS NOT NULL, 0) as mdsrFilled, mdsr.syncState as mdsrSyncState, IFNULL(pmsma.benId IS NOT NULL, 0) as pmsmaFilled, pmsma.syncState as pmsmaSyncState, IFNULL(hbnc.benId IS NOT NULL, 0) as hbncFilled, IFNULL(hbyc.benId IS NOT NULL, 0) as hbycFilled, IFNULL(pwr.benId IS NOT NULL, 0) as pwrFilled, pwr.syncState as pwrSyncState, IFNULL(pwa.pregnantWomanDelivered, 0) as isDelivered, IFNULL(pwa.hrpConfirmed, 0) as pwHrp, IFNULL(ecr.benId IS NOT NULL, 0) as ecrFilled, IFNULL(ect.benId IS NOT NULL, 0) as ectFilled, IFNULL((pwa.maternalDeath OR do.complication = 'DEATH' OR pnc.motherDeath), 0) as isMdsr, IFNULL(tbsn.benId IS NOT NULL, 0) as tbsnFilled, tbsn.syncState as tbsnSyncState, IFNULL(tbsp.benId IS NOT NULL, 0) as tbspFilled, tbsp.syncState as tbspSyncState, IFNULL(ir.motherBenId IS NOT NULL, 0) as irFilled, ir.syncState as irSyncState, IFNULL(cr.motherBenId IS NOT NULL, 0) as crFilled, cr.syncState as crSyncState, IFNULL(do.benId IS NOT NULL, 0) as doFilled, do.syncState as doSyncState, IFNULL((hrppa.benId IS NOT NULL AND hrppa.noOfDeliveries IS NOT NULL AND hrppa.timeLessThan18m IS NOT NULL AND hrppa.heightShort IS NOT NULL AND hrppa.age IS NOT NULL AND hrppa.rhNegative IS NOT NULL AND hrppa.homeDelivery IS NOT NULL AND hrppa.badObstetric IS NOT NULL AND hrppa.multiplePregnancy IS NOT NULL), 0) as hrppaFilled, hrppa.syncState as hrppaSyncState, IFNULL((hrpnpa.benId IS NOT NULL AND hrpnpa.noOfDeliveries IS NOT NULL AND hrpnpa.timeLessThan18m IS NOT NULL AND hrpnpa.heightShort IS NOT NULL AND hrpnpa.age IS NOT NULL AND hrpnpa.misCarriage IS NOT NULL AND hrpnpa.homeDelivery IS NOT NULL AND hrpnpa.medicalIssues IS NOT NULL AND hrpnpa.pastCSection IS NOT NULL), 0) as hrpnpaFilled, hrpnpa.syncState as hrpnpaSyncState, IFNULL(hrpmbp.benId IS NOT NULL, 0) as hrpmbpFilled, hrpmbp.syncState as hrpmbpSyncState, IFNULL(hrpt.benId IS NOT NULL, 0) as hrptFilled, IFNULL(((count(distinct hrpt.id) > 3) OR (((JulianDay('now')) - JulianDay(date(max(hrpt.visitDate)/1000,'unixepoch','localtime'))) < 1)), 0) as hrptrackingDone, hrpt.syncState as hrptSyncState, IFNULL(hrnpt.benId IS NOT NULL, 0) as hrnptFilled, IFNULL(((JulianDay('now') - JulianDay(date(max(hrnpt.visitDate)/1000,'unixepoch','localtime'))) < 1), 0) as hrnptrackingDone, hrnpt.syncState as hrnptSyncState FROM BENEFICIARY b JOIN HOUSEHOLD h ON b.householdId = h.householdId LEFT OUTER JOIN CBAC cbac ON b.beneficiaryId = cbac.benId LEFT OUTER JOIN CDR cdr ON b.beneficiaryId = cdr.benId LEFT OUTER JOIN MDSR mdsr ON b.beneficiaryId = mdsr.benId LEFT OUTER JOIN PMSMA pmsma ON b.beneficiaryId = pmsma.benId LEFT OUTER JOIN HBNC hbnc ON b.beneficiaryId = hbnc.benId LEFT OUTER JOIN HBYC hbyc ON b.beneficiaryId = hbyc.benId LEFT OUTER JOIN PREGNANCY_REGISTER pwr ON b.beneficiaryId = pwr.benId LEFT OUTER JOIN PREGNANCY_ANC pwa ON b.beneficiaryId = pwa.benId LEFT OUTER JOIN pnc_visit pnc ON b.beneficiaryId = pnc.benId LEFT OUTER JOIN ELIGIBLE_COUPLE_REG ecr ON b.beneficiaryId = ecr.benId LEFT OUTER JOIN ELIGIBLE_COUPLE_TRACKING ect ON (b.beneficiaryId = ect.benId AND CAST((strftime('%s','now') - ect.visitDate/1000)/60/60/24 AS INTEGER) < 30) LEFT OUTER JOIN TB_SCREENING tbsn ON b.beneficiaryId = tbsn.benId LEFT OUTER JOIN TB_SUSPECTED tbsp ON b.beneficiaryId = tbsp.benId LEFT OUTER JOIN MALARIA_SCREENING masp on b.beneficiaryId = masp.benId LEFT OUTER JOIN MALARIA_CONFIRMED macp on b.beneficiaryId = macp.benId LEFT OUTER JOIN HRP_PREGNANT_ASSESS hrppa ON b.beneficiaryId = hrppa.benId LEFT OUTER JOIN HRP_NON_PREGNANT_ASSESS hrpnpa ON b.beneficiaryId = hrpnpa.benId LEFT OUTER JOIN HRP_MICRO_BIRTH_PLAN hrpmbp ON b.beneficiaryId = hrpmbp.benId LEFT OUTER JOIN HRP_NON_PREGNANT_TRACK hrnpt ON b.beneficiaryId = hrnpt.benId LEFT OUTER JOIN HRP_PREGNANT_TRACK hrpt ON b.beneficiaryId = hrpt.benId LEFT OUTER JOIN DELIVERY_OUTCOME do ON b.beneficiaryId = do.benId LEFT OUTER JOIN INFANT_REG ir ON b.beneficiaryId = ir.motherBenId LEFT OUTER JOIN CHILD_REG cr ON b.beneficiaryId = cr.motherBenId WHERE b.isDraft = 0 GROUP BY b.beneficiaryId ORDER BY b.updatedDate DESC + """.trimIndent() + ) + + if (tableExists(it, "PREGNANCY_ANC")) { + if (!columnExists(it, "PREGNANCY_ANC", "placeOfAnc")) { + it.execSQL("ALTER TABLE PREGNANCY_ANC ADD COLUMN placeOfAnc TEXT") + } + if (!columnExists(it, "PREGNANCY_ANC", "placeOfAncId")) { + it.execSQL("ALTER TABLE PREGNANCY_ANC ADD COLUMN placeOfAncId INTEGER") + } + } + } + + val MIGRATION_44_45 = object : Migration(44, 45) { + override fun migrate(database: SupportSQLiteDatabase) { + + database.execSQL("ALTER TABLE BENEFICIARY ADD COLUMN isSpouseAdded INTEGER NOT NULL DEFAULT 0") + database.execSQL("ALTER TABLE BENEFICIARY ADD COLUMN isChildrenAdded INTEGER NOT NULL DEFAULT 0") + database.execSQL("ALTER TABLE BENEFICIARY ADD COLUMN isMarried INTEGER NOT NULL DEFAULT 0") + database.execSQL("ALTER TABLE BENEFICIARY ADD COLUMN noOfChildren INTEGER NOT NULL DEFAULT 0") + database.execSQL("ALTER TABLE BENEFICIARY ADD COLUMN noOfAliveChildren INTEGER NOT NULL DEFAULT 0") + database.execSQL("ALTER TABLE BENEFICIARY ADD COLUMN doYouHavechildren INTEGER NOT NULL DEFAULT 0") + + database.execSQL("DROP VIEW IF EXISTS BEN_BASIC_CACHE") + database.execSQL( + "CREATE VIEW `BEN_BASIC_CACHE` AS " + + "SELECT b.beneficiaryId as benId,b.isMarried, b.noOfAliveChildren, b.noOfChildren, b.doYouHavechildren, b.isConsent as isConsent, b.motherName as motherName, b.householdId as hhId, b.regDate, b.firstName as benName, b.lastName as benSurname, b.gender, b.dob as dob, b.isDeath,b.isDeathValue,b.dateOfDeath,b.timeOfDeath,b.reasonOfDeath,b.reasonOfDeathId,b.placeOfDeath,b.placeOfDeathId,b.otherPlaceOfDeath,b.isSpouseAdded,b.isChildrenAdded, b.familyHeadRelationPosition as relToHeadId" + + ", b.contactNumber as mobileNo, b.fatherName, h.fam_familyHeadName as familyHeadName, b.gen_spouseName as spouseName, b.rchId, b.gen_lastMenstrualPeriod as lastMenstrualPeriod" + + ", b.isHrpStatus as hrpStatus, b.syncState, b.gen_reproductiveStatusId as reproductiveStatusId, b.isKid, b.immunizationStatus" + + ", b.loc_village_id as villageId, b.abha_healthIdNumber as abhaId" + + ", b.isNewAbha" + // FIX: Using only one, correct source for isNewAbha. + ", IFNULL(cbac.benId IS NOT NULL, 0) as cbacFilled, cbac.syncState as cbacSyncState" + + ", IFNULL(cdr.benId IS NOT NULL, 0) as cdrFilled, cdr.syncState as cdrSyncState" + + ", IFNULL(mdsr.benId IS NOT NULL, 0) as mdsrFilled, mdsr.syncState as mdsrSyncState" + + ", IFNULL(pmsma.benId IS NOT NULL, 0) as pmsmaFilled, pmsma.syncState as pmsmaSyncState" + + ", IFNULL(hbnc.benId IS NOT NULL, 0) as hbncFilled" + + ", IFNULL(hbyc.benId IS NOT NULL, 0) as hbycFilled" + + ", IFNULL(pwr.benId IS NOT NULL, 0) as pwrFilled, pwr.syncState as pwrSyncState" + + ", IFNULL(pwa.pregnantWomanDelivered, 0) as isDelivered, IFNULL(pwa.hrpConfirmed, 0) as pwHrp" + + ", IFNULL(ecr.benId IS NOT NULL, 0) as ecrFilled" + + ", IFNULL(ect.benId IS NOT NULL, 0) as ectFilled" + // FIX: Removed duplicate ectFilled and used a safe version. + ", IFNULL((pwa.maternalDeath OR do.complication = 'DEATH' OR pnc.motherDeath), 0) as isMdsr" + + ", IFNULL(tbsn.benId IS NOT NULL, 0) as tbsnFilled, tbsn.syncState as tbsnSyncState" + + ", IFNULL(tbsp.benId IS NOT NULL, 0) as tbspFilled, tbsp.syncState as tbspSyncState" + + ", IFNULL(ir.motherBenId IS NOT NULL, 0) as irFilled, ir.syncState as irSyncState" + + ", IFNULL(cr.motherBenId IS NOT NULL, 0) as crFilled, cr.syncState as crSyncState" + + ", IFNULL(do.benId IS NOT NULL, 0) as doFilled, do.syncState as doSyncState" + + ", IFNULL((hrppa.benId IS NOT NULL AND hrppa.noOfDeliveries IS NOT NULL AND hrppa.timeLessThan18m IS NOT NULL AND hrppa.heightShort IS NOT NULL AND hrppa.age IS NOT NULL AND hrppa.rhNegative IS NOT NULL AND hrppa.homeDelivery IS NOT NULL AND hrppa.badObstetric IS NOT NULL AND hrppa.multiplePregnancy IS NOT NULL), 0) as hrppaFilled, hrppa.syncState as hrppaSyncState" + + ", IFNULL((hrpnpa.benId IS NOT NULL AND hrpnpa.noOfDeliveries IS NOT NULL AND hrpnpa.timeLessThan18m IS NOT NULL AND hrpnpa.heightShort IS NOT NULL AND hrpnpa.age IS NOT NULL AND hrpnpa.misCarriage IS NOT NULL AND hrpnpa.homeDelivery IS NOT NULL AND hrpnpa.medicalIssues IS NOT NULL AND hrpnpa.pastCSection IS NOT NULL), 0) as hrpnpaFilled, hrpnpa.syncState as hrpnpaSyncState" + + ", IFNULL(hrpmbp.benId IS NOT NULL, 0) as hrpmbpFilled, hrpmbp.syncState as hrpmbpSyncState" + + ", IFNULL(hrpt.benId IS NOT NULL, 0) as hrptFilled, IFNULL(((count(distinct hrpt.id) > 3) OR (((JulianDay('now')) - JulianDay(date(max(hrpt.visitDate)/1000,'unixepoch','localtime'))) < 1)), 0) as hrptrackingDone, hrpt.syncState as hrptSyncState" + + ", IFNULL(hrnpt.benId IS NOT NULL, 0) as hrnptFilled, IFNULL(((JulianDay('now') - JulianDay(date(max(hrnpt.visitDate)/1000,'unixepoch','localtime'))) < 1), 0) as hrnptrackingDone, hrnpt.syncState as hrnptSyncState " + + "FROM BENEFICIARY b " + + "JOIN HOUSEHOLD h ON b.householdId = h.householdId " + + "LEFT OUTER JOIN CBAC cbac ON b.beneficiaryId = cbac.benId " + + "LEFT OUTER JOIN CDR cdr ON b.beneficiaryId = cdr.benId " + + "LEFT OUTER JOIN MDSR mdsr ON b.beneficiaryId = mdsr.benId " + + "LEFT OUTER JOIN PMSMA pmsma ON b.beneficiaryId = pmsma.benId " + + "LEFT OUTER JOIN HBNC hbnc ON b.beneficiaryId = hbnc.benId " + + "LEFT OUTER JOIN HBYC hbyc ON b.beneficiaryId = hbyc.benId " + + "LEFT OUTER JOIN PREGNANCY_REGISTER pwr ON b.beneficiaryId = pwr.benId " + + "LEFT OUTER JOIN PREGNANCY_ANC pwa ON b.beneficiaryId = pwa.benId " + + "LEFT OUTER JOIN pnc_visit pnc ON b.beneficiaryId = pnc.benId " + + "LEFT OUTER JOIN ELIGIBLE_COUPLE_REG ecr ON b.beneficiaryId = ecr.benId " + + "LEFT OUTER JOIN ELIGIBLE_COUPLE_TRACKING ect ON (b.beneficiaryId = ect.benId AND CAST((strftime('%s','now') - ect.visitDate/1000)/60/60/24 AS INTEGER) < 30) " + + "LEFT OUTER JOIN TB_SCREENING tbsn ON b.beneficiaryId = tbsn.benId " + + "LEFT OUTER JOIN TB_SUSPECTED tbsp ON b.beneficiaryId = tbsp.benId " + + "LEFT OUTER JOIN MALARIA_SCREENING masp on b.beneficiaryId = masp.benId " + + "LEFT OUTER JOIN MALARIA_CONFIRMED macp on b.beneficiaryId = macp.benId " + + "LEFT OUTER JOIN HRP_PREGNANT_ASSESS hrppa ON b.beneficiaryId = hrppa.benId " + + "LEFT OUTER JOIN HRP_NON_PREGNANT_ASSESS hrpnpa ON b.beneficiaryId = hrpnpa.benId " + + "LEFT OUTER JOIN HRP_MICRO_BIRTH_PLAN hrpmbp ON b.beneficiaryId = hrpmbp.benId " + + "LEFT OUTER JOIN HRP_NON_PREGNANT_TRACK hrnpt ON b.beneficiaryId = hrnpt.benId " + + "LEFT OUTER JOIN HRP_PREGNANT_TRACK hrpt ON b.beneficiaryId = hrpt.benId " + + "LEFT OUTER JOIN DELIVERY_OUTCOME do ON b.beneficiaryId = do.benId " + + "LEFT OUTER JOIN INFANT_REG ir ON b.beneficiaryId = ir.motherBenId " + + "LEFT OUTER JOIN CHILD_REG cr ON b.beneficiaryId = cr.motherBenId " + + "WHERE b.isDraft = 0 GROUP BY b.beneficiaryId ORDER BY b.updatedDate DESC" + ) + + database.execSQL(""" + UPDATE BENEFICIARY + SET isMarried = CASE + WHEN gen_maritalStatusId = 2 THEN 1 + ELSE 0 + END + """.trimIndent()) + + fun migrateTable( + tableName: String, + criticalColumns: List, + createTableColumns: String + ) { + val cursor = database.query( + "SELECT name FROM sqlite_master WHERE type='table' AND name='$tableName'" + ) + val tableExists = cursor.moveToFirst() + cursor.close() + + if (tableExists) { + val columnsCursor = database.query("PRAGMA table_info($tableName)") + val existingColumns = mutableSetOf() + while (columnsCursor.moveToNext()) { + existingColumns.add( + columnsCursor.getString( + columnsCursor.getColumnIndexOrThrow( + "name" + ) + ) + ) + } + columnsCursor.close() + + if (!existingColumns.contains("image2")) { + database.execSQL("ALTER TABLE $tableName ADD COLUMN image2 TEXT") + } + if (!existingColumns.contains("syncState")) { + database.execSQL("ALTER TABLE $tableName ADD COLUMN syncState INTEGER NOT NULL DEFAULT 0") + } + + val missingCritical = + criticalColumns.filter { !existingColumns.contains(it) } + + if (missingCritical.isNotEmpty()) { + database.execSQL("CREATE TABLE IF NOT EXISTS ${tableName}_temp ($createTableColumns)") + + val copyColumns = existingColumns.intersect(criticalColumns.toSet()) + if (copyColumns.isNotEmpty()) { + database.execSQL( + """ + INSERT INTO ${tableName}_temp (${copyColumns.joinToString(",")}) + SELECT ${copyColumns.joinToString(",")} FROM $tableName + """.trimIndent() + ) + } + + database.execSQL("DROP TABLE $tableName") + database.execSQL("ALTER TABLE ${tableName}_temp RENAME TO $tableName") + } + + } else { + database.execSQL("CREATE TABLE IF NOT EXISTS $tableName ($createTableColumns)") + } + } + + + migrateTable( + tableName = "PHCReviewMeeting", + criticalColumns = listOf( + "id", + "phcReviewDate", + "place", + "noOfBeneficiariesAttended", + "image1" + ), + createTableColumns = """ + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + phcReviewDate TEXT NOT NULL, + place TEXT, + noOfBeneficiariesAttended INTEGER, + image1 TEXT, + image2 TEXT, + syncState INTEGER NOT NULL DEFAULT 0 + """.trimIndent() + ) + + migrateTable( + tableName = "VHND", + criticalColumns = listOf( + "id", + "vhndDate", + "place", + "noOfBeneficiariesAttended", + "image1" + ), + createTableColumns = """ + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + vhndDate TEXT NOT NULL, + place TEXT, + noOfBeneficiariesAttended INTEGER, + image1 TEXT, + image2 TEXT, + syncState INTEGER NOT NULL DEFAULT 0 + """.trimIndent() + ) + + migrateTable( + tableName = "VHNC", + criticalColumns = listOf( + "id", + "vhncDate", + "place", + "noOfBeneficiariesAttended", + "image1" + ), + createTableColumns = """ + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + vhncDate TEXT NOT NULL, + place TEXT, + noOfBeneficiariesAttended INTEGER, + image1 TEXT, + image2 TEXT, + syncState INTEGER NOT NULL DEFAULT 0 + """.trimIndent() + ) + + migrateTable( + tableName = "AHDMeeting", + criticalColumns = listOf( + "id", + "mobilizedForAHD", + "ahdPlace", + "ahdDate", + "image1" + ), + createTableColumns = """ + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + mobilizedForAHD TEXT, + ahdPlace TEXT, + ahdDate TEXT, + image1 TEXT, + image2 TEXT, + syncState INTEGER NOT NULL DEFAULT 0 + """.trimIndent() + ) + + migrateTable( + tableName = "DewormingMeeting", + criticalColumns = listOf( + "id", + "dewormingDone", + "dewormingDate", + "dewormingLocation", + "ageGroup", + "image1" + ), + createTableColumns = """ + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + dewormingDone TEXT, + dewormingDate TEXT, + dewormingLocation TEXT, + ageGroup INTEGER, + image1 TEXT, + image2 TEXT, + regDate TEXT, + syncState INTEGER NOT NULL DEFAULT 0 + """.trimIndent() + ) + + migrateTable( + tableName = "NCD_REFER", + criticalColumns = listOf( + "id", + "benId", + "referredToInstituteID", + "refrredToAdditionalServiceList", + "referredToInstituteName", + "referralReason", + "revisitDate", + "vanID", + "parkingPlaceID", + "beneficiaryRegID", + "benVisitID", + "visitCode", + "providerServiceMapID", + "createdBy", + "isSpecialist", + "syncState" + ), + createTableColumns = """ + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + benId INTEGER NOT NULL, + referredToInstituteID INTEGER DEFAULT 0, + refrredToAdditionalServiceList TEXT DEFAULT 'undefined', + referredToInstituteName TEXT DEFAULT 'undefined', + referralReason TEXT DEFAULT 'undefined', + revisitDate INTEGER NOT NULL DEFAULT ${System.currentTimeMillis()}, + vanID INTEGER DEFAULT 0, + parkingPlaceID INTEGER DEFAULT 0, + beneficiaryRegID INTEGER DEFAULT 0, + benVisitID INTEGER DEFAULT 0, + visitCode INTEGER DEFAULT 0, + providerServiceMapID INTEGER DEFAULT 0, + createdBy TEXT DEFAULT '', + isSpecialist INTEGER DEFAULT 0, + syncState INTEGER NOT NULL DEFAULT 0, + FOREIGN KEY(benId) + REFERENCES BENEFICIARY(beneficiaryId) + ON UPDATE CASCADE + ON DELETE CASCADE + """.trimIndent() + ) + + database.execSQL( + """ + CREATE UNIQUE INDEX IF NOT EXISTS ind_refcache + ON NCD_REFER (benId) + """ + ) + + + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS MALARIA_SCREENING ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + + benId INTEGER NOT NULL, + visitId INTEGER NOT NULL, + houseHoldDetailsId INTEGER NOT NULL, + + caseDate INTEGER NOT NULL, + screeningDate INTEGER NOT NULL, + dateOfDeath INTEGER NOT NULL, + dateOfRdt INTEGER NOT NULL, + dateOfSlideTest INTEGER NOT NULL, + dateOfVisitBySupervisor INTEGER NOT NULL, + followUpDate INTEGER NOT NULL, + + beneficiaryStatus TEXT, + beneficiaryStatusId INTEGER NOT NULL, + + placeOfDeath TEXT, + otherPlaceOfDeath TEXT, + reasonForDeath TEXT, + otherReasonForDeath TEXT, + + rapidDiagnosticTest TEXT, + slideTestPf TEXT, + slideTestPv TEXT, + slideTestName TEXT, + + caseStatus TEXT, + referredTo INTEGER, + referToName TEXT, + otherReferredFacility TEXT, + remarks TEXT, + + diseaseTypeID INTEGER, + malariaTestType INTEGER, + malariaSlideTestType INTEGER, + + feverMoreThanTwoWeeks INTEGER, + fluLikeIllness INTEGER, + shakingChills INTEGER, + headache INTEGER, + muscleAches INTEGER, + tiredness INTEGER, + nausea INTEGER, + vomiting INTEGER, + diarrhea INTEGER, + + createdBy TEXT, + syncState INTEGER NOT NULL DEFAULT 0, + + FOREIGN KEY(benId) + REFERENCES BENEFICIARY(beneficiaryId) + ON UPDATE CASCADE + ON DELETE CASCADE + ) +""" + ) + + database.execSQL( + """ + CREATE UNIQUE INDEX IF NOT EXISTS ind_malariasn + ON MALARIA_SCREENING (benId, visitId) +""" + ) + + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS AES_SCREENING ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + + benId INTEGER NOT NULL, + houseHoldDetailsId INTEGER NOT NULL, + + visitDate INTEGER NOT NULL, + createdDate INTEGER NOT NULL, + dateOfDeath INTEGER NOT NULL, + + beneficiaryStatus TEXT, + beneficiaryStatusId INTEGER NOT NULL, + + placeOfDeath TEXT, + otherPlaceOfDeath TEXT, + reasonForDeath TEXT, + otherReasonForDeath TEXT, + + aesJeCaseStatus TEXT, + + referredTo INTEGER, + referToName TEXT, + otherReferredFacility TEXT, + + diseaseTypeID INTEGER, + followUpPoint INTEGER, + + createdBy TEXT, + syncState INTEGER NOT NULL DEFAULT 0, + + FOREIGN KEY(benId) + REFERENCES BENEFICIARY(beneficiaryId) + ON UPDATE CASCADE + ON DELETE CASCADE + ) +""" + ) + + database.execSQL( + """ + CREATE INDEX IF NOT EXISTS ind_aessn + ON AES_SCREENING (benId) +""" + ) + + + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS KALAZAR_SCREENING ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + + benId INTEGER NOT NULL, + houseHoldDetailsId INTEGER NOT NULL, + + visitDate INTEGER NOT NULL, + createdDate INTEGER NOT NULL, + dateOfDeath INTEGER NOT NULL, + dateOfRdt INTEGER NOT NULL, + + beneficiaryStatus TEXT, + beneficiaryStatusId INTEGER NOT NULL, + + placeOfDeath TEXT, + otherPlaceOfDeath TEXT, + reasonForDeath TEXT, + otherReasonForDeath TEXT, + + rapidDiagnosticTest TEXT, + kalaAzarCaseStatus TEXT, + + referredTo INTEGER, + referToName TEXT, + otherReferredFacility TEXT, + + diseaseTypeID INTEGER, + followUpPoint INTEGER, + + createdBy TEXT, + syncState INTEGER NOT NULL DEFAULT 0, + + FOREIGN KEY(benId) + REFERENCES BENEFICIARY(beneficiaryId) + ON UPDATE CASCADE + ON DELETE CASCADE + ) +""" + ) + + database.execSQL( + """ + CREATE INDEX IF NOT EXISTS ind_kalazarsn + ON KALAZAR_SCREENING (benId) +""" + ) + + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS FILARIA_SCREENING ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + + benId INTEGER NOT NULL, + houseHoldDetailsId INTEGER NOT NULL, + + mdaHomeVisitDate INTEGER NOT NULL, + createdDate INTEGER NOT NULL, + + sufferingFromFilariasis INTEGER, + doseStatus TEXT, + affectedBodyPart TEXT, + otherDoseStatusDetails TEXT, + filariasisCaseCount TEXT, + + medicineSideEffect TEXT, + otherSideEffectDetails TEXT, + + diseaseTypeID INTEGER, + createdBy TEXT, + + syncState INTEGER NOT NULL DEFAULT 0, + + FOREIGN KEY(benId) + REFERENCES BENEFICIARY(beneficiaryId) + ON UPDATE CASCADE + ON DELETE CASCADE + ) +""" + ) + + database.execSQL( + """ + CREATE INDEX IF NOT EXISTS ind_filariasn + ON FILARIA_SCREENING (benId) +""" + ) + + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS MALARIA_CONFIRMED ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + + diseaseId INTEGER NOT NULL, + benId INTEGER NOT NULL, + houseHoldDetailsId INTEGER NOT NULL, + + dateOfDiagnosis INTEGER NOT NULL, + treatmentStartDate INTEGER NOT NULL, + treatmentCompletionDate INTEGER NOT NULL, + + treatmentGiven TEXT, + referralDate INTEGER NOT NULL, + day TEXT, + + syncState INTEGER NOT NULL DEFAULT 0, + + FOREIGN KEY(benId) + REFERENCES BENEFICIARY(beneficiaryId) + ON UPDATE CASCADE + ON DELETE CASCADE + ) +""" + ) + + database.execSQL( + """ + CREATE INDEX IF NOT EXISTS ind_malariacs + ON MALARIA_CONFIRMED (benId) +""" + ) + + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS IRS_ROUND ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + date INTEGER NOT NULL, + rounds INTEGER NOT NULL, + householdId INTEGER NOT NULL + ) +""" + ) + + database.execSQL( + """ + CREATE INDEX IF NOT EXISTS ind_irs_round + ON IRS_ROUND (householdId) +""" + ) + + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS Adolescent_Health_Form_Data ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + userID INTEGER, + benId INTEGER, + + visitDate INTEGER NOT NULL, + healthStatus TEXT, + ifaTabletDistributed INTEGER, + quantityOfIfaTablets INTEGER, + menstrualHygieneAwarenessGiven INTEGER, + sanitaryNapkinDistributed INTEGER, + noOfPacketsDistributed INTEGER, + place TEXT, + distributionDate INTEGER NOT NULL, + referredToHealthFacility TEXT, + counselingProvided INTEGER, + counselingType TEXT, + followUpDate INTEGER NOT NULL, + + referralStatus TEXT, + syncState INTEGER NOT NULL DEFAULT 0, + + FOREIGN KEY(benId) + REFERENCES BENEFICIARY(beneficiaryId) + ON UPDATE CASCADE + ON DELETE CASCADE + ) +""" + ) + + database.execSQL( + """ + CREATE INDEX IF NOT EXISTS ind_adolescentsn + ON Adolescent_Health_Form_Data (benId) +""" + ) + + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS infant ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + + rchId TEXT NOT NULL, + name TEXT NOT NULL, + motherName TEXT NOT NULL, + fatherName TEXT, + dob TEXT NOT NULL, + gender TEXT NOT NULL, + phoneNumber TEXT NOT NULL, + sncuDischarged INTEGER NOT NULL DEFAULT 0 + ) +""" + ) + + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS SAAS_BAHU_ACTIVITY ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + + ashaId INTEGER NOT NULL, + place TEXT, + participants INTEGER, + date INTEGER, + + sammelanImages TEXT, + syncState INTEGER NOT NULL DEFAULT 0 + ) +""" + ) + } + } + + + val MIGRATION_43_44 = object : Migration(43, 44) { + override fun migrate(database: SupportSQLiteDatabase) { + + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS LEPROSY_SCREENING ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + benId INTEGER NOT NULL, + houseHoldDetailsId INTEGER NOT NULL, + visitNumber INTEGER, + isConfirmed INTEGER NOT NULL, + homeVisitDate INTEGER NOT NULL, + leprosyState TEXT, + leprosyStatusDate INTEGER NOT NULL, + lerosyStatusPosition INTEGER, + beneficiaryStatus TEXT, + beneficiaryStatusId INTEGER, + dateOfDeath INTEGER NOT NULL, + placeOfDeath TEXT, + otherPlaceOfDeath TEXT, + reasonForDeath TEXT, + otherReasonForDeath TEXT, + treatmentStatus TEXT, + mdtBlisterPackRecived TEXT, + leprosySymptoms TEXT, + typeOfLeprosy TEXT, + leprosySymptomsPosition INTEGER, + visitLabel TEXT, + leprosyStatus TEXT, + referredTo INTEGER, + referToName TEXT, + otherReferredTo TEXT, + treatmentEndDate INTEGER NOT NULL, + treatmentStartDate INTEGER NOT NULL, + currentVisitNumber INTEGER NOT NULL, + totalFollowUpMonthsRequired INTEGER NOT NULL, + diseaseTypeID INTEGER, + remarks TEXT, + syncState INTEGER NOT NULL, + createdBy TEXT NOT NULL, + createdDate INTEGER NOT NULL, + modifiedBy TEXT NOT NULL, + lastModDate INTEGER NOT NULL, + FOREIGN KEY(benId) REFERENCES BENEFICIARY(beneficiaryId) ON UPDATE CASCADE ON DELETE CASCADE + ) + """ + ) + database.execSQL( + """ + CREATE UNIQUE INDEX IF NOT EXISTS ind_leprosysn ON LEPROSY_SCREENING (benId) + """ + ) + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS LEPROSY_FOLLOW_UP ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + benId INTEGER NOT NULL, + visitNumber INTEGER NOT NULL, + followUpDate INTEGER NOT NULL, + treatmentStatus TEXT, + mdtBlisterPackReceived TEXT, + treatmentCompleteDate INTEGER NOT NULL, + remarks TEXT, + homeVisitDate INTEGER NOT NULL, + leprosySymptoms TEXT, + typeOfLeprosy TEXT, + leprosySymptomsPosition INTEGER, + visitLabel TEXT, + leprosyStatus TEXT, + referredTo INTEGER, + referToName TEXT, + treatmentEndDate INTEGER NOT NULL, + mdtBlisterPackRecived TEXT, + treatmentStartDate INTEGER NOT NULL, + syncState INTEGER NOT NULL, + createdBy TEXT NOT NULL, + createdDate INTEGER NOT NULL, + modifiedBy TEXT NOT NULL, + lastModDate INTEGER NOT NULL + ) + """ + ) + + database.execSQL("CREATE INDEX IF NOT EXISTS ind_leprosy_followup_ben ON LEPROSY_FOLLOW_UP (benId)") + database.execSQL("CREATE INDEX IF NOT EXISTS ind_leprosy_followup_visit ON LEPROSY_FOLLOW_UP (benId, visitNumber)") + + + } + } + + + val MIGRATION_42_43 = object : Migration(42, 43) { + override fun migrate(db: SupportSQLiteDatabase) { + + if (!tableExists(db, "LEPROSY_SCREENING")) { + return + } + + val columns = listOf( + "leprosySymptoms" to "TEXT", + "visitLabel" to "TEXT", + "visitNumber" to "INTEGER", + "leprosySymptomsPosition" to "INTEGER DEFAULT 1", + "isConfirmed" to "INTEGER NOT NULL DEFAULT 0", + "treatmentStartDate" to "INTEGER NOT NULL DEFAULT 0", + "treatmentEndDate" to "INTEGER NOT NULL DEFAULT 0", + "mdtBlisterPackRecived" to "TEXT", + "treatmentStatus" to "TEXT", + "leprosyState" to "TEXT" + ) + + for ((column, type) in columns) { + if (!columnExists(db, "LEPROSY_SCREENING", column)) { + db.execSQL( + "ALTER TABLE LEPROSY_SCREENING ADD COLUMN $column $type" + ) + } + } + } + } + + + val MIGRATION_41_42 = object : Migration(41, 42) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE form_schema ADD COLUMN language TEXT NOT NULL DEFAULT 'en'") + if (tableExists(database, "HRP_MICRO_BIRTH_PLAN")) { + + if (!columnExists(database, "HRP_MICRO_BIRTH_PLAN", "processed")) { + database.execSQL( + "ALTER TABLE HRP_MICRO_BIRTH_PLAN ADD COLUMN processed TEXT" + ) + } + } + } + } + + val MIGRATION_40_41 = object : Migration(40, 41) { + override fun migrate(db: SupportSQLiteDatabase) { + + if (tableExists(db, "PREGNANCY_ANC")) { + + if (!columnExists(db, "PREGNANCY_ANC", "isYesOrNo")) { + db.execSQL( + "ALTER TABLE PREGNANCY_ANC ADD COLUMN isYesOrNo INTEGER " + ) + } + + if (!columnExists(db, "PREGNANCY_ANC", "dateSterilisation")) { + db.execSQL( + "ALTER TABLE PREGNANCY_ANC ADD COLUMN dateSterilisation INTEGER" + ) + } + + if (columnExists(db, "PREGNANCY_ANC", "isPaiucdId")) { + db.execSQL( + """ + UPDATE PREGNANCY_ANC + SET isPaiucdId = CASE + WHEN isPaiucdId = 1 THEN 1 + ELSE 0 + END + """.trimIndent() + ) + } + + db.execSQL( + "ALTER TABLE PREGNANCY_ANC ADD COLUMN isPaiucd TEXT" + ) + + db.execSQL( + "ALTER TABLE PREGNANCY_ANC ADD COLUMN remarks TEXT" + ) + db.execSQL( + "ALTER TABLE PREGNANCY_ANC ADD COLUMN isPaiucdId INTEGER" + ) + db.execSQL( + "ALTER TABLE PREGNANCY_ANC ADD COLUMN serialNo TEXT " + ) + db.execSQL("ALTER TABLE PREGNANCY_ANC ADD COLUMN abortionImg1 TEXT") + db.execSQL("ALTER TABLE PREGNANCY_ANC ADD COLUMN abortionImg2 TEXT") + + + } + + } + } + + + val MIGRATION_39_40 = object : Migration(39, 40) { + override fun migrate(database: SupportSQLiteDatabase) { + + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS `ALL_BEN_IFA_VISIT_HISTORY` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + `benId` INTEGER NOT NULL, + `hhId` INTEGER NOT NULL, + `visitDate` TEXT NOT NULL, + `formId` TEXT NOT NULL, + `version` INTEGER NOT NULL, + `formDataJson` TEXT NOT NULL, + `isSynced` INTEGER NOT NULL, + `createdAt` INTEGER NOT NULL, + `syncedAt` INTEGER + ) + """.trimIndent() + ) + + database.execSQL( + """ + CREATE UNIQUE INDEX IF NOT EXISTS + `index_ALL_BEN_IFA_VISIT_HISTORY_benId_hhId_visitDate_formId` + ON `ALL_BEN_IFA_VISIT_HISTORY` (`benId`, `hhId`, `visitDate`, `formId`) + """.trimIndent() + ) + } + } + + + val MIGRATION_38_39 = object : Migration(38, 39) { + override fun migrate(database: SupportSQLiteDatabase) { + + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS FILARIA_MDA_VISIT_HISTORY ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + hhId INTEGER NOT NULL, + visitDate TEXT NOT NULL, + visitMonth TEXT NOT NULL, + formId TEXT NOT NULL, + version INTEGER NOT NULL, + formDataJson TEXT NOT NULL, + isSynced INTEGER NOT NULL DEFAULT 0, + createdAt INTEGER NOT NULL DEFAULT (strftime('%s','now')), + syncedAt TEXT + ) + """.trimIndent() + ) + database.execSQL( + """ + CREATE UNIQUE INDEX IF NOT EXISTS index_FILARIA_MDA_VISIT_HISTORY_hhId_formId_visitMonth + ON FILARIA_MDA_VISIT_HISTORY (hhId, formId, visitMonth) + """.trimIndent() + ) + + database.execSQL( + """ + CREATE INDEX IF NOT EXISTS index_FILARIA_MDA_VISIT_HISTORY_hhId_visitDate + ON FILARIA_MDA_VISIT_HISTORY (hhId, visitDate) + """.trimIndent() + ) + } + } + + + val MIGRATION_37_38 = object : Migration(37, 38) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE INFANT_REG ADD COLUMN isSNCU TEXT") + database.execSQL("ALTER TABLE INFANT_REG ADD COLUMN deliveryDischargeSummary1 TEXT") + database.execSQL("ALTER TABLE INFANT_REG ADD COLUMN deliveryDischargeSummary2 TEXT") + database.execSQL("ALTER TABLE INFANT_REG ADD COLUMN deliveryDischargeSummary3 TEXT") + database.execSQL("ALTER TABLE INFANT_REG ADD COLUMN deliveryDischargeSummary4 TEXT") + } + } + + val MIGRATION_36_37 = object : Migration(36, 37) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS mosquito_net_visit ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + hhId INTEGER NOT NULL, + visitDate TEXT NOT NULL, + formId TEXT NOT NULL, + version INTEGER NOT NULL, + formDataJson TEXT NOT NULL, + isSynced INTEGER NOT NULL DEFAULT 0, + syncedAt TEXT + ) + """.trimIndent() + ) + + database.execSQL( + """ + CREATE UNIQUE INDEX IF NOT EXISTS index_mosquito_net_visit_unique + ON mosquito_net_visit (hhId, visitDate, formId) + """.trimIndent() + ) + } + } + + val MIGRATION_35_36 = object : Migration(35, 36) { + override fun migrate(db: SupportSQLiteDatabase) { + + + if (tableExists(db, "ALL_EYE_SURGERY_VISIT_HISTORY")) { + + db.execSQL("ALTER TABLE ALL_EYE_SURGERY_VISIT_HISTORY RENAME TO temp_eye_history") + + db.execSQL( + """ + CREATE TABLE ALL_EYE_SURGERY_VISIT_HISTORY ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + benId INTEGER NOT NULL, + hhId INTEGER NOT NULL, + visitDate TEXT NOT NULL, + formId TEXT NOT NULL, + version INTEGER NOT NULL, + formDataJson TEXT NOT NULL, + isSynced INTEGER NOT NULL DEFAULT 0, + createdAt INTEGER NOT NULL, + syncedAt TEXT, + visitMonth TEXT NOT NULL DEFAULT '' + ) + """.trimIndent() + ) + + db.execSQL( + """ + INSERT INTO ALL_EYE_SURGERY_VISIT_HISTORY ( + id, benId, hhId, visitDate, formId, version, formDataJson, isSynced, createdAt, syncedAt, visitMonth + ) + SELECT id, benId, hhId, visitDate, formId, version, formDataJson, isSynced, createdAt, syncedAt, visitMonth + FROM temp_eye_history + """.trimIndent() + ) + + db.execSQL("DROP TABLE temp_eye_history") + + db.execSQL( + """ + CREATE UNIQUE INDEX IF NOT EXISTS index_ALL_EYE_SURGERY_VISIT_HISTORY_benId_formId_visitMonth + ON ALL_EYE_SURGERY_VISIT_HISTORY(benId, formId, visitMonth) + """.trimIndent() + ) + + db.execSQL( + """ + CREATE INDEX IF NOT EXISTS index_ALL_EYE_SURGERY_VISIT_HISTORY_benId_visitDate + ON ALL_EYE_SURGERY_VISIT_HISTORY(benId, visitDate) + """.trimIndent() + ) + } + + if (tableExists(db, "MALARIA_SCREENING")) { + + db.execSQL("DROP INDEX IF EXISTS ind_malariasn") + + if (!columnExists(db, "MALARIA_SCREENING", "visitId")) { + db.execSQL( + "ALTER TABLE MALARIA_SCREENING ADD COLUMN visitId INTEGER NOT NULL DEFAULT 1" + ) + } + + db.execSQL( + "CREATE UNIQUE INDEX IF NOT EXISTS ind_malariasn ON MALARIA_SCREENING(benId, visitId)" + ) + } + } + } + + + val MIGRATION_34_35 = object : Migration(34, 35) { + override fun migrate(database: SupportSQLiteDatabase) { + + database.execSQL( + """ + CREATE TABLE ALL_EYE_SURGERY_VISIT_HISTORY ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + benId INTEGER NOT NULL, + hhId INTEGER NOT NULL, + visitDate TEXT NOT NULL, + formId TEXT NOT NULL, + version INTEGER NOT NULL, + formDataJson TEXT NOT NULL, + isSynced INTEGER NOT NULL DEFAULT 0, + createdAt INTEGER NOT NULL, + syncedAt TEXT, + visitMonth TEXT NOT NULL DEFAULT '' + ) + """.trimIndent() + ) + + database.execSQL( + """ + CREATE UNIQUE INDEX IF NOT EXISTS + index_ALL_EYE_SURGERY_VISIT_HISTORY_benId_hhId_visitDate_formId + ON ALL_EYE_SURGERY_VISIT_HISTORY (benId, hhId, visitDate, formId) + """.trimIndent() + ) + + if (tableExists(database, "MALARIA_SCREENING")) { + + if (!columnExists(database, "MALARIA_SCREENING", "visitId")) { + database.execSQL( + "ALTER TABLE MALARIA_SCREENING ADD COLUMN visitId INTEGER NOT NULL DEFAULT 1" + ) + } + + if (!columnExists(database, "MALARIA_SCREENING", "malariaTestType")) { + database.execSQL( + "ALTER TABLE MALARIA_SCREENING ADD COLUMN malariaTestType INTEGER DEFAULT 0" + ) + } + + if (!columnExists(database, "MALARIA_SCREENING", "malariaSlideTestType")) { + database.execSQL( + "ALTER TABLE MALARIA_SCREENING ADD COLUMN malariaSlideTestType INTEGER DEFAULT 0" + ) + } + + database.execSQL( + "CREATE UNIQUE INDEX IF NOT EXISTS ind_malariasn ON MALARIA_SCREENING(benId, visitId)" + ) + } + } + } + + + val MIGRATION_33_34 = object : Migration(33, 34) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS form_schema ( + formId TEXT NOT NULL PRIMARY KEY, + formName TEXT NOT NULL, + version INTEGER NOT NULL DEFAULT 1, + schemaJson TEXT NOT NULL + ) + """.trimIndent() + ) + + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS all_visit_history ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + benId INTEGER NOT NULL, + hhId INTEGER NOT NULL, + visitDay TEXT NOT NULL, + visitDate TEXT NOT NULL, + formId TEXT NOT NULL, + version INTEGER NOT NULL, + formDataJson TEXT NOT NULL, + isSynced INTEGER NOT NULL DEFAULT 0, + createdAt INTEGER NOT NULL, + syncedAt INTEGER + ) + """.trimIndent() + ) + + database.execSQL( + """ + CREATE UNIQUE INDEX IF NOT EXISTS index_all_visit_history_unique + ON all_visit_history (benId, hhId, visitDay, visitDate, formId) + """.trimIndent() + ) + } + } + + + val MIGRATION_32_33 = object : Migration(32, 33) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS ALL_VISIT_HISTORY_HBYC ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + benId INTEGER NOT NULL, + hhId INTEGER NOT NULL, + visitDay TEXT NOT NULL, + visitDate TEXT NOT NULL, + formId TEXT NOT NULL, + version INTEGER NOT NULL, + formDataJson TEXT NOT NULL, + isSynced INTEGER NOT NULL DEFAULT 0, + createdAt INTEGER NOT NULL, + syncedAt INTEGER + ) + """.trimIndent() + ) + database.execSQL( + """ + CREATE UNIQUE INDEX IF NOT EXISTS index_all_visit_history_hbyc_unique + ON ALL_VISIT_HISTORY_HBYC (benId, hhId, visitDay, visitDate, formId) + """.trimIndent() + ) + } + } + + val MIGRATION_31_32 = object : Migration(31, 32) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS `children_under_five_all_visit` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + `benId` INTEGER NOT NULL, + `hhId` INTEGER NOT NULL, + `visitDate` TEXT NOT NULL, + `formId` TEXT NOT NULL, + `version` INTEGER NOT NULL, + `formDataJson` TEXT NOT NULL, + `isSynced` INTEGER NOT NULL DEFAULT 0, + `createdAt` INTEGER NOT NULL, + `updatedAt` INTEGER NOT NULL, + `syncedAt` INTEGER + ) + """.trimIndent() + ) + + database.execSQL( + """ + CREATE UNIQUE INDEX IF NOT EXISTS `index_children_under_five_all_visit_benId_hhId_visitDate_formId` + ON `children_under_five_all_visit` (`benId`, `hhId`, `visitDate`, `formId`) + """.trimIndent() + ) + } + } + + + val MIGRATION_30_31 = object : Migration(30, 31) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE CBAC ADD COLUMN isReffered INTEGER DEFAULT 0") + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS form_schema ( + formId TEXT NOT NULL PRIMARY KEY, + formName TEXT NOT NULL, + version INTEGER NOT NULL DEFAULT 1, + schemaJson TEXT NOT NULL + ) + """.trimIndent() + ) + database.execSQL("ALTER TABLE IMMUNIZATION ADD COLUMN mcpCardSummary1 TEXT") + database.execSQL("ALTER TABLE IMMUNIZATION ADD COLUMN mcpCardSummary2 TEXT") + database.execSQL("DROP TABLE IF EXISTS `UWIN_SESSION`") + database.execSQL( + """CREATE TABLE IF NOT EXISTS `UWIN_SESSION` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + `sessionDate` INTEGER NOT NULL, + `place` TEXT, + `participantsCount` INTEGER NOT NULL, + `uploadedFiles1` TEXT, + `uploadedFiles2` TEXT, + `processed` TEXT, + `createdBy` TEXT NOT NULL, + `createdDate` INTEGER NOT NULL, + `updatedBy` TEXT NOT NULL, + `updatedDate` INTEGER NOT NULL, + `syncState` INTEGER NOT NULL + ) + """.trimIndent() + ) + } + } + + + val MIGRATION_29_30 = object : Migration(29, 30) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE eligible_couple_tracking ADD COLUMN dischargeSummary1 TEXT") + database.execSQL("ALTER TABLE eligible_couple_tracking ADD COLUMN dischargeSummary2 TEXT") + database.execSQL( + "CREATE TABLE IF NOT EXISTS `MAA_MEETING` (" + + "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " + + "`meetingDate` TEXT, `place` TEXT, `participants` INTEGER, `ashaId` INTEGER, " + + "`meetingImages` TEXT, " + + "`createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `syncState` INTEGER NOT NULL)" + ) + database.execSQL("ALTER TABLE ELIGIBLE_COUPLE_REG ADD COLUMN isKitHandedOver INTEGER ") + database.execSQL("ALTER TABLE ELIGIBLE_COUPLE_REG ADD COLUMN kitHandedOverDate INTEGER") + database.execSQL("ALTER TABLE ELIGIBLE_COUPLE_REG ADD COLUMN kitPhoto1 TEXT") + database.execSQL("ALTER TABLE ELIGIBLE_COUPLE_REG ADD COLUMN kitPhoto2 TEXT") + database.execSQL("ALTER TABLE ELIGIBLE_COUPLE_REG ADD COLUMN lmpDate INTEGER NOT NULL DEFAULT 0 ") + database.execSQL("ALTER TABLE ELIGIBLE_COUPLE_REG ADD COLUMN lmp_date INTEGER NOT NULL DEFAULT 0") + + } + } + + +// val MIGRATION_22_23 = object : Migration(22, 23) { +// override fun migrate(database: SupportSQLiteDatabase) { +// database.execSQL("ALTER TABLE GENERAL_OPD_ACTIVITY ADD COLUMN village TEXT") +// } +// } + + + val MIGRATION_28_29 = object : Migration(28, 29) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE PMSMA ADD COLUMN visitDate INTEGER") + database.execSQL("ALTER TABLE PMSMA ADD COLUMN visitNumber INTEGER NOT NULL DEFAULT 0") + database.execSQL("ALTER TABLE PMSMA ADD COLUMN anyOtherHighRiskCondition TEXT") + } + } + + val MIGRATION_27_28 = object : Migration(27, 28) { + override fun migrate(database: SupportSQLiteDatabase) { + val cursor = database.query("PRAGMA table_info(PNC_VISIT)") + val existingColumns = mutableSetOf() + while (cursor.moveToNext()) { + existingColumns.add(cursor.getString(1)) + } + cursor.close() + + if (!existingColumns.contains("deliveryDischargeSummary1")) { + database.execSQL("ALTER TABLE PNC_VISIT ADD COLUMN deliveryDischargeSummary1 TEXT") + } + if (!existingColumns.contains("deliveryDischargeSummary2")) { + database.execSQL("ALTER TABLE PNC_VISIT ADD COLUMN deliveryDischargeSummary2 TEXT") + } + if (!existingColumns.contains("deliveryDischargeSummary3")) { + database.execSQL("ALTER TABLE PNC_VISIT ADD COLUMN deliveryDischargeSummary3 TEXT") + } + if (!existingColumns.contains("deliveryDischargeSummary4")) { + database.execSQL("ALTER TABLE PNC_VISIT ADD COLUMN deliveryDischargeSummary4 TEXT") + } + if (!existingColumns.contains("sterilisationDate")) { + database.execSQL("ALTER TABLE PNC_VISIT ADD COLUMN sterilisationDate INTEGER ") + } + if (!existingColumns.contains("anyDangerSign")) { + database.execSQL("ALTER TABLE PNC_VISIT ADD COLUMN anyDangerSign TEXT ") + } + } + + } + + val MIGRATION_26_27 = object : Migration(26, 27) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE INCENTIVE_ACTIVITY ADD COLUMN groupName TEXT NOT NULL DEFAULT 'undefined'") + + } + } + + + val MIGRATION_25_26 = object : Migration(25, 26) { + override fun migrate(database: SupportSQLiteDatabase) { + if (tableExists(database, "PREGNANCY_ANC")) { + if (!columnExists(database, "PREGNANCY_ANC", "lmpDate")) { + database.execSQL("ALTER TABLE PREGNANCY_ANC ADD COLUMN lmpDate INTEGER") + } + if (!columnExists(database, "PREGNANCY_ANC", "visitDate")) { + database.execSQL("ALTER TABLE PREGNANCY_ANC ADD COLUMN visitDate INTEGER") + } + if (!columnExists(database, "PREGNANCY_ANC", "weekOfPregnancy")) { + database.execSQL("ALTER TABLE PREGNANCY_ANC ADD COLUMN weekOfPregnancy INTEGER") + } + if (!columnExists(database, "PREGNANCY_ANC", "placeOfDeath")) { + database.execSQL("ALTER TABLE PREGNANCY_ANC ADD COLUMN placeOfDeath TEXT") + } + if (!columnExists(database, "PREGNANCY_ANC", "placeOfDeathId")) { + database.execSQL("ALTER TABLE PREGNANCY_ANC ADD COLUMN placeOfDeathId INTEGER") + } + if (!columnExists(database, "PREGNANCY_ANC", "otherPlaceOfDeath")) { + database.execSQL("ALTER TABLE PREGNANCY_ANC ADD COLUMN otherPlaceOfDeath TEXT") + } + if (!columnExists(database, "PREGNANCY_ANC", "methodOfTermination")) { + database.execSQL("ALTER TABLE PREGNANCY_ANC ADD COLUMN methodOfTermination TEXT") + } + if (!columnExists(database, "PREGNANCY_ANC", "methodOfTerminationId")) { + database.execSQL("ALTER TABLE PREGNANCY_ANC ADD COLUMN methodOfTerminationId INTEGER") + } + if (!columnExists(database, "PREGNANCY_ANC", "terminationDoneBy")) { + database.execSQL("ALTER TABLE PREGNANCY_ANC ADD COLUMN terminationDoneBy TEXT") + } + if (!columnExists(database, "PREGNANCY_ANC", "terminationDoneById")) { + database.execSQL("ALTER TABLE PREGNANCY_ANC ADD COLUMN terminationDoneById INTEGER") + } + } + } + } + +// val MIGRATION_20_21 = object : Migration(20, 21) { +// override fun migrate(database: SupportSQLiteDatabase) { +// database.execSQL(""" +// CREATE TABLE IF NOT EXISTS `GENERAL_OPD_ACTIVITY` ( +// `benFlowID` INTEGER, +// `beneficiaryRegID` INTEGER, +// `benVisitID` INTEGER, +// `visitCode` INTEGER, +// `benVisitNo` INTEGER, +// `nurseFlag` INTEGER, +// `doctorFlag` INTEGER, +// `pharmacist_flag` INTEGER, +// `lab_technician_flag` INTEGER, +// `radiologist_flag` INTEGER, +// `oncologist_flag` INTEGER, +// `specialist_flag` INTEGER, +// `agentId` TEXT, +// `visitDate` TEXT, +// `modified_by` TEXT, +// `modified_date` TEXT, +// `benName` TEXT, +// `deleted` INTEGER, +// `firstName` TEXT, +// `lastName` TEXT, +// `age` TEXT, +// `ben_age_val` INTEGER, +// `genderID` INTEGER, +// `genderName` TEXT, +// `preferredPhoneNum` TEXT, +// `fatherName` TEXT, +// `spouseName` TEXT, +// `districtName` TEXT, +// `servicePointName` TEXT, +// `registrationDate` TEXT, +// `benVisitDate` TEXT, +// `consultationDate` TEXT, +// `consultantID` INTEGER, +// `consultantName` TEXT, +// `visitSession` TEXT, +// `servicePointID` INTEGER, +// `districtID` INTEGER, +// `villageID` INTEGER, +// `vanID` INTEGER, +// `beneficiaryId` INTEGER NOT NULL, +// `dob` TEXT, +// `tc_SpecialistLabFlag` INTEGER, +// `visitReason` TEXT, +// `visitCategory` TEXT, +// PRIMARY KEY(`beneficiaryId`) +// ) +// """) +// } +// } + +// val MIGRATION_19_20 = object : Migration(19, 20) { +// override fun migrate(database: SupportSQLiteDatabase) { +// database.execSQL("ALTER TABLE MALARIA_SCREENING ADD COLUMN slideTestName TEXT") +// } +// } + + val MIGRATION_24_25 = object : Migration(24, 25) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE ELIGIBLE_COUPLE_TRACKING ADD COLUMN dateOfAntraInjection TEXT") + database.execSQL("ALTER TABLE ELIGIBLE_COUPLE_TRACKING ADD COLUMN dueDateOfAntraInjection TEXT") + database.execSQL("ALTER TABLE ELIGIBLE_COUPLE_TRACKING ADD COLUMN mpaFile TEXT") + database.execSQL("ALTER TABLE ELIGIBLE_COUPLE_TRACKING ADD COLUMN antraDose TEXT") + database.execSQL("ALTER TABLE ELIGIBLE_COUPLE_TRACKING ADD COLUMN lmp_date INTEGER NOT NULL DEFAULT 0") + database.execSQL("ALTER TABLE ELIGIBLE_COUPLE_TRACKING ADD COLUMN lmpDate INTEGER NOT NULL DEFAULT 0") + + + } + } + + val MIGRATION_23_24 = object : Migration(23, 24) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE MDSR ADD COLUMN mdsr1File TEXT") + database.execSQL("ALTER TABLE MDSR ADD COLUMN mdsr2File TEXT") + database.execSQL("ALTER TABLE MDSR ADD COLUMN mdsrDeathCertFile TEXT") + } + } + val MIGRATION_22_23 = object : Migration(22, 23) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL( + "ALTER TABLE PNC_VISIT ADD COLUMN otherPlaceOfDeath TEXT" + ) + database.execSQL("ALTER TABLE GENERAL_OPD_ACTIVITY ADD COLUMN village TEXT") + } + } + + +// val MIGRATION_21_22 = object : Migration(21, 22) { +// override fun migrate(database: SupportSQLiteDatabase) { +// database.execSQL("ALTER TABLE DELIVERY_OUTCOME ADD COLUMN isDeath INTEGER") +// database.execSQL("ALTER TABLE DELIVERY_OUTCOME ADD COLUMN isDeathValue TEXT") +// database.execSQL("ALTER TABLE DELIVERY_OUTCOME ADD COLUMN dateOfDeath TEXT") +// database.execSQL("ALTER TABLE DELIVERY_OUTCOME ADD COLUMN placeOfDeath TEXT") +// database.execSQL("ALTER TABLE DELIVERY_OUTCOME ADD COLUMN placeOfDeathId INTEGER DEFAULT 0") +// database.execSQL("ALTER TABLE DELIVERY_OUTCOME ADD COLUMN otherPlaceOfDeath TEXT") +// database.execSQL("ALTER TABLE DELIVERY_OUTCOME ADD COLUMN mcp1File TEXT") +// database.execSQL("ALTER TABLE DELIVERY_OUTCOME ADD COLUMN mcp2File TEXT") +// database.execSQL("ALTER TABLE DELIVERY_OUTCOME ADD COLUMN jsyFile TEXT") +// } +// } + + + val MIGRATION_20_21 = object : Migration(20, 21) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE CDR ADD COLUMN cdr1File TEXT") + database.execSQL("ALTER TABLE CDR ADD COLUMN cdr2File TEXT") + database.execSQL("ALTER TABLE CDR ADD COLUMN cdrDeathCertFile TEXT") + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS `GENERAL_OPD_ACTIVITY` ( + `benFlowID` INTEGER, + `beneficiaryRegID` INTEGER, + `benVisitID` INTEGER, + `visitCode` INTEGER, + `benVisitNo` INTEGER, + `nurseFlag` INTEGER, + `doctorFlag` INTEGER, + `pharmacist_flag` INTEGER, + `lab_technician_flag` INTEGER, + `radiologist_flag` INTEGER, + `oncologist_flag` INTEGER, + `specialist_flag` INTEGER, + `agentId` TEXT, + `visitDate` TEXT, + `modified_by` TEXT, + `modified_date` TEXT, + `benName` TEXT, + `deleted` INTEGER, + `firstName` TEXT, + `lastName` TEXT, + `age` TEXT, + `ben_age_val` INTEGER, + `genderID` INTEGER, + `genderName` TEXT, + `preferredPhoneNum` TEXT, + `fatherName` TEXT, + `spouseName` TEXT, + `districtName` TEXT, + `servicePointName` TEXT, + `registrationDate` TEXT, + `benVisitDate` TEXT, + `consultationDate` TEXT, + `consultantID` INTEGER, + `consultantName` TEXT, + `visitSession` TEXT, + `servicePointID` INTEGER, + `districtID` INTEGER, + `villageID` INTEGER, + `vanID` INTEGER, + `beneficiaryId` INTEGER NOT NULL, + `dob` TEXT, + `tc_SpecialistLabFlag` INTEGER, + `visitReason` TEXT, + `visitCategory` TEXT, + PRIMARY KEY(`beneficiaryId`) + ) + """ + ) + } + } + val MIGRATION_19_20 = object : Migration(19, 20) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("DROP VIEW IF EXISTS BEN_BASIC_CACHE") + db.execSQL( + "CREATE VIEW `BEN_BASIC_CACHE` AS " + + "SELECT b.beneficiaryId as benId, b.motherName as motherName, b.householdId as hhId, b.regDate, b.firstName as benName, b.lastName as benSurname, b.gender, b.dob as dob, b.familyHeadRelationPosition as relToHeadId" + + ", b.contactNumber as mobileNo, b.fatherName, h.fam_familyHeadName as familyHeadName, b.gen_spouseName as spouseName, b.rchId, b.gen_lastMenstrualPeriod as lastMenstrualPeriod" + + ", b.isHrpStatus as hrpStatus, b.syncState, b.gen_reproductiveStatusId as reproductiveStatusId, b.isKid, b.immunizationStatus" + + ", b.loc_village_id as villageId, b.abha_healthIdNumber as abhaId" + + ", b.isNewAbha" + + ", IFNULL(cbac.benId IS NOT NULL, 0) as cbacFilled, cbac.syncState as cbacSyncState" + + ", IFNULL(cdr.benId IS NOT NULL, 0) as cdrFilled, cdr.syncState as cdrSyncState" + + ", IFNULL(mdsr.benId IS NOT NULL, 0) as mdsrFilled, mdsr.syncState as mdsrSyncState" + + ", IFNULL(pmsma.benId IS NOT NULL, 0) as pmsmaFilled, pmsma.syncState as pmsmaSyncState" + + ", IFNULL(hbnc.benId IS NOT NULL, 0) as hbncFilled" + + ", IFNULL(hbyc.benId IS NOT NULL, 0) as hbycFilled" + + ", IFNULL(pwr.benId IS NOT NULL, 0) as pwrFilled, pwr.syncState as pwrSyncState" + + ", IFNULL(pwa.pregnantWomanDelivered, 0) as isDelivered, IFNULL(pwa.hrpConfirmed, 0) as pwHrp" + + ", IFNULL(ecr.benId IS NOT NULL, 0) as ecrFilled" + + ", IFNULL(ect.benId IS NOT NULL, 0) as ectFilled" + + ", IFNULL((pwa.maternalDeath OR do.complication = 'DEATH' OR pnc.motherDeath), 0) as isMdsr" + + ", IFNULL(tbsn.benId IS NOT NULL, 0) as tbsnFilled, tbsn.syncState as tbsnSyncState" + + ", IFNULL(tbsp.benId IS NOT NULL, 0) as tbspFilled, tbsp.syncState as tbspSyncState" + + ", IFNULL(ir.motherBenId IS NOT NULL, 0) as irFilled, ir.syncState as irSyncState" + + ", IFNULL(cr.motherBenId IS NOT NULL, 0) as crFilled, cr.syncState as crSyncState" + + ", IFNULL(do.benId IS NOT NULL, 0) as doFilled, do.syncState as doSyncState" + + ", IFNULL((hrppa.benId IS NOT NULL AND hrppa.noOfDeliveries IS NOT NULL AND hrppa.timeLessThan18m IS NOT NULL AND hrppa.heightShort IS NOT NULL AND hrppa.age IS NOT NULL AND hrppa.rhNegative IS NOT NULL AND hrppa.homeDelivery IS NOT NULL AND hrppa.badObstetric IS NOT NULL AND hrppa.multiplePregnancy IS NOT NULL), 0) as hrppaFilled, hrppa.syncState as hrppaSyncState" + + ", IFNULL((hrpnpa.benId IS NOT NULL AND hrpnpa.noOfDeliveries IS NOT NULL AND hrpnpa.timeLessThan18m IS NOT NULL AND hrpnpa.heightShort IS NOT NULL AND hrpnpa.age IS NOT NULL AND hrpnpa.misCarriage IS NOT NULL AND hrpnpa.homeDelivery IS NOT NULL AND hrpnpa.medicalIssues IS NOT NULL AND hrpnpa.pastCSection IS NOT NULL), 0) as hrpnpaFilled, hrpnpa.syncState as hrpnpaSyncState" + + ", IFNULL(hrpmbp.benId IS NOT NULL, 0) as hrpmbpFilled, hrpmbp.syncState as hrpmbpSyncState" + + ", IFNULL(hrpt.benId IS NOT NULL, 0) as hrptFilled, IFNULL(((count(distinct hrpt.id) > 3) OR (((JulianDay('now')) - JulianDay(date(max(hrpt.visitDate)/1000,'unixepoch','localtime'))) < 1)), 0) as hrptrackingDone, hrpt.syncState as hrptSyncState" + + ", IFNULL(hrnpt.benId IS NOT NULL, 0) as hrnptFilled, IFNULL(((JulianDay('now') - JulianDay(date(max(hrnpt.visitDate)/1000,'unixepoch','localtime'))) < 1), 0) as hrnptrackingDone, hrnpt.syncState as hrnptSyncState " + + "FROM BENEFICIARY b " + + "JOIN HOUSEHOLD h ON b.householdId = h.householdId " + + "LEFT OUTER JOIN CBAC cbac ON b.beneficiaryId = cbac.benId " + + "LEFT OUTER JOIN CDR cdr ON b.beneficiaryId = cdr.benId " + + "LEFT OUTER JOIN MDSR mdsr ON b.beneficiaryId = mdsr.benId " + + "LEFT OUTER JOIN PMSMA pmsma ON b.beneficiaryId = pmsma.benId " + + "LEFT OUTER JOIN HBNC hbnc ON b.beneficiaryId = hbnc.benId " + + "LEFT OUTER JOIN HBYC hbyc ON b.beneficiaryId = hbyc.benId " + + "LEFT OUTER JOIN PREGNANCY_REGISTER pwr ON b.beneficiaryId = pwr.benId " + + "LEFT OUTER JOIN PREGNANCY_ANC pwa ON b.beneficiaryId = pwa.benId " + + "LEFT OUTER JOIN pnc_visit pnc ON b.beneficiaryId = pnc.benId " + + "LEFT OUTER JOIN ELIGIBLE_COUPLE_REG ecr ON b.beneficiaryId = ecr.benId " + + "LEFT OUTER JOIN ELIGIBLE_COUPLE_TRACKING ect ON (b.beneficiaryId = ect.benId AND CAST((strftime('%s','now') - ect.visitDate/1000)/60/60/24 AS INTEGER) < 30) " + + "LEFT OUTER JOIN TB_SCREENING tbsn ON b.beneficiaryId = tbsn.benId " + + "LEFT OUTER JOIN TB_SUSPECTED tbsp ON b.beneficiaryId = tbsp.benId " + + "LEFT OUTER JOIN HRP_PREGNANT_ASSESS hrppa ON b.beneficiaryId = hrppa.benId " + + "LEFT OUTER JOIN HRP_NON_PREGNANT_ASSESS hrpnpa ON b.beneficiaryId = hrpnpa.benId " + + "LEFT OUTER JOIN HRP_MICRO_BIRTH_PLAN hrpmbp ON b.beneficiaryId = hrpmbp.benId " + + "LEFT OUTER JOIN HRP_NON_PREGNANT_TRACK hrnpt ON b.beneficiaryId = hrnpt.benId " + + "LEFT OUTER JOIN HRP_PREGNANT_TRACK hrpt ON b.beneficiaryId = hrpt.benId " + + "LEFT OUTER JOIN DELIVERY_OUTCOME do ON b.beneficiaryId = do.benId " + + "LEFT OUTER JOIN INFANT_REG ir ON b.beneficiaryId = ir.motherBenId " + + "LEFT OUTER JOIN CHILD_REG cr ON b.beneficiaryId = cr.motherBenId " + + "WHERE b.isDraft = 0 GROUP BY b.beneficiaryId ORDER BY b.updatedDate DESC" + ) + + if (tableExists(db, "MALARIA_SCREENING") + && !columnExists(db, "MALARIA_SCREENING", "slideTestName") + ) { + db.execSQL( + "ALTER TABLE MALARIA_SCREENING ADD COLUMN slideTestName TEXT" + ) + } + } + } +// val MIGRATION_18_19 = object : Migration(18, 19) { +// override fun migrate(database: SupportSQLiteDatabase) { +// val columns = listOf( +// "isDeath INTEGER", +// "isDeathValue TEXT", +// "dateOfDeath TEXT", +// "timeOfDeath TEXT", +// "reasonOfDeath TEXT", +// "reasonOfDeathId INTEGER", +// "placeOfDeath TEXT", +// "placeOfDeathId INTEGER", +// "otherPlaceOfDeath TEXT" +// ) +// +// for (column in columns) { +// database.execSQL("ALTER TABLE BENEFICIARY ADD COLUMN $column") +// } +// +// +// +// // 🔹 Columns for PREGNANCY_ANC table +// val pregnancyAncColumns = listOf( +// "serialNo TEXT", +// "methodOfTermination TEXT", +// "methodOfTerminationId INTEGER DEFAULT 0 NOT NULL", +// "terminationDoneBy TEXT", +// "terminationDoneById INTEGER DEFAULT 0 NOT NULL", +// "isPaiucdId INTEGER DEFAULT 0 NOT NULL", +// "isPaiucd TEXT", +// "remarks TEXT", +// "abortionImg1 TEXT", +// "abortionImg2 TEXT", +// "placeOfDeath TEXT", +// "placeOfDeathId INTEGER DEFAULT 0 NOT NULL", +// "otherPlaceOfDeath TEXT" +// ) +// +// for (column in pregnancyAncColumns) { +// database.execSQL("ALTER TABLE PREGNANCY_ANC ADD COLUMN $column") +// } +// +// } +// } + + val MIGRATION_17_18 = object : Migration(17, 18) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS `ABHA_GENERATED_NEW` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + `beneficiaryID` INTEGER NOT NULL, + `beneficiaryRegID` INTEGER NOT NULL, + `benName` TEXT NOT NULL, + `createdBy` TEXT NOT NULL, + `message` TEXT NOT NULL, + `txnId` TEXT NOT NULL, + `benSurname` TEXT, + `healthId` TEXT NOT NULL, + `healthIdNumber` TEXT NOT NULL, + `abhaProfileJson` TEXT NOT NULL, + `isNewAbha` INTEGER NOT NULL, + `providerServiceMapId` INTEGER NOT NULL, + `syncState` INTEGER NOT NULL, + FOREIGN KEY(`beneficiaryID`) REFERENCES `BENEFICIARY`(`beneficiaryId`) ON UPDATE CASCADE ON DELETE CASCADE + ) + """.trimIndent() + ) + try { + database.execSQL( + """ + INSERT INTO ABHA_GENERATED_NEW ( + id, + beneficiaryID, + beneficiaryRegID, + benName, + createdBy, + message, + txnId, + benSurname, + healthId, + healthIdNumber, + abhaProfileJson, + isNewAbha, + providerServiceMapId, + syncState + ) + SELECT + id, + benId AS beneficiaryID, + hhId AS beneficiaryRegID, + benName, + '' AS createdBy, + '' AS message, + '' AS txnId, + benSurname, + healthId, + healthIdNumber, + '' AS abhaProfileJson, + isNewAbha, + 0, + 0 + FROM ABHA_GENERATED + """.trimIndent() + ) + } catch (e: Exception) { + Log.w( + "RoomMigration", + "Skipping data copy: ABHA_GENERATED table not found", + e + ) + } + + try { + database.execSQL("DROP TABLE IF EXISTS ABHA_GENERATED") + } catch (_: Exception) { + } + database.execSQL("ALTER TABLE ABHA_GENERATED_NEW RENAME TO ABHA_GENERATED") + database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_ABHA_GENERATED_beneficiaryID` ON `ABHA_GENERATED` (`beneficiaryID`)") + } + } + + val MIGRATION_16_18 = object : Migration(16, 18) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS `ABHA_GENERATED_NEW` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + `beneficiaryID` INTEGER NOT NULL, + `beneficiaryRegID` INTEGER NOT NULL, + `benName` TEXT NOT NULL, + `createdBy` TEXT NOT NULL, + `message` TEXT NOT NULL, + `txnId` TEXT NOT NULL, + `benSurname` TEXT, + `healthId` TEXT NOT NULL, + `healthIdNumber` TEXT NOT NULL, + `abhaProfileJson` TEXT NOT NULL, + `isNewAbha` INTEGER NOT NULL, + `providerServiceMapId` INTEGER NOT NULL, + `syncState` INTEGER NOT NULL, + FOREIGN KEY(`beneficiaryID`) REFERENCES `BENEFICIARY`(`beneficiaryId`) ON UPDATE CASCADE ON DELETE CASCADE + ) + """.trimIndent() + ) + try { + database.execSQL( + """ + INSERT INTO ABHA_GENERATED_NEW ( + id, + beneficiaryID, + beneficiaryRegID, + benName, + createdBy, + message, + txnId, + benSurname, + healthId, + healthIdNumber, + abhaProfileJson, + isNewAbha, + providerServiceMapId, + syncState + ) + SELECT + id, + benId AS beneficiaryID, + hhId AS beneficiaryRegID, + benName, + '' AS createdBy, + '' AS message, + '' AS txnId, + benSurname, + healthId, + healthIdNumber, + '' AS abhaProfileJson, + isNewAbha, + 0, + 0 + FROM ABHA_GENERATED + """.trimIndent() + ) + } catch (e: Exception) { + Log.w( + "RoomMigration", + "Skipping data copy: ABHA_GENERATED table not found", + e + ) + } + + try { + database.execSQL("DROP TABLE IF EXISTS ABHA_GENERATED") + } catch (_: Exception) { + } + + database.execSQL("ALTER TABLE ABHA_GENERATED_NEW RENAME TO ABHA_GENERATED") + + database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_ABHA_GENERATED_beneficiaryID` ON `ABHA_GENERATED` (`beneficiaryID`)") + } + } + + val MIGRATION_21_22 = object : Migration(21, 22) { + override fun migrate(database: SupportSQLiteDatabase) { + + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS PREGNANCY_ANC_NEW ( + id INTEGER NOT NULL PRIMARY KEY, + benId INTEGER NOT NULL, + visitNumber INTEGER NOT NULL, + isActive INTEGER NOT NULL, + ancDate INTEGER NOT NULL, + isAborted INTEGER NOT NULL, + abortionType TEXT, + abortionTypeId INTEGER NOT NULL, + abortionFacility TEXT, + abortionFacilityId INTEGER NOT NULL, + abortionDate INTEGER, + weight INTEGER, + bpSystolic INTEGER, + bpDiastolic INTEGER, + pulseRate TEXT, + hb REAL, + fundalHeight INTEGER, + urineAlbumin TEXT, + urineAlbuminId INTEGER NOT NULL, + randomBloodSugarTest TEXT, + randomBloodSugarTestId INTEGER NOT NULL, + numFolicAcidTabGiven INTEGER NOT NULL, + numIfaAcidTabGiven INTEGER NOT NULL, + anyHighRisk INTEGER, + highRisk TEXT, + highRiskId INTEGER NOT NULL, + otherHighRisk TEXT, + referralFacility TEXT, + referralFacilityId INTEGER NOT NULL, + hrpConfirmed INTEGER, + hrpConfirmedBy TEXT, + hrpConfirmedById INTEGER NOT NULL, + maternalDeath INTEGER, + maternalDeathProbableCause TEXT, + maternalDeathProbableCauseId INTEGER NOT NULL, + otherMaternalDeathProbableCause TEXT, + deathDate INTEGER, + pregnantWomanDelivered INTEGER, + processed TEXT, + createdBy TEXT NOT NULL, + createdDate INTEGER NOT NULL, + updatedBy TEXT NOT NULL, + updatedDate INTEGER NOT NULL, + syncState INTEGER NOT NULL, + frontFilePath TEXT, + backFilePath TEXT, + FOREIGN KEY(benId) REFERENCES BENEFICIARY(beneficiaryId) + ON UPDATE CASCADE ON DELETE CASCADE + ) + """.trimIndent() + ) + + database.execSQL("DROP VIEW IF EXISTS BEN_BASIC_CACHE") + if (tableExists(database, "PREGNANCY_ANC")) { + database.execSQL( + """ + INSERT INTO PREGNANCY_ANC_NEW ( + id, benId, visitNumber, isActive, ancDate, isAborted, + abortionType, abortionTypeId, abortionFacility, abortionFacilityId, abortionDate, + weight, bpSystolic, bpDiastolic, pulseRate, hb, fundalHeight, + urineAlbumin, urineAlbuminId, randomBloodSugarTest, randomBloodSugarTestId, + numFolicAcidTabGiven, numIfaAcidTabGiven, anyHighRisk, highRisk, highRiskId, + otherHighRisk, referralFacility, referralFacilityId, + hrpConfirmed, hrpConfirmedBy, hrpConfirmedById, + maternalDeath, maternalDeathProbableCause, maternalDeathProbableCauseId, + otherMaternalDeathProbableCause, deathDate, pregnantWomanDelivered, + processed, createdBy, createdDate, updatedBy, updatedDate, syncState, + frontFilePath, backFilePath + ) + SELECT + id, benId, visitNumber, isActive, ancDate, isAborted, + abortionType, abortionTypeId, abortionFacility, abortionFacilityId, abortionDate, + weight, bpSystolic, bpDiastolic, pulseRate, hb, fundalHeight, + urineAlbumin, urineAlbuminId, randomBloodSugarTest, randomBloodSugarTestId, + numFolicAcidTabGiven, numIfaAcidTabGiven, anyHighRisk, highRisk, highRiskId, + otherHighRisk, referralFacility, referralFacilityId, + hrpConfirmed, hrpConfirmedBy, hrpConfirmedById, + maternalDeath, maternalDeathProbableCause, maternalDeathProbableCauseId, + otherMaternalDeathProbableCause, deathDate, pregnantWomanDelivered, + processed, createdBy, createdDate, updatedBy, updatedDate, syncState, + NULL AS frontFilePath, + NULL AS backFilePath + FROM PREGNANCY_ANC + """.trimIndent() + ) + database.execSQL("DROP TABLE PREGNANCY_ANC") + } + database.execSQL("ALTER TABLE PREGNANCY_ANC_NEW RENAME TO PREGNANCY_ANC") + + database.execSQL("CREATE INDEX IF NOT EXISTS ind_mha ON PREGNANCY_ANC(benId)") + + if (tableExists(database, "DELIVERY_OUTCOME")) { + if (!columnExists(database, "DELIVERY_OUTCOME", "isDeath")) { + database.execSQL("ALTER TABLE DELIVERY_OUTCOME ADD COLUMN isDeath INTEGER DEFAULT 0") + } + if (!columnExists(database, "DELIVERY_OUTCOME", "isDeathValue")) { + database.execSQL("ALTER TABLE DELIVERY_OUTCOME ADD COLUMN isDeathValue TEXT") + } + if (!columnExists(database, "DELIVERY_OUTCOME", "dateOfDeath")) { + database.execSQL("ALTER TABLE DELIVERY_OUTCOME ADD COLUMN dateOfDeath TEXT") + } + if (!columnExists(database, "DELIVERY_OUTCOME", "placeOfDeath")) { + database.execSQL("ALTER TABLE DELIVERY_OUTCOME ADD COLUMN placeOfDeath TEXT") + } + if (!columnExists(database, "DELIVERY_OUTCOME", "placeOfDeathId")) { + database.execSQL("ALTER TABLE DELIVERY_OUTCOME ADD COLUMN placeOfDeathId INTEGER DEFAULT 0") + } + if (!columnExists(database, "DELIVERY_OUTCOME", "otherPlaceOfDeath")) { + database.execSQL("ALTER TABLE DELIVERY_OUTCOME ADD COLUMN otherPlaceOfDeath TEXT") + } + if (!columnExists(database, "DELIVERY_OUTCOME", "mcp1File")) { + database.execSQL("ALTER TABLE DELIVERY_OUTCOME ADD COLUMN mcp1File TEXT") + } + if (!columnExists(database, "DELIVERY_OUTCOME", "mcp2File")) { + database.execSQL("ALTER TABLE DELIVERY_OUTCOME ADD COLUMN mcp2File TEXT") + } + if (!columnExists(database, "DELIVERY_OUTCOME", "jsyFile")) { + database.execSQL("ALTER TABLE DELIVERY_OUTCOME ADD COLUMN jsyFile TEXT") + } + } + } + } + + + val MIGRATION_15_16 = object : Migration(15, 16) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("DROP VIEW IF EXISTS BEN_BASIC_CACHE") + + db.execSQL( + "CREATE VIEW `BEN_BASIC_CACHE` AS " + + "SELECT b.beneficiaryId as benId, b.householdId as hhId, b.regDate, b.firstName as benName, b.lastName as benSurname, b.gender, b.dob as dob, b.familyHeadRelationPosition as relToHeadId" + + ", b.contactNumber as mobileNo, b.fatherName, h.fam_familyHeadName as familyHeadName, b.gen_spouseName as spouseName, b.rchId, b.gen_lastMenstrualPeriod as lastMenstrualPeriod" + + ", b.isHrpStatus as hrpStatus, b.syncState, b.gen_reproductiveStatusId as reproductiveStatusId, b.isKid, b.immunizationStatus" + + ", b.loc_village_id as villageId, b.abha_healthIdNumber as abhaId" + + ", b.isNewAbha" + + ", IFNULL(cbac.benId IS NOT NULL, 0) as cbacFilled, cbac.syncState as cbacSyncState" + + ", IFNULL(cdr.benId IS NOT NULL, 0) as cdrFilled, cdr.syncState as cdrSyncState" + + ", IFNULL(mdsr.benId IS NOT NULL, 0) as mdsrFilled, mdsr.syncState as mdsrSyncState" + + ", IFNULL(pmsma.benId IS NOT NULL, 0) as pmsmaFilled, pmsma.syncState as pmsmaSyncState" + + ", IFNULL(hbnc.benId IS NOT NULL, 0) as hbncFilled" + + ", IFNULL(hbyc.benId IS NOT NULL, 0) as hbycFilled" + + ", IFNULL(pwr.benId IS NOT NULL, 0) as pwrFilled, pwr.syncState as pwrSyncState" + + ", IFNULL(pwa.pregnantWomanDelivered, 0) as isDelivered, IFNULL(pwa.hrpConfirmed, 0) as pwHrp" + + ", IFNULL(ecr.benId IS NOT NULL, 0) as ecrFilled" + + ", IFNULL(ect.benId IS NOT NULL, 0) as ectFilled" + + ", IFNULL((pwa.maternalDeath OR do.complication = 'DEATH' OR pnc.motherDeath), 0) as isMdsr" + + ", IFNULL(tbsn.benId IS NOT NULL, 0) as tbsnFilled, tbsn.syncState as tbsnSyncState" + + ", IFNULL(tbsp.benId IS NOT NULL, 0) as tbspFilled, tbsp.syncState as tbspSyncState" + + ", IFNULL(ir.motherBenId IS NOT NULL, 0) as irFilled, ir.syncState as irSyncState" + + ", IFNULL(cr.motherBenId IS NOT NULL, 0) as crFilled, cr.syncState as crSyncState" + + ", IFNULL(do.benId IS NOT NULL, 0) as doFilled, do.syncState as doSyncState" + + ", IFNULL((hrppa.benId IS NOT NULL AND hrppa.noOfDeliveries IS NOT NULL AND hrppa.timeLessThan18m IS NOT NULL AND hrppa.heightShort IS NOT NULL AND hrppa.age IS NOT NULL AND hrppa.rhNegative IS NOT NULL AND hrppa.homeDelivery IS NOT NULL AND hrppa.badObstetric IS NOT NULL AND hrppa.multiplePregnancy IS NOT NULL), 0) as hrppaFilled, hrppa.syncState as hrppaSyncState" + + ", IFNULL((hrpnpa.benId IS NOT NULL AND hrpnpa.noOfDeliveries IS NOT NULL AND hrpnpa.timeLessThan18m IS NOT NULL AND hrpnpa.heightShort IS NOT NULL AND hrpnpa.age IS NOT NULL AND hrpnpa.misCarriage IS NOT NULL AND hrpnpa.homeDelivery IS NOT NULL AND hrpnpa.medicalIssues IS NOT NULL AND hrpnpa.pastCSection IS NOT NULL), 0) as hrpnpaFilled, hrpnpa.syncState as hrpnpaSyncState" + + ", IFNULL(hrpmbp.benId IS NOT NULL, 0) as hrpmbpFilled, hrpmbp.syncState as hrpmbpSyncState" + + ", IFNULL(hrpt.benId IS NOT NULL, 0) as hrptFilled, IFNULL(((count(distinct hrpt.id) > 3) OR (((JulianDay('now')) - JulianDay(date(max(hrpt.visitDate)/1000,'unixepoch','localtime'))) < 1)), 0) as hrptrackingDone, hrpt.syncState as hrptSyncState" + + ", IFNULL(hrnpt.benId IS NOT NULL, 0) as hrnptFilled, IFNULL(((JulianDay('now') - JulianDay(date(max(hrnpt.visitDate)/1000,'unixepoch','localtime'))) < 1), 0) as hrnptrackingDone, hrnpt.syncState as hrnptSyncState " + + "FROM BENEFICIARY b " + + "JOIN HOUSEHOLD h ON b.householdId = h.householdId " + + "LEFT OUTER JOIN CBAC cbac ON b.beneficiaryId = cbac.benId " + + "LEFT OUTER JOIN CDR cdr ON b.beneficiaryId = cdr.benId " + + "LEFT OUTER JOIN MDSR mdsr ON b.beneficiaryId = mdsr.benId " + + "LEFT OUTER JOIN PMSMA pmsma ON b.beneficiaryId = pmsma.benId " + + "LEFT OUTER JOIN HBNC hbnc ON b.beneficiaryId = hbnc.benId " + + "LEFT OUTER JOIN HBYC hbyc ON b.beneficiaryId = hbyc.benId " + + "LEFT OUTER JOIN PREGNANCY_REGISTER pwr ON b.beneficiaryId = pwr.benId " + + "LEFT OUTER JOIN PREGNANCY_ANC pwa ON b.beneficiaryId = pwa.benId " + + "LEFT OUTER JOIN pnc_visit pnc ON b.beneficiaryId = pnc.benId " + + "LEFT OUTER JOIN ELIGIBLE_COUPLE_REG ecr ON b.beneficiaryId = ecr.benId " + + "LEFT OUTER JOIN ELIGIBLE_COUPLE_TRACKING ect ON (b.beneficiaryId = ect.benId AND CAST((strftime('%s','now') - ect.visitDate/1000)/60/60/24 AS INTEGER) < 30) " + + "LEFT OUTER JOIN TB_SCREENING tbsn ON b.beneficiaryId = tbsn.benId " + + "LEFT OUTER JOIN TB_SUSPECTED tbsp ON b.beneficiaryId = tbsp.benId " + + "LEFT OUTER JOIN HRP_PREGNANT_ASSESS hrppa ON b.beneficiaryId = hrppa.benId " + + "LEFT OUTER JOIN HRP_NON_PREGNANT_ASSESS hrpnpa ON b.beneficiaryId = hrpnpa.benId " + + "LEFT OUTER JOIN HRP_MICRO_BIRTH_PLAN hrpmbp ON b.beneficiaryId = hrpmbp.benId " + + "LEFT OUTER JOIN HRP_NON_PREGNANT_TRACK hrnpt ON b.beneficiaryId = hrnpt.benId " + + "LEFT OUTER JOIN HRP_PREGNANT_TRACK hrpt ON b.beneficiaryId = hrpt.benId " + + "LEFT OUTER JOIN DELIVERY_OUTCOME do ON b.beneficiaryId = do.benId " + + "LEFT OUTER JOIN INFANT_REG ir ON b.beneficiaryId = ir.motherBenId " + + "LEFT OUTER JOIN CHILD_REG cr ON b.beneficiaryId = cr.motherBenId " + + "WHERE b.isDraft = 0 GROUP BY b.beneficiaryId ORDER BY b.updatedDate DESC" + ) + } + } + + val MIGRATION_14_15 = Migration(14, 15, migrate = { + it.execSQL("ALTER TABLE BENEFICIARY ADD COLUMN isNewAbha INTEGER NOT NULL DEFAULT 0") + it.execSQL("DROP VIEW IF EXISTS BEN_BASIC_CACHE") + // it.execSQL("CREATE VIEW BEN_BASIC_CACHE AS " + + // "SELECT benId,hhId,regDate,benName,benSurname,gender,dob,relToHeadId,mobileNo,fatherName,familyHeadName,spouseName,rchId,hrpStatus,syncState,reproductiveStatusId, lastMenstrualPeriod,isKid,immunizationStatus,villageId,abhaId,isNewAbha,cbacFilled,cbacSyncState,cdrFilled,cdrSyncState,mdsrFilled,mdsrSyncState,pmsmaSyncState,pmsmaFilled,hbncFilled,hbycFilled,pwrFilled,pwrSyncState,doSyncState,irSyncState,crSyncState,ecrFilled,ectFilled,tbsnFilled,tbsnSyncState,tbspFilled,tbspSyncState,hrppaFilled,hrpnpaFilled,hrpmbpFilled,hrptFilled,hrptrackingDone,hrnptrackingDone,hrnptFilled,hrppaSyncState,hrpnpaSyncState,hrpmbpSyncState,hrptSyncState,hrnptSyncState,isDelivered,pwHrp,irFilled,isMdsr,crFilled,doFilled FROM BENEFICIARY"); + it.execSQL( + "CREATE VIEW BEN_BASIC_CACHE AS " + + "SELECT b.beneficiaryId as benId, b.householdId as hhId, b.regDate, " + + "b.firstName as benName, b.lastName as benSurname, b.gender, b.dob as dob, " + + "b.familyHeadRelationPosition as relToHeadId, b.contactNumber as mobileNo, " + + "b.fatherName, h.fam_familyHeadName as familyHeadName, b.gen_spouseName as spouseName, " + + "b.rchId, b.gen_lastMenstrualPeriod as lastMenstrualPeriod, b.isHrpStatus as hrpStatus, " + + "b.syncState, b.gen_reproductiveStatusId as reproductiveStatusId, b.isKid, b.immunizationStatus, " + + "b.loc_village_id as villageId, b.abha_healthIdNumber as abhaId, " + + "b.isNewAbha, " + // Added the new column here + "cbac.benId is not null as cbacFilled, cbac.syncState as cbacSyncState " + + "FROM BENEFICIARY b " + + "JOIN HOUSEHOLD h ON b.householdId = h.householdId " + + "LEFT OUTER JOIN CBAC cbac on b.beneficiaryId = cbac.benId " + + "WHERE b.isDraft = 0 GROUP BY b.beneficiaryId ORDER BY b.updatedDate DESC" + ) + }) val MIGRATION_13_14 = Migration(13, 14, migrate = { @@ -161,19 +2951,273 @@ abstract class InAppDb : RoomDatabase() { it.execSQL("alter table HRP_PREGNANT_TRACK add column fastingOgtt INTEGER") it.execSQL("alter table HRP_PREGNANT_TRACK add column after2hrsOgtt INTEGER") }) -// _db.execSQL("CREATE TABLE IF NOT EXISTS `HRP_PREGNANT_TRACK` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `benId` INTEGER NOT NULL, `visitDate` INTEGER, `rdPmsa` TEXT, `rdDengue` TEXT, `rdFilaria` TEXT, `severeAnemia` TEXT, `hemoglobinTest` TEXT, `ifaGiven` TEXT, `ifaQuantity` INTEGER, `pregInducedHypertension` TEXT, `systolic` INTEGER, `diastolic` INTEGER, `gestDiabetesMellitus` TEXT, `bloodGlucoseTest` TEXT, `fbg` INTEGER, `rbg` INTEGER, `ppbg` INTEGER, `fastingOgtt` INTEGER, `after2hrsOgtt` INTEGER, `hypothyrodism` TEXT, `polyhydromnios` TEXT, `oligohydromnios` TEXT, `antepartumHem` TEXT, `malPresentation` TEXT, `hivsyph` TEXT, `visit` TEXT, `syncState` INTEGER NOT NULL, FOREIGN KEY(`benId`) REFERENCES `BENEFICIARY`(`beneficiaryId`) ON UPDATE CASCADE ON DELETE CASCADE )"); + + val MIGRATION_18_19 = object : Migration(18, 19) { + override fun migrate(database: SupportSQLiteDatabase) { + + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS PROFILE_ACTIVITY_new ( + id INTEGER PRIMARY KEY NOT NULL, + name TEXT, + profileImage TEXT NOT NULL, + village TEXT NOT NULL, + employeeId INTEGER NOT NULL, + dob TEXT NOT NULL, + age INTEGER NOT NULL, + mobileNumber TEXT NOT NULL, + alternateMobileNumber TEXT NOT NULL, + fatherOrSpouseName TEXT NOT NULL, + dateOfJoining TEXT NOT NULL, + bankAccount TEXT NOT NULL, + ifsc TEXT NOT NULL, + populationCovered INTEGER NOT NULL, + choName TEXT NOT NULL, + choMobile TEXT NOT NULL, + awwName TEXT NOT NULL, + awwMobile TEXT NOT NULL, + anm1Name TEXT NOT NULL, + anm1Mobile TEXT NOT NULL, + anm2Name TEXT NOT NULL, + anm2Mobile TEXT NOT NULL, + abhaNumber TEXT NOT NULL, + ashaHouseholdRegistration TEXT NOT NULL, + ashaFamilyMember TEXT NOT NULL, + providerServiceMapID TEXT NOT NULL, + isFatherOrSpouse INTEGER NOT NULL DEFAULT 0, + supervisorName TEXT NOT NULL, + supervisorMobile TEXT NOT NULL + ) + """ + ) + + if (tableExists(database, "PROFILE_ACTIVITY")) { + database.execSQL( + """ + INSERT INTO PROFILE_ACTIVITY_new ( + id, name, profileImage, village, employeeId, dob, age, + mobileNumber, alternateMobileNumber, fatherOrSpouseName, + dateOfJoining, bankAccount, ifsc, populationCovered, + choName, choMobile, awwName, awwMobile, + anm1Name, anm1Mobile, anm2Name, anm2Mobile, + abhaNumber, ashaHouseholdRegistration, + ashaFamilyMember, providerServiceMapID, + isFatherOrSpouse, supervisorName, supervisorMobile + ) + SELECT + id, name, profileImage, village, employeeId, dob, age, + mobileNumber, alternateMobileNumber, fatherOrSpouseName, + dateOfJoining, bankAccount, ifsc, populationCovered, + choName, choMobile, awwName, awwMobile, + anm1Name, anm1Mobile, anm2Name, anm2Mobile, + abhaNumber, ashaHouseholdRegistration, + ashaFamilyMember, providerServiceMapID, + isFatherOrSpouse, supervisorName, supervisorMobile + FROM PROFILE_ACTIVITY + """ + ) + } + + database.execSQL("DROP TABLE IF EXISTS PROFILE_ACTIVITY") + database.execSQL("ALTER TABLE PROFILE_ACTIVITY_new RENAME TO PROFILE_ACTIVITY") + + + if (tableExists(database, "BENEFICIARY")) { + val beneficiaryColumns = listOf( + "isDeath INTEGER NOT NULL DEFAULT 'undefined'", + "isDeathValue TEXT", + "dateOfDeath TEXT", + "timeOfDeath TEXT", + "reasonOfDeath TEXT", + "reasonOfDeathId INTEGER NOT NULL DEFAULT 0", + "placeOfDeath TEXT", + "placeOfDeathId INTEGER NOT NULL DEFAULT 'undefined'", + "otherPlaceOfDeath TEXT", + "isConsent INTEGER NOT NULL DEFAULT 0", + "kid_isConsent INTEGER ", + "suspectedTb TEXT DEFAULT 'undefined'", + "kid_childName TEXT DEFAULT 'undefined'", + "loc_country_nameHindi TEXT DEFAULT 'undefined'", + "kid_birthOPV INTEGER DEFAULT 'undefined'", + "kid_opvDate TEXT DEFAULT 'undefined'", + "kid_conductedDelivery TEXT DEFAULT 'undefined'", + "kid_conductedDeliveryOther TEXT DEFAULT 'undefined'", + "kid_conductedDeliveryId INTEGER DEFAULT 'undefined'", + "suspectedHrp TEXT DEFAULT 'undefined'", + "familyHeadRelation TEXT DEFAULT 'undefined'", + "kid_opvGivenDueDate TEXT DEFAULT 'undefined'", + "kid_childMotherName TEXT DEFAULT 'undefined'", + "community TEXT DEFAULT 'undefined'", + "kid_birthPlace TEXT DEFAULT 'undefined'", + "loc_village_nameHindi TEXT DEFAULT 'undefined'", + "religionOthers TEXT DEFAULT 'undefined'", + "kid_typeOfSchool TEXT DEFAULT 'undefined'", + "hrpLastVisitDate TEXT DEFAULT 'undefined'", + "kid_typeOfSchoolId INTEGER DEFAULT 'undefined'", + "kid_complicationsId INTEGER DEFAULT 'undefined'", + "kid_motherPosition INTEGER DEFAULT 'undefined'", + "kid_feedingStarted TEXT DEFAULT 'undefined'", + "hrpIdentificationDate TEXT DEFAULT 'undefined'", + "kid_gestationalAge TEXT DEFAULT 'undefined'", + "loc_state_nameHindi TEXT DEFAULT 'undefined'", + "suspectedNcdDiseases TEXT DEFAULT 'undefined'", + "kid_bcdBatchNo TEXT DEFAULT 'undefined'", + "abha_isNewAbha INTEGER DEFAULT 'undefined'", + "kid_bcgDate TEXT DEFAULT 'undefined'", + "kid_bcgGivenDueDate TEXT DEFAULT 'undefined'", + "kid_hptBatchNo TEXT DEFAULT 'undefined'", + "loc_state_nameAssamese TEXT DEFAULT 'undefined'", + "gen_maritalStatus TEXT DEFAULT 'undefined'", + "loc_block_id INTEGER DEFAULT 'undefined'", + "syncState INTEGER DEFAULT 'undefined'", + "kid_birthPlaceId INTEGER DEFAULT 'undefined'", + "kid_birthDosageId INTEGER DEFAULT 'undefined'", + "abha_healthIdNumber TEXT DEFAULT 'undefined'", + "loc_country_name TEXT DEFAULT 'undefined'", + "kid_birthDefectsId INTEGER DEFAULT 'undefined'", + "kid_vitaminKGivenDueDate TEXT DEFAULT 'undefined'", + "createdDate INTEGER DEFAULT 'undefined'", + "kid_birthCertificateFileBackView TEXT DEFAULT 'undefined'", + "kid_birthCertificateFileFrontView TEXT DEFAULT 'undefined'", + "kid_hptGivenDueDate TEXT DEFAULT 'undefined'", + "kid_motherBenId INTEGER DEFAULT 'undefined'", + "kid_childRegisteredSchoolId INTEGER DEFAULT 'undefined'", + "kid_birthHepB INTEGER DEFAULT 'undefined'", + "kid_birthBCG INTEGER DEFAULT 'undefined'", + "kid_birthCertificateNumber TEXT DEFAULT 'undefined'", + "kid_birthDosage TEXT DEFAULT 'undefined'", + "kid_gestationalAgeId INTEGER DEFAULT 'undefined'", + "kid_facilityId INTEGER DEFAULT 'undefined'", + "kid_opvBatchNo TEXT DEFAULT 'undefined'", + "confirmedHrp TEXT DEFAULT 'undefined'", + "confirmedTb TEXT DEFAULT 'undefined'", + "kid_corticosteroidGivenMother TEXT DEFAULT 'undefined'", + "kid_birthDefects TEXT DEFAULT 'undefined'", + "kid_deliveryTypeOther TEXT DEFAULT 'undefined'", + "kid_deliveryTypeId INTEGER DEFAULT 'undefined'", + "kid_term TEXT DEFAULT 'undefined'", + "kid_facilityName TEXT DEFAULT 'undefined'", + "kid_hptDate TEXT DEFAULT 'undefined'", + "kid_vitaminKBatchNo TEXT DEFAULT 'undefined'", + "confirmedNcdDiseases TEXT DEFAULT 'undefined'", + "processed TEXT DEFAULT 'undefined'", + "kid_childRegisteredAWC TEXT DEFAULT 'undefined'", + "kid_facilityOther TEXT DEFAULT 'undefined'", + "kid_deliveryType TEXT DEFAULT 'undefined'", + "kid_heightAtBirth REAL DEFAULT 'undefined'", + "kid_weightAtBirth REAL DEFAULT 'undefined'", + "kid_placeName TEXT DEFAULT 'undefined'", + "tempMobileNoOfRelationId INTEGER NOT NULL DEFAULT 'undefined'" + ) + + for (column in beneficiaryColumns) { + val columnName = column.split(" ")[0] + if (!columnExists(database, "BENEFICIARY", columnName)) { + database.execSQL("ALTER TABLE BENEFICIARY ADD COLUMN $column") + } + } + } + + if (tableExists(database, "PREGNANCY_ANC")) { + val pregnancyAncColumns = listOf( + "serialNo TEXT", + "methodOfTermination TEXT", + "methodOfTerminationId INTEGER NOT NULL DEFAULT 0", + "terminationDoneBy TEXT", + "terminationDoneById INTEGER NOT NULL DEFAULT 0", + "isPaiucdId INTEGER NOT NULL DEFAULT 0", + "isPaiucd TEXT", + "remarks TEXT", + "abortionImg1 TEXT", + "abortionImg2 TEXT", + "placeOfDeath TEXT", + "placeOfDeathId INTEGER NOT NULL DEFAULT 0", + "otherPlaceOfDeath TEXT" + ) + + for (column in pregnancyAncColumns) { + val columnName = column.split(" ")[0] + if (!columnExists(database, "PREGNANCY_ANC", columnName)) { + database.execSQL("ALTER TABLE PREGNANCY_ANC ADD COLUMN $column") + } + } + } + } + } + + synchronized(this) { var instance = INSTANCE if (instance == null) { - instance = Room.databaseBuilder( + val isDebug = appContext.applicationInfo.flags and android.content.pm.ApplicationInfo.FLAG_DEBUGGABLE != 0 + + val builder = Room.databaseBuilder( appContext, InAppDb::class.java, "Sakhi-2.0-In-app-database" ) - .addMigrations( - MIGRATION_13_14 + + if (!isDebug) { + val passphrase = DatabaseKeyManager.getDatabasePassphrase(appContext) + + RoomDbEncryptionHelper.encryptIfNeeded( + context = appContext, + dbName = "Sakhi-2.0-In-app-database", + passphrase = passphrase ) - .build() + + val factory = SupportOpenHelperFactory(String(passphrase).toByteArray(Charsets.UTF_8)) + builder.openHelperFactory(factory) + } + + instance = builder.addMigrations( + MIGRATION_13_14, + MIGRATION_14_15, + MIGRATION_15_16, + MIGRATION_16_18, + MIGRATION_17_18, + MIGRATION_18_19, + MIGRATION_19_20, + MIGRATION_20_21, + MIGRATION_21_22, + MIGRATION_22_23, + MIGRATION_23_24, + MIGRATION_24_25, + MIGRATION_25_26, + MIGRATION_26_27, + MIGRATION_27_28, + MIGRATION_28_29, + MIGRATION_29_30, + MIGRATION_30_31, + MIGRATION_31_32, + MIGRATION_32_33, + MIGRATION_33_34, + MIGRATION_34_35, + MIGRATION_35_36, + MIGRATION_36_37, + MIGRATION_37_38, + MIGRATION_38_39, + MIGRATION_39_40, + MIGRATION_40_41, + MIGRATION_41_42, + MIGRATION_42_43, + MIGRATION_43_44, + MIGRATION_44_45, + MIGRATION_45_46, + MIGRATION_46_47, + MIGRATION_47_48, + MIGRATION_48_49, + MIGRATION_49_50, + MIGRATION_50_51, + MIGRATION_51_52, + MIGRATION_52_53, + MIGRATION_53_54, + MIGRATION_54_55, + MIGRATION_55_56, + MIGRATION_56_57 + + + ).build() INSTANCE = instance } diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/NcdReferalDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/NcdReferalDao.kt new file mode 100644 index 000000000..ef47b0748 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/NcdReferalDao.kt @@ -0,0 +1,25 @@ +package org.piramalswasthya.sakhi.database.room + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Update +import org.piramalswasthya.sakhi.model.ReferalCache +@Dao +interface NcdReferalDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun upsert(vararg referalCache: ReferalCache) + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertAll(referalCache: List) + @Update + suspend fun update(vararg referalCache: ReferalCache) + + @Query("SELECT * FROM NCD_REFER WHERE benId = :benId LIMIT 1") + suspend fun getReferalFromBenId(benId: Long): ReferalCache? + + @Query("SELECT * FROM NCD_REFER WHERE syncState = 0") + suspend fun getAllUnprocessedReferals(): List + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/ABHAGenratedDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/ABHAGenratedDao.kt new file mode 100644 index 000000000..499a0e11c --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/ABHAGenratedDao.kt @@ -0,0 +1,28 @@ +package org.piramalswasthya.sakhi.database.room.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Transaction +import androidx.room.Update +import org.piramalswasthya.sakhi.model.ABHAModel +import org.piramalswasthya.sakhi.model.BenWithABHAGeneratedCache + +@Dao +interface ABHAGenratedDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun saveABHA(abhaModel: ABHAModel) + @Update + suspend fun updateAbha(abhaModel: ABHAModel) + + @Query("DELETE FROM ABHA_GENERATED WHERE beneficiaryID = :benId") + suspend fun deleteAbhaByBenId(benId: Long) + + @Query("SELECT * FROM ABHA_GENERATED") + fun getAllAbha(): List + + @Transaction + @Query("SELECT * FROM BEN_BASIC_CACHE WHERE benId = :benId") + suspend fun getBenWithAbha(benId: Long): BenWithABHAGeneratedCache? +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/AdolescentHealthDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/AdolescentHealthDao.kt new file mode 100644 index 000000000..ef490e19e --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/AdolescentHealthDao.kt @@ -0,0 +1,26 @@ +package org.piramalswasthya.sakhi.database.room.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.model.AdolescentHealthCache + +@Dao +interface AdolescentHealthDao { + @Query("SELECT * FROM ADOLESCENT_HEALTH_FORM_DATA WHERE benId =:benId limit 1") + suspend fun getAdolescentHealth(benId: Long): AdolescentHealthCache? + + @Query("SELECT * FROM Adolescent_Health_Form_Data WHERE benId =:benId and (visitDate = :visitDate or visitDate = :visitDateGMT) limit 1") + suspend fun getAdolescentHealth(benId: Long, visitDate: Long, visitDateGMT: Long): AdolescentHealthCache? + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun saveAdolescentHealth(tbScreeningCache: AdolescentHealthCache) + + @Query("SELECT * FROM Adolescent_Health_Form_Data WHERE syncState = :syncState") + suspend fun getAdolescentHealth(syncState: SyncState): List + + @Query("UPDATE Adolescent_Health_Form_Data SET syncState = 0 WHERE syncState = 1") + suspend fun resetSyncingToUnsynced() +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/AesDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/AesDao.kt new file mode 100644 index 000000000..d42f607d6 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/AesDao.kt @@ -0,0 +1,26 @@ +package org.piramalswasthya.sakhi.database.room.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.model.AESScreeningCache +import org.piramalswasthya.sakhi.model.MalariaScreeningCache + +@Dao +interface AesDao { + @Query("SELECT * FROM AES_SCREENING WHERE benId =:benId limit 1") + suspend fun getAESScreening(benId: Long): AESScreeningCache? + + @Query("SELECT * FROM AES_SCREENING WHERE benId =:benId and (visitDate = :visitDate or visitDate = :visitDateGMT) limit 1") + suspend fun getAESScreening(benId: Long, visitDate: Long, visitDateGMT: Long): AESScreeningCache? + + @Query("SELECT * FROM AES_SCREENING WHERE syncState = :syncState") + suspend fun getAESScreening(syncState: SyncState): List + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun saveAESScreening(malariaScreeningCache: AESScreeningCache) + + @Query("UPDATE AES_SCREENING SET syncState = 0 WHERE syncState = 1") + suspend fun resetSyncingToUnsynced() +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/BenDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/BenDao.kt index 5f27cdafa..221b25ee7 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/BenDao.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/BenDao.kt @@ -1,6 +1,7 @@ package org.piramalswasthya.sakhi.database.room.dao import androidx.lifecycle.LiveData +import androidx.paging.PagingSource import androidx.room.* import kotlinx.coroutines.flow.Flow import org.piramalswasthya.sakhi.database.room.SyncState @@ -16,37 +17,496 @@ interface BenDao { @Update suspend fun updateBen(ben: BenRegCache) + @Query("UPDATE BENEFICIARY SET syncState = :unsynced ,processed = :proccess , serverUpdatedStatus =:updateStatus WHERE householdId = :householdId") + suspend fun updateBenToSync( + householdId: Long, + unsynced: SyncState, + proccess: String, + updateStatus: Int + ) + + @Query("UPDATE BENEFICIARY SET isSpouseAdded = 1 , syncState = :unsynced ,processed = :proccess , serverUpdatedStatus =:updateStatus WHERE householdId = :householdId AND familyHeadRelationPosition = 19") + suspend fun updateHofSpouseAdded( + householdId: Long, + unsynced: SyncState, + proccess: String, + updateStatus: Int + ) + + @Query( + "UPDATE BENEFICIARY " + + "SET isChildrenAdded = 1 , syncState = :unsynced , processed = :proccess , serverUpdatedStatus =:updateStatus WHERE householdId = :householdId AND beneficiaryId = :benId" + ) + suspend fun updateBeneficiaryChildrenAdded( + householdId: Long, + benId: Long, + unsynced: SyncState, + proccess: String, + updateStatus: Int + ) + + @Query( + "UPDATE BENEFICIARY " + + "SET fatherName = :benName , syncState = :unsynced , processed = :proccess , serverUpdatedStatus =:updateStatus WHERE householdId = :householdId AND fatherName = :parentName AND (familyHeadRelationPosition = 9 OR familyHeadRelationPosition = 10)" + ) + suspend fun updateFatherInChildren( + benName: String, + householdId: Long, + parentName: String, + unsynced: SyncState, + proccess: String, + updateStatus: Int + ) + + @Query( + "UPDATE BENEFICIARY " + + "SET motherName = :benName , syncState = :unsynced , processed = :proccess , serverUpdatedStatus =:updateStatus WHERE householdId = :householdId AND motherName = :parentName AND (familyHeadRelationPosition = 9 OR familyHeadRelationPosition = 10)" + ) + suspend fun updateMotherInChildren( + benName: String, + householdId: Long, + parentName: String, + unsynced: SyncState, + proccess: String, + updateStatus: Int + ) + + @Query( + "UPDATE BENEFICIARY " + + "SET gen_spouseName = :benName , syncState = :unsynced , processed = :proccess , serverUpdatedStatus =:updateStatus WHERE gen_maritalStatusId = 2 AND householdId = :householdId AND gen_spouseName = :spouseName AND (familyHeadRelationPosition = 5 OR familyHeadRelationPosition = 6)" + ) + suspend fun updateSpouseOfHoF( + benName: String, + householdId: Long, + spouseName: String, + unsynced: SyncState, + proccess: String, + updateStatus: Int + ) + + @Query( + "UPDATE BENEFICIARY " + + "SET gen_marriageDate = :marriageDate , gen_ageAtMarriage = :ageAtMarriage, syncState = :unsynced , processed = :proccess , serverUpdatedStatus =:updateStatus WHERE householdId = :householdId AND gen_spouseName = :spouseName" + ) + suspend fun updateMarriageAgeOfWife( + marriageDate: Long, + ageAtMarriage: Int, + householdId: Long, + spouseName: String, + unsynced: SyncState, + proccess: String, + updateStatus: Int + ) + + @Query( + "UPDATE BENEFICIARY " + + "SET gen_marriageDate = :marriageDate , gen_ageAtMarriage = :ageAtMarriage, syncState = :unsynced , processed = :proccess , serverUpdatedStatus =:updateStatus WHERE householdId = :householdId AND gen_spouseName = :spouseName" + ) + suspend fun updateMarriageAgeOfHusband( + marriageDate: Long, + ageAtMarriage: Int, + householdId: Long, + spouseName: String, + unsynced: SyncState, + proccess: String, + updateStatus: Int + ) + + @Query( + "UPDATE BENEFICIARY " + + "SET gen_spouseName = :benName , syncState = :unsynced , processed = :proccess , serverUpdatedStatus =:updateStatus WHERE gen_maritalStatusId = 2 AND householdId = :householdId AND gen_spouseName = :spouseName" + ) + suspend fun updateSpouse( + benName: String, + householdId: Long, + spouseName: String, + unsynced: SyncState, + proccess: String, + updateStatus: Int + ) + + @Query( + "UPDATE BENEFICIARY " + + "SET fatherName = :benName , syncState = :unsynced , processed = :proccess , serverUpdatedStatus =:updateStatus WHERE householdId = :householdId AND fatherName = :parentName" + ) + suspend fun updateFather( + benName: String, + householdId: Long, + parentName: String, + unsynced: SyncState, + proccess: String, + updateStatus: Int + ) + + @Query( + "UPDATE BENEFICIARY " + + "SET motherName = :benName , syncState = :unsynced , processed = :proccess , serverUpdatedStatus =:updateStatus WHERE householdId = :householdId AND motherName = :parentName" + ) + suspend fun updateMother( + benName: String, + householdId: Long, + parentName: String, + unsynced: SyncState, + proccess: String, + updateStatus: Int + ) + + @Query( + "UPDATE BENEFICIARY " + + "SET firstName = :babyName , syncState = :unsynced , processed = :proccess , serverUpdatedStatus =:updateStatus WHERE householdId = :householdId AND motherName = :parentName AND age < 1" + ) + suspend fun updateBabyName( + babyName: String, + householdId: Long, + parentName: String, + unsynced: SyncState, + proccess: String, + updateStatus: Int + ) + + @Query( + "UPDATE BENEFICIARY " + + "SET lastName = :lastName , syncState = :unsynced , processed = :proccess , serverUpdatedStatus =:updateStatus WHERE gen_maritalStatusId = 2 AND householdId = :householdId AND gen_spouseName = :spouseName" + ) + suspend fun updateSpouseLastName( + lastName: String, + householdId: Long, + spouseName: String, + unsynced: SyncState, + proccess: String, + updateStatus: Int + ) + + @Query( + "UPDATE BENEFICIARY " + + "SET lastName = :lastName , syncState = :unsynced , processed = :proccess , serverUpdatedStatus =:updateStatus WHERE householdId = :householdId AND fatherName = :parentName" + ) + suspend fun updateChildrenLastName( + lastName: String, + householdId: Long, + parentName: String, + unsynced: SyncState, + proccess: String, + updateStatus: Int + ) + + @Query("UPDATE BENEFICIARY SET isSpouseAdded = 1 , syncState = :unsynced , processed = :proccess , serverUpdatedStatus =:updateStatus WHERE householdId = :householdId AND beneficiaryId = :benId") + suspend fun updateBeneficiarySpouseAdded(householdId: Long,benId: Long, unsynced: SyncState, proccess: String, + updateStatus: Int) + @Query("SELECT * FROM BENEFICIARY WHERE isDraft = 1 and householdId =:hhId LIMIT 1") suspend fun getDraftBenKidForHousehold(hhId: Long): BenRegCache? - @Query("SELECT * FROM BEN_BASIC_CACHE where villageId = :selectedVillage") + @Query("SELECT * FROM BEN_BASIC_CACHE where villageId = :selectedVillage AND isDeactivate = 0") fun getAllBen(selectedVillage: Int): Flow> - @Query("SELECT * FROM BEN_BASIC_CACHE where villageId = :selectedVillage and gender = :gender") + @Query(""" + SELECT * FROM BEN_BASIC_CACHE + WHERE villageId = :selectedVillage + AND isDeactivate = 0 + AND (:source = 0 + OR (:source = 1 AND abhaId IS NOT NULL) + OR (:source = 2 AND rchId IS NOT NULL AND rchId != '') + OR (:source = 3 AND CAST((strftime('%s','now') - dob/1000)/60/60/24/365 AS INTEGER) >= 30 AND isDeath = 0) + OR (:source = 4 AND gender = 'Female' AND isDeath = 0 + AND CAST((strftime('%s','now') - dob/1000)/60/60/24/365 AS INTEGER) BETWEEN 20 AND 49 + AND (reproductiveStatusId = 1 OR reproductiveStatusId = 2)) + ) + AND (:filterType = 0 + OR (:filterType = 1 AND abhaId IS NOT NULL) + OR (:filterType = 2 AND abhaId IS NULL) + OR (:filterType = 3 AND CAST((strftime('%s','now') - dob/1000)/60/60/24/365 AS INTEGER) >= 30 AND isDeath = 0) + OR (:filterType = 4 AND gender = 'Female' AND isDeath = 0 + AND CAST((strftime('%s','now') - dob/1000)/60/60/24/365 AS INTEGER) BETWEEN 20 AND 49 + AND (reproductiveStatusId = 1 OR reproductiveStatusId = 2)) + ) + AND (:query = '' OR + benName LIKE '%' || :query || '%' + OR benSurname LIKE '%' || :query || '%' + OR CAST(mobileNo AS TEXT) LIKE '%' || REPLACE(:query, ' ', '') || '%' + OR REPLACE(IFNULL(abhaId, ''), '-', '') LIKE '%' || REPLACE(:query, ' ', '') || '%' + OR IFNULL(familyHeadName, '') LIKE '%' || :query || '%' + OR IFNULL(spouseName, '') LIKE '%' || :query || '%' + OR IFNULL(fatherName, '') LIKE '%' || :query || '%' + OR CAST(benId AS TEXT) LIKE '%' || REPLACE(:query, ' ', '') || '%' + OR CAST(hhId AS TEXT) LIKE '%' || :query || '%' + OR IFNULL(rchId, '') LIKE '%' || REPLACE(:query, ' ', '') || '%' + ) + """) + fun searchBen(selectedVillage: Int, source: Int, filterType: Int, query: String): Flow> + + @Query(""" + SELECT * FROM BEN_BASIC_CACHE + WHERE villageId = :selectedVillage + AND isDeactivate = 0 + AND (:source = 0 + OR (:source = 1 AND abhaId IS NOT NULL) + OR (:source = 2 AND rchId IS NOT NULL AND rchId != '') + OR (:source = 3 AND CAST((strftime('%s','now') - dob/1000)/60/60/24/365 AS INTEGER) >= 30 AND isDeath = 0) + OR (:source = 4 AND gender = 'Female' AND isDeath = 0 + AND CAST((strftime('%s','now') - dob/1000)/60/60/24/365 AS INTEGER) BETWEEN 20 AND 49 + AND (reproductiveStatusId = 1 OR reproductiveStatusId = 2)) + ) + AND (:filterType = 0 + OR (:filterType = 1 AND abhaId IS NOT NULL) + OR (:filterType = 2 AND abhaId IS NULL) + OR (:filterType = 3 AND CAST((strftime('%s','now') - dob/1000)/60/60/24/365 AS INTEGER) >= 30 AND isDeath = 0) + OR (:filterType = 4 AND gender = 'Female' AND isDeath = 0 + AND CAST((strftime('%s','now') - dob/1000)/60/60/24/365 AS INTEGER) BETWEEN 20 AND 49 + AND (reproductiveStatusId = 1 OR reproductiveStatusId = 2)) + ) + AND (:query = '' OR + benName LIKE '%' || :query || '%' + OR benSurname LIKE '%' || :query || '%' + OR CAST(mobileNo AS TEXT) LIKE '%' || REPLACE(:query, ' ', '') || '%' + OR REPLACE(IFNULL(abhaId, ''), '-', '') LIKE '%' || REPLACE(:query, ' ', '') || '%' + OR IFNULL(familyHeadName, '') LIKE '%' || :query || '%' + OR IFNULL(spouseName, '') LIKE '%' || :query || '%' + OR IFNULL(fatherName, '') LIKE '%' || :query || '%' + OR CAST(benId AS TEXT) LIKE '%' || REPLACE(:query, ' ', '') || '%' + OR CAST(hhId AS TEXT) LIKE '%' || :query || '%' + OR IFNULL(rchId, '') LIKE '%' || REPLACE(:query, ' ', '') || '%' + ) + ORDER BY CASE + WHEN isDeath = 0 THEN 0 + WHEN isDeath = 1 THEN 1 + ELSE 2 + END + """) + fun searchBenPaged(selectedVillage: Int, source: Int, filterType: Int, query: String): PagingSource + + @Query(""" + SELECT * FROM BEN_BASIC_CACHE + WHERE villageId = :selectedVillage + AND isDeactivate = 0 + AND (:source = 0 + OR (:source = 1 AND abhaId IS NOT NULL) + OR (:source = 2 AND rchId IS NOT NULL AND rchId != '') + OR (:source = 3 AND CAST((strftime('%s','now') - dob/1000)/60/60/24/365 AS INTEGER) >= 30 AND isDeath = 0) + OR (:source = 4 AND gender = 'Female' AND isDeath = 0 + AND CAST((strftime('%s','now') - dob/1000)/60/60/24/365 AS INTEGER) BETWEEN 20 AND 49 + AND (reproductiveStatusId = 1 OR reproductiveStatusId = 2)) + ) + AND (:filterType = 0 + OR (:filterType = 1 AND abhaId IS NOT NULL) + OR (:filterType = 2 AND abhaId IS NULL) + OR (:filterType = 3 AND CAST((strftime('%s','now') - dob/1000)/60/60/24/365 AS INTEGER) >= 30 AND isDeath = 0) + OR (:filterType = 4 AND gender = 'Female' AND isDeath = 0 + AND CAST((strftime('%s','now') - dob/1000)/60/60/24/365 AS INTEGER) BETWEEN 20 AND 49 + AND (reproductiveStatusId = 1 OR reproductiveStatusId = 2)) + ) + AND (:query = '' OR + benName LIKE '%' || :query || '%' + OR benSurname LIKE '%' || :query || '%' + OR CAST(mobileNo AS TEXT) LIKE '%' || REPLACE(:query, ' ', '') || '%' + OR REPLACE(IFNULL(abhaId, ''), '-', '') LIKE '%' || REPLACE(:query, ' ', '') || '%' + OR IFNULL(familyHeadName, '') LIKE '%' || :query || '%' + OR IFNULL(spouseName, '') LIKE '%' || :query || '%' + OR IFNULL(fatherName, '') LIKE '%' || :query || '%' + OR CAST(benId AS TEXT) LIKE '%' || REPLACE(:query, ' ', '') || '%' + OR CAST(hhId AS TEXT) LIKE '%' || :query || '%' + OR IFNULL(rchId, '') LIKE '%' || REPLACE(:query, ' ', '') || '%' + ) + ORDER BY CASE + WHEN isDeath = 0 AND isDeactivate = 0 THEN 0 + WHEN isDeath = 1 AND isDeactivate = 0 THEN 1 + WHEN isDeactivate = 1 THEN 2 + ELSE 4 + END ASC + """) + suspend fun searchBenOnce(selectedVillage: Int, source: Int, filterType: Int, query: String): List + + @Query("SELECT * FROM BEN_BASIC_CACHE where villageId = :selectedVillage AND abhaId IS NOT NULL AND isDeactivate = 0") + fun getAllBenWithAbha(selectedVillage: Int): Flow> + + @Query("SELECT * FROM BEN_BASIC_CACHE where villageId = :selectedVillage AND abhaId IS NULL and isDeactivate=0") + fun getAllBenWithoutAbha(selectedVillage: Int): Flow> + + @Query("SELECT * FROM BEN_BASIC_CACHE where villageId = :selectedVillage AND rchId IS NOT NULL AND rchId != '' AND isDeactivate = 0") + fun getAllBenWithRch(selectedVillage: Int): Flow> + + @Query("SELECT * FROM BEN_BASIC_CACHE where villageId = :selectedVillage AND isDeactivate = 0 AND CAST((strftime('%s','now') - dob/1000)/60/60/24/365 AS INTEGER) >= 30 AND isDeath = 0") + fun getAllBenAboveThirty(selectedVillage: Int): Flow> + + @Query("SELECT * FROM BEN_BASIC_CACHE WHERE villageId = :selectedVillage AND isDeactivate=0 AND gender = 'Female' AND isDeath = 0 AND CAST((strftime('%s','now') - dob/1000)/60/60/24/365 AS INTEGER) BETWEEN 20 AND 49 AND (reproductiveStatusId = 1 OR reproductiveStatusId = 2)") + fun getAllBenWARA(selectedVillage: Int): Flow> + + @Query("SELECT * FROM BEN_BASIC_CACHE where villageId = :selectedVillage and gender = :gender and isDeactivate=0") fun getAllBenGender(selectedVillage: Int, gender: String): Flow> - @Query("SELECT COUNT(*) FROM BEN_BASIC_CACHE where villageId = :selectedVillage and gender = :gender") + @Query("SELECT COUNT(*) FROM BEN_BASIC_CACHE where villageId = :selectedVillage and gender = :gender and isDeactivate=0") fun getAllBenGenderCount(selectedVillage: Int, gender: String): Flow @Transaction - @Query("SELECT * FROM BEN_BASIC_CACHE where villageId = :selectedVillage") + @Query("SELECT * FROM BEN_BASIC_CACHE where villageId = :selectedVillage and isDeactivate=0") fun getAllTbScreeningBen(selectedVillage: Int): Flow> - @Query("SELECT COUNT(*) FROM BEN_BASIC_CACHE where villageId = :selectedVillage") + @Transaction + @Query("SELECT * FROM BEN_BASIC_CACHE where villageId = :selectedVillage and hhId = :hhId and isDeactivate=0") + fun getAllMalariaScreeningBen(selectedVillage: Int,hhId: Long): Flow> + + @Transaction + @Query("SELECT * FROM BEN_BASIC_CACHE where villageId = :selectedVillage and hhId = :hhId and isDeactivate=0") + fun getAllAESScreeningBen(selectedVillage: Int,hhId: Long): Flow> + + @Transaction + @Query("SELECT * FROM IRS_ROUND where householdId = :hhId") + fun getAllIRSRoundBen(hhId: Long): Flow> + + @Transaction + @Query("SELECT * FROM IRS_ROUND WHERE householdId = :hhId ORDER BY rounds DESC LIMIT 1") + fun getLastIRSRoundBen(hhId: Long): Flow + @Transaction + @Query("SELECT * FROM BEN_BASIC_CACHE where villageId = :selectedVillage and hhId = :hhId and isDeactivate=0") + fun getAllKALAZARScreeningBen(selectedVillage: Int,hhId: Long): Flow> + + @Transaction + @Query("SELECT * FROM BEN_BASIC_CACHE where villageId = :selectedVillage and hhId = :hhId and isDeactivate=0") + fun getAllLeprosyScreeningBen(selectedVillage: Int,hhId: Long): Flow> + + + @Transaction + @Query(""" + SELECT b.*, l.leprosySymptomsPosition + FROM BEN_BASIC_CACHE b + INNER JOIN LEPROSY_SCREENING l ON b.benId = l.benId + WHERE b.villageId = :selectedVillage + AND b.isDeactivate=0 + AND l.leprosySymptomsPosition = :symptomsPosition + AND l.isConfirmed = 0 +""") + fun getLeprosyScreeningBenBySymptoms(selectedVillage: Int, symptomsPosition: Int): Flow> + + @Query(""" + SELECT COUNT(*) + FROM BEN_BASIC_CACHE b + INNER JOIN LEPROSY_SCREENING l ON b.benId = l.benId + WHERE b.villageId = :selectedVillage + AND b.isDeactivate=0 + AND l.leprosySymptomsPosition = :symptomsPosition + AND l.isConfirmed = 0 +""") + fun getLeprosyScreeningBenCountBySymptoms(selectedVillage: Int, symptomsPosition: Int): Flow + + @Transaction + @Query(""" + SELECT b.*, l.isConfirmed + FROM BEN_BASIC_CACHE b + INNER JOIN LEPROSY_SCREENING l ON b.benId = l.benId + WHERE b.villageId = :selectedVillage + AND b.isDeactivate=0 + AND l.isConfirmed = 1 +""") + fun getConfirmedLeprosyCases( + selectedVillage: Int + ): Flow> + + @Query(""" + SELECT COUNT(*) + FROM BEN_BASIC_CACHE b + INNER JOIN LEPROSY_SCREENING l ON b.benId = l.benId + WHERE b.villageId = :selectedVillage + AND isDeactivate=0 + AND l.isConfirmed = 1 +""") + fun getConfirmedLeprosyCaseCount( + selectedVillage: Int + ): Flow + + @Transaction + @Query("SELECT * FROM BEN_BASIC_CACHE WHERE benId = :benId and isDeactivate=0") + suspend fun getBenWithLeprosyScreeningAndFollowUps(benId: Long): BenWithLeprosyScreeningCache? + @Transaction + @Query("SELECT * FROM BEN_BASIC_CACHE where villageId = :selectedVillage and hhId = :hhId and isDeactivate=0") + fun getAllFilariaScreeningBen(selectedVillage: Int,hhId: Long): Flow> + + + @Query("SELECT COUNT(*) FROM BEN_BASIC_CACHE where villageId = :selectedVillage and isDeactivate=0") fun getAllBenCount(selectedVillage: Int): Flow + @Query("SELECT COUNT(*) FROM BEN_BASIC_CACHE where villageId = :selectedVillage AND isDeactivate=0 AND abhaId IS NOT NULL") + fun getAllBenWithAbhaCount(selectedVillage: Int): Flow + + @Query("SELECT COUNT(*) FROM BEN_BASIC_CACHE where villageId = :selectedVillage AND isDeactivate=0 AND abhaId IS NOT NULL AND isNewAbha = 0") + fun getAllBenWithOldAbhaCount(selectedVillage: Int): Flow + + @Query("SELECT COUNT(*) FROM BEN_BASIC_CACHE where villageId = :selectedVillage AND isDeactivate=0 AND abhaId IS NOT NULL AND isNewAbha = 1") + fun getAllBenWithNewAbhaCount(selectedVillage: Int): Flow + + @Query("SELECT COUNT(*) FROM BEN_BASIC_CACHE where villageId = :selectedVillage AND isDeactivate=0 AND rchId IS NOT NULL AND rchId != ''") + fun getAllBenWithRchCount(selectedVillage: Int): Flow + + @Query(""" + SELECT parent.beneficiaryId as benId, COUNT(child.beneficiaryId) as childCount + FROM BENEFICIARY parent + LEFT JOIN BENEFICIARY child + ON child.householdId = parent.householdId + AND child.beneficiaryId != parent.beneficiaryId + AND (parent.firstName IS NULL OR parent.firstName = '' OR child.motherName LIKE parent.firstName || '%') + WHERE parent.isDraft = 0 + AND parent.loc_village_id = :selectedVillage + AND parent.isDeactivate = 0 + GROUP BY parent.beneficiaryId + """) + fun getChildCountsForAllBen(selectedVillage: Int): Flow> + + @Query(""" + SELECT COUNT(child.beneficiaryId) + FROM BENEFICIARY parent + LEFT JOIN BENEFICIARY child + ON child.householdId = parent.householdId + AND child.beneficiaryId != parent.beneficiaryId + AND (parent.firstName IS NULL OR parent.firstName = '' OR child.motherName LIKE parent.firstName || '%') + WHERE parent.beneficiaryId = :benId + AND parent.isDraft = 0 + AND parent.isDeactivate = 0 + """) + suspend fun getChildCountForBen(benId: Long): Int + @Query("SELECT * FROM BEN_BASIC_CACHE WHERE hhId = :hhId") fun getAllBasicBenForHousehold(hhId: Long): Flow> @Query("SELECT * FROM BENEFICIARY WHERE householdId = :hhId") suspend fun getAllBenForHousehold(hhId: Long): List + @Query("SELECT * FROM BENEFICIARY WHERE householdId = :hhId AND beneficiaryId != :selectedbenIdFromArgs AND (:firstName IS NULL OR :firstName = '' OR motherName LIKE :firstName || '%') order by age desc") + suspend fun getChildBenForHousehold(hhId: Long, selectedbenIdFromArgs: Long, firstName: String?): List + + @Query(""" + SELECT COUNT(*) FROM BENEFICIARY + WHERE householdId = :hhId + AND beneficiaryId != :selectedbenIdFromArgs + AND age < 15 + AND (:firstName IS NULL OR :firstName = '' OR motherName LIKE :firstName || '%') + """) + suspend fun getBelow15Count( + hhId: Long, + selectedbenIdFromArgs: Long, + firstName: String? + ): Int + + @Query(""" + SELECT COUNT(*) FROM BENEFICIARY + WHERE householdId = :hhId + AND beneficiaryId != :selectedbenIdFromArgs + AND age >= 15 + AND (:firstName IS NULL OR :firstName = '' OR motherName LIKE :firstName || '%') + """) + suspend fun get15aboveCount( + hhId: Long, + selectedbenIdFromArgs: Long, + firstName: String? + ): Int + @Query("SELECT * FROM BENEFICIARY WHERE beneficiaryId =:benId AND householdId = :hhId LIMIT 1") suspend fun getBen(hhId: Long, benId: Long): BenRegCache? @Query("SELECT * FROM BENEFICIARY WHERE beneficiaryId =:benId LIMIT 1") suspend fun getBen(benId: Long): BenRegCache? + @Query("SELECT EXISTS(SELECT 1 FROM BENEFICIARY WHERE beneficiaryId = :benId AND isDeath = 1)") + suspend fun isBenDead(benId: Long): Boolean + @Query("UPDATE BENEFICIARY SET syncState = :syncState WHERE beneficiaryId =:benId AND householdId = :hhId") suspend fun setSyncState(hhId: Long, benId: Long, syncState: SyncState) @@ -57,8 +517,8 @@ interface BenDao { @Query("UPDATE BENEFICIARY SET beneficiaryId = :newId, benRegId = :benRegId WHERE householdId = :hhId AND beneficiaryId =:oldId") suspend fun substituteBenId(hhId: Long, oldId: Long, newId: Long, benRegId: Long) - @Query("UPDATE BENEFICIARY SET serverUpdatedStatus = 1 , beneficiaryId = :newId , processed = 'U', userImage = :imageUri WHERE householdId = :hhId AND beneficiaryId =:oldId") - suspend fun updateToFinalBenId(hhId: Long, oldId: Long, newId: Long, imageUri: String? = null) + @Query("UPDATE BENEFICIARY SET serverUpdatedStatus = 1 , beneficiaryId = :newId , benRegId =:newBenRegId ,processed = 'U', userImage = :imageUri WHERE householdId = :hhId AND beneficiaryId =:oldId") + suspend fun updateToFinalBenId(hhId: Long, oldId: Long, newId: Long, imageUri: String? = null,newBenRegId:Long) @Query("SELECT * FROM BENEFICIARY WHERE isDraft = 0 AND processed = 'N' AND syncState =:unsynced ") suspend fun getAllUnprocessedBen(unsynced: SyncState = SyncState.UNSYNCED): List @@ -72,7 +532,8 @@ interface BenDao { @Query("SELECT COUNT(*) FROM BENEFICIARY WHERE isDraft = 0 AND (processed = 'N' OR processed = 'U') AND syncState =0") fun getAllUnProcessedRecordCount(): Flow - @Query("SELECT * FROM BENEFICIARY WHERE isDraft = 0 AND processed = 'U' AND syncState =:unsynced ") +// @Query("SELECT * FROM BENEFICIARY WHERE isDraft = 0 AND processed = 'U' AND syncState =:unsynced AND isConsent = 1") + @Query("SELECT * FROM BENEFICIARY WHERE isDraft = 0 AND processed = 'U' AND syncState =:unsynced") suspend fun getAllBenForSyncWithServer(unsynced: SyncState = SyncState.UNSYNCED): List @Query("UPDATE BENEFICIARY SET processed = 'P' , syncState = 2 WHERE beneficiaryId in (:benId)") @@ -84,28 +545,27 @@ interface BenDao { @Query("SELECT beneficiaryId FROM BENEFICIARY WHERE beneficiaryId IN (:list)") fun getAllBeneficiaryFromList(list: List): LiveData> + @Query("SELECT beneficiaryId FROM BENEFICIARY WHERE beneficiaryId IN (:list)") + suspend fun getExistingBenIds(list: List): List + @Query("SELECT * FROM BEN_BASIC_CACHE WHERE CAST((strftime('%s','now') - dob/1000)/60/60/24/365 AS INTEGER) BETWEEN :min and :max and reproductiveStatusId = 1 and villageId=:selectedVillage") fun getAllEligibleCoupleList( selectedVillage: Int, min: Int = Konstants.minAgeForEligibleCouple, max: Int = Konstants.maxAgeForEligibleCouple ): Flow> -// @Query("SELECT * FROM BEN_BASIC_CACHE WHERE CAST((strftime('%s','now') - dob/1000)/60/60/24/365 AS INTEGER) BETWEEN :min and :max and reproductiveStatusId = 1 and ecrFilled = 1 and villageId=:selectedVillage") -// fun getAllEligibleTrackingList( -// selectedVillage: Int, -// min: Int = Konstants.minAgeForEligibleCouple, max: Int = Konstants.maxAgeForEligibleCouple -// ): Flow> - // @Query("SELECT b.benId as ecBenId,b.*, r.noOfLiveChildren as numChildren , t.* FROM ben_basic_cache b join eligible_couple_reg r on b.benId=r.benId left outer join eligible_couple_tracking t on t.benId=b.benId WHERE CAST((strftime('%s','now') - b.dob/1000)/60/60/24/365 AS INTEGER) BETWEEN :min and :max and b.reproductiveStatusId = 1 and b.villageId=:selectedVillage group by b.benId") +// @Query("SELECT b.benId as ecBenId,b.*, r.noOfLiveChildren as numChildren , t.* FROM ben_basic_cache b join eligible_couple_reg r on b.benId=r.benId left outer join eligible_couple_tracking t on t.benId=b.benId WHERE CAST((strftime('%s','now') - b.dob/1000)/60/60/24/365 AS INTEGER) BETWEEN :min and :max and b.reproductiveStatusId = 1 and b.villageId=:selectedVillage group by b.benId") @Transaction - @Query("SELECT b.* FROM ben_basic_cache b join eligible_couple_reg r on b.benId=r.benId WHERE CAST((strftime('%s','now') - b.dob/1000)/60/60/24/365 AS INTEGER) BETWEEN :min and :max and b.reproductiveStatusId = 1 and b.villageId=:selectedVillage group by b.benId") +// @Query("SELECT b.* FROM ben_basic_cache b join eligible_couple_reg r on b.benId=r.benId LEFT JOIN pregnancy_anc a ON b.benId = a.benId WHERE CAST((strftime('%s','now') - b.dob/1000)/60/60/24/365 AS INTEGER) BETWEEN :min and :max and b.reproductiveStatusId = 1 and b.villageId=:selectedVillage and isDeath = 0 or isDeath is NULL group by b.benId") + @Query("SELECT b.* FROM ben_basic_cache b JOIN eligible_couple_reg r ON b.benId = r.benId LEFT JOIN pregnancy_anc a ON b.benId = a.benId WHERE CAST((strftime('%s','now') - b.dob/1000)/60/60/24/365 AS INTEGER) BETWEEN :min AND :max AND b.reproductiveStatusId = 1 AND b.isDeactivate = 0 AND b.villageId = :selectedVillage AND (b.isDeath = 0 OR b.isDeath IS NULL OR b.isDeath = 'undefined') GROUP BY b.benId") fun getAllEligibleTrackingList( selectedVillage: Int, min: Int = Konstants.minAgeForEligibleCouple, max: Int = Konstants.maxAgeForEligibleCouple ): Flow> @Transaction - @Query("SELECT * FROM ben_basic_cache WHERE CAST((strftime('%s','now') - dob/1000)/60/60/24/365 AS INTEGER) BETWEEN :min and :max and reproductiveStatusId = 1 and villageId=:selectedVillage group by benId") + @Query("SELECT * FROM ben_basic_cache WHERE CAST((strftime('%s','now') - dob/1000)/60/60/24/365 AS INTEGER) BETWEEN :min and :max and reproductiveStatusId = 1 and villageId=:selectedVillage and isDeactivate = 0 and (isDeath = 0 or isDeath is NULL or isDeath = 'undefined') group by benId") fun getAllEligibleRegistrationList( selectedVillage: Int, min: Int = Konstants.minAgeForEligibleCouple, max: Int = Konstants.maxAgeForEligibleCouple @@ -118,54 +578,175 @@ interface BenDao { ): Flow @Transaction - @Query("SELECT ben.* FROM BEN_BASIC_CACHE ben left outer join pregnancy_register pr on pr.benId = ben.benId WHERE reproductiveStatusId = 2 and (pr.benId is null or pr.active = 1) and villageId=:selectedVillage") + @Query("SELECT ben.* FROM BEN_BASIC_CACHE ben left outer join pregnancy_register pr on pr.benId = ben.benId WHERE reproductiveStatusId = 2 and isDeactivate =0 and (pr.benId is null or pr.active = 1) and villageId=:selectedVillage") fun getAllPregnancyWomenList(selectedVillage: Int): Flow> @Transaction - @Query("SELECT * FROM BEN_BASIC_CACHE WHERE reproductiveStatusId = 2 and villageId=:selectedVillage") + @Query("SELECT ben.* FROM BEN_BASIC_CACHE ben left outer join pregnancy_register pr on pr.benId = ben.benId WHERE reproductiveStatusId = 2 and isDeactivate =0 and (pr.benId is null or pr.active = 1) and villageId=:selectedVillage and ben.rchId is not null and ben.rchId != ''") + fun getAllPregnancyWomenWithRchList(selectedVillage: Int): Flow> + + @Transaction + @Query("SELECT * FROM BEN_BASIC_CACHE WHERE reproductiveStatusId = 2 and isDeactivate=0 and villageId=:selectedVillage") fun getAllPregnancyWomenForHRList(selectedVillage: Int): Flow> @Transaction - @Query("SELECT count(*) FROM BEN_BASIC_CACHE WHERE reproductiveStatusId = 2 and villageId=:selectedVillage") + @Query("SELECT count(*) FROM BEN_BASIC_CACHE WHERE reproductiveStatusId = 2 and isDeactivate=0 and villageId=:selectedVillage") fun getAllPregnancyWomenForHRListCount(selectedVillage: Int): Flow - @Query("SELECT COUNT(*) FROM BEN_BASIC_CACHE WHERE reproductiveStatusId = 2 and villageId=:selectedVillage") + @Query("SELECT COUNT(*) FROM BEN_BASIC_CACHE WHERE reproductiveStatusId = 2 and isDeactivate=0 and villageId=:selectedVillage") fun getAllPregnancyWomenListCount(selectedVillage: Int): Flow - @Query("SELECT ben.* FROM BEN_BASIC_CACHE ben inner join pregnancy_register pwr on pwr.benId = ben.benId inner join pregnancy_anc anc on ben.benId = anc.benId WHERE ben.reproductiveStatusId =3 and anc.pregnantWomanDelivered =1 and anc.isActive = 1 and pwr.active = 1 and villageId=:selectedVillage group by ben.benId order by anc.updatedDate desc ") + @Query("SELECT ben.* FROM BEN_BASIC_CACHE ben inner join pregnancy_register pwr on pwr.benId = ben.benId inner join pregnancy_anc anc on ben.benId = anc.benId WHERE ben.reproductiveStatusId =3 and ben.isDeactivate =0 and anc.pregnantWomanDelivered =1 and anc.isActive = 1 and pwr.active = 1 and villageId=:selectedVillage group by ben.benId order by anc.updatedDate desc ") fun getAllDeliveredWomenList(selectedVillage: Int): Flow> - @Query("SELECT count(distinct(ben.benId)) FROM BEN_BASIC_CACHE ben inner join pregnancy_register pwr on pwr.benId = ben.benId inner join pregnancy_anc anc on ben.benId = anc.benId WHERE ben.reproductiveStatusId =3 and anc.pregnantWomanDelivered =1 and anc.isActive = 1 and pwr.active = 1 and villageId=:selectedVillage") + @Query("SELECT count(distinct(ben.benId)) FROM BEN_BASIC_CACHE ben inner join pregnancy_register pwr on pwr.benId = ben.benId inner join pregnancy_anc anc on ben.benId = anc.benId WHERE ben.reproductiveStatusId =3 and ben.isDeactivate=0 and anc.pregnantWomanDelivered =1 and anc.isActive = 1 and pwr.active = 1 and villageId=:selectedVillage") fun getAllDeliveredWomenListCount(selectedVillage: Int): Flow @Transaction - @Query("SELECT * FROM BEN_BASIC_CACHE WHERE reproductiveStatusId = 1 and gender = 'FEMALE' and villageId=:selectedVillage") + @Query("SELECT * FROM BEN_BASIC_CACHE WHERE reproductiveStatusId = 1 and isDeactivate=0 and gender = 'FEMALE' and villageId=:selectedVillage") fun getAllNonPregnancyWomenList(selectedVillage: Int): Flow> - @Query("SELECT COUNT(*) FROM BEN_BASIC_CACHE WHERE reproductiveStatusId = 1 and gender = 'FEMALE' and villageId=:selectedVillage") + @Query("SELECT COUNT(*) FROM BEN_BASIC_CACHE WHERE reproductiveStatusId = 1 and gender = 'FEMALE' and isDeactivate=0 and villageId=:selectedVillage") fun getAllNonPregnancyWomenListCount(selectedVillage: Int): Flow @Transaction - @Query("SELECT ben.* FROM BEN_BASIC_CACHE ben inner join delivery_outcome do on do.benId = ben.benId where do.isActive = 1 and villageId=:selectedVillage") + @Query("SELECT ben.* FROM BEN_BASIC_CACHE ben inner join delivery_outcome do on do.benId = ben.benId where do.isActive = 1 and ben.isDeactivate=0 and villageId=:selectedVillage") fun getListForInfantRegister(selectedVillage: Int): Flow> - @Query("SELECT count(distinct(ben.benId)) FROM BEN_BASIC_CACHE ben inner join delivery_outcome do on do.benId = ben.benId where do.isActive = 1 and ben.villageId=:selectedVillage") + @Query(""" SELECT SUM(do.liveBirth) + FROM delivery_outcome do + INNER JOIN BEN_BASIC_CACHE ben ON do.benId = ben.benId + WHERE do.isActive = 1 AND do.liveBirth > 0 AND ben.isDeactivate=0 AND ben.villageId = :selectedVillage """) fun getInfantRegisterCount(selectedVillage: Int): Flow - @Query("SELECT * FROM BEN_BASIC_CACHE WHERE pwHrp = 1 and villageId=:selectedVillage") + @Query("SELECT * FROM BEN_BASIC_CACHE WHERE pwHrp = 1 and villageId=:selectedVillage and isDeactivate=0 ") fun getAllWomenListForPmsma(selectedVillage: Int): Flow> - @Query("SELECT COUNT(*) FROM BEN_BASIC_CACHE WHERE pwHrp = 1 and villageId=:selectedVillage") + @Query("SELECT COUNT(*) FROM BEN_BASIC_CACHE WHERE pwHrp = 1 and isDeactivate=0 and villageId=:selectedVillage") fun getAllWomenListForPmsmaCount(selectedVillage: Int): Flow @Transaction - @Query("SELECT ben.* from BEN_BASIC_CACHE ben inner join pregnancy_register pwr on pwr.benId = ben.benId where pwr.active = 1 and ben.reproductiveStatusId=2 and ben.villageId=:selectedVillage group by ben.benId") + @Query("SELECT ben.* from BEN_BASIC_CACHE ben inner join pregnancy_register pwr on pwr.benId = ben.benId where pwr.active = 1 and ben.reproductiveStatusId=2 and ben.isDeactivate=0 and ben.villageId=:selectedVillage group by ben.benId") fun getAllRegisteredPregnancyWomenList(selectedVillage: Int): Flow> - @Query("SELECT count(distinct(ben.benId)) FROM BEN_BASIC_CACHE ben inner join pregnancy_register pwr on pwr.benId = ben.benId where pwr.active = 1 and ben.reproductiveStatusId=2 and ben.villageId=:selectedVillage") + @Transaction + @Query(""" + SELECT ben.* + FROM BEN_BASIC_CACHE ben + INNER JOIN pregnancy_register pwr ON pwr.benId = ben.benId + WHERE pwr.active = 1 + AND ben.reproductiveStatusId = 2 + AND ben.isDeactivate=0 + AND ben.villageId = :selectedVillage + AND (ben.isDeath = 0 OR ben.isDeath IS NULL OR ben.isDeath = 'undefined') + AND (ben.benId IN (SELECT benId FROM PMSMA WHERE highriskSymbols = 1) + OR ben.benId IN (SELECT benId FROM PREGNANCY_ANC WHERE anyHighRisk = 1)) + GROUP BY ben.benId +""") + fun getAllHighRiskPregnancyWomenList(selectedVillage: Int): Flow> + + + + @Transaction + @Query(""" + SELECT * + FROM BEN_BASIC_CACHE + WHERE reproductiveStatusId = 2 AND isDeactivate = 0 + AND villageId = :selectedVillage +""") + fun getAllRegisteredPmsmaWomenList(selectedVillage: Int): Flow> + + + @Transaction + @Query("SELECT ben.* from BEN_BASIC_CACHE ben inner join pregnancy_anc pwr on pwr.benId = ben.benId where pwr.isAborted = 1 and ben.isDeactivate = 0 and ben.villageId=:selectedVillage group by ben.benId") + fun getAllAbortionWomenList(selectedVillage: Int): Flow> + + @Query(""" + SELECT * FROM BEN_BASIC_CACHE + WHERE + CAST((strftime('%s','now') - dob/1000)/60/60/24/365 AS INTEGER) >= 15 + AND isDeath = 1 + and isDeactivate=0 + AND (reasonOfDeath IS NULL OR reasonOfDeath != 'Maternal Death') + AND isMdsr = 0 + AND villageId = :selectedVillage +""") + fun getAllGeneralDeathsList(selectedVillage: Int): Flow> + + @Query(""" + SELECT * FROM BEN_BASIC_CACHE + WHERE + gender = 'FEMALE' + AND CAST((strftime('%s','now') - dob/1000)/60/60/24/365 AS INTEGER) BETWEEN 15 AND 49 + AND isDeath = 1 + AND isDeactivate=0 + AND (reasonOfDeath IS NULL OR reasonOfDeath != 'Maternal Death') + AND isMdsr = 0 + AND villageId = :selectedVillage + """) + fun getAllNonMaternalDeathsList(selectedVillage: Int): Flow> + + @Query(""" + SELECT COUNT(*) FROM BEN_BASIC_CACHE + WHERE + CAST((strftime('%s','now') - dob/1000)/60/60/24/365 AS INTEGER) >= 15 + AND isDeath = 1 + AND isDeactivate=0 + AND (reasonOfDeath IS NULL OR reasonOfDeath != 'Maternal Death') + AND isMdsr = 0 + AND villageId = :selectedVillage + +""") + fun getAllGeneralDeathsCount(selectedVillage: Int): Flow + + @Query(""" + SELECT COUNT(*) FROM BEN_BASIC_CACHE + WHERE + gender = 'FEMALE' + AND CAST((strftime('%s','now') - dob/1000)/60/60/24/365 AS INTEGER) BETWEEN 15 AND 49 + AND isDeath = 1 + AND isDeactivate=0 + AND (reasonOfDeath IS NULL OR reasonOfDeath != 'Maternal Death') + AND isMdsr = 0 + AND villageId = :selectedVillage +""") + fun getAllNonMaternalDeathsCount(selectedVillage: Int): Flow + @Query(""" + SELECT count(distinct(ben.benId)) FROM BEN_BASIC_CACHE ben + inner join pregnancy_register pwr on pwr.benId = ben.benId + where pwr.active = 1 and ben.reproductiveStatusId=2 + and isDeactivate=0 and ben.villageId=:selectedVillage + AND ben.benId NOT IN (SELECT benId FROM PREGNANCY_ANC WHERE maternalDeath = 1) + """) fun getAllRegisteredPregnancyWomenListCount(selectedVillage: Int): Flow + @Query("SELECT count(distinct(ben.benId)) FROM BEN_BASIC_CACHE ben inner join pregnancy_anc pwr on pwr.benId = ben.benId where pwr.isAborted = 1 and pwr.abortionDate is not null and ben.villageId=:selectedVillage and isDeactivate=0") + fun getAllAbortionWomenListCount(selectedVillage: Int): Flow + + @Query(""" + SELECT COUNT(DISTINCT ben.benId) + FROM BEN_BASIC_CACHE ben + INNER JOIN pregnancy_register pwr ON pwr.benId = ben.benId + WHERE pwr.active = 1 + AND ben.reproductiveStatusId = 2 + AND ben.isDeactivate = 0 + AND ben.villageId = :selectedVillage + AND (ben.isDeath = 0 OR ben.isDeath IS NULL OR ben.isDeath = 'undefined') + AND (ben.benId IN (SELECT benId FROM PMSMA WHERE highriskSymbols = 1) + OR ben.benId IN (SELECT benId FROM PREGNANCY_ANC WHERE anyHighRisk = 1)) + AND ben.benId NOT IN (SELECT benId FROM PREGNANCY_ANC WHERE maternalDeath = 1) +""") + fun getHighRiskWomenCount(selectedVillage: Int): Flow + + @Query("SELECT count(distinct(ben.benId)) FROM BEN_BASIC_CACHE ben inner join pregnancy_anc pwr on pwr.benId = ben.benId where pwr.isActive = 1 and (pwr.ancDate + :nonFollowUpDuration) <= :currentTime and pwr.ancDate > (:currentTime - :year) and ben.reproductiveStatusId = 2 and ben.villageId = :selectedVillage") + fun getAllRegisteredPregnancyWomenNonFollowUpListCount( + selectedVillage: Int, + nonFollowUpDuration: Long = Konstants.nonFollowUpDuration, + year: Long = Konstants.minMillisBwtweenCbacFiling, + currentTime: Long = System.currentTimeMillis() + ): Flow + @Query("SELECT * FROM BEN_BASIC_CACHE where CAST((strftime('%s','now') - dob/1000)/60/60/24/365 AS INTEGER) >= :min and villageId=:selectedVillage") fun getAllNCDList( selectedVillage: Int, min: Int = Konstants.minAgeForNcd @@ -184,10 +765,10 @@ interface BenDao { fun getAllNCDNonEligibleList(selectedVillage: Int): Flow> // have to add those as well who we are adding to menopause entries manually from app - @Query("SELECT * FROM BEN_BASIC_CACHE WHERE reproductiveStatusId = 5 and villageId=:selectedVillage") + @Query("SELECT * FROM BEN_BASIC_CACHE WHERE reproductiveStatusId = 5 and isDeactivate=0 and villageId=:selectedVillage") fun getAllMenopauseStageList(selectedVillage: Int): Flow> - @Query("SELECT * FROM BEN_BASIC_CACHE WHERE gender = :female and CAST((strftime('%s','now') - dob/1000)/60/60/24/365 AS INTEGER) BETWEEN :min and :max and villageId=:selectedVillage") + @Query("SELECT * FROM BEN_BASIC_CACHE WHERE gender = :female and CAST((strftime('%s','now') - dob/1000)/60/60/24/365 AS INTEGER) BETWEEN :min and :max and villageId=:selectedVillage and isDeactivate=0") fun getAllReproductiveAgeList( selectedVillage: Int, min: Int = Konstants.minAgeForReproductiveAge, @@ -196,75 +777,172 @@ interface BenDao { ): Flow> @Transaction - @Query("SELECT ben.* FROM BEN_BASIC_CACHE ben join delivery_outcome del on ben.benId = del.benId left outer join pnc_visit pnc on pnc.benId = ben.benId WHERE reproductiveStatusId = 3 and (pnc.isActive is null or pnc.isActive == 1) and CAST((strftime('%s','now') - del.dateOfDelivery/1000)/60/60/24 AS INTEGER) BETWEEN :minPncDate and :maxPncDate and villageId=:selectedVillage group by ben.benId") + //@Query("SELECT ben.* FROM BEN_BASIC_CACHE ben left outer join delivery_outcome del on ben.benId = del.benId left outer join pnc_visit pnc on pnc.benId = ben.benId WHERE reproductiveStatusId = 3 and (pnc.isActive is null or pnc.isActive == 1) and CAST((strftime('%s','now') - del.dateOfDelivery/1000)/60/60/24 AS INTEGER) BETWEEN :minPncDate and :maxPncDate and villageId=:selectedVillage group by ben.benId") + //@Query("SELECT ben.* FROM BEN_BASIC_CACHE ben LEFT OUTER JOIN delivery_outcome del ON ben.benId = del.benId LEFT OUTER JOIN pnc_visit pnc ON pnc.benId = ben.benId WHERE reproductiveStatusId = 3 AND (pnc.isActive IS NULL OR pnc.isActive == 1) AND ( del.dateOfDelivery IS NULL OR CAST((strftime('%s','now') - COALESCE(del.dateOfDelivery, 0)/1000)/60/60/24 AS INTEGER) BETWEEN :minPncDate AND :maxPncDate) AND (:selectedVillage IS NULL OR villageId = :selectedVillage) GROUP BY ben.benId") + + @Query("SELECT ben.* FROM BEN_BASIC_CACHE ben LEFT OUTER JOIN delivery_outcome del ON ben.benId = del.benId LEFT OUTER JOIN pnc_visit pnc ON pnc.benId = ben.benId WHERE reproductiveStatusId = 3 AND (pnc.isActive IS NULL OR pnc.isActive = 1) AND (pnc.pncPeriod IS NULL OR pnc.pncPeriod != 42) AND (ben.isDeath IS NULL OR ben.isDeath = 0 OR ben.isDeath = 'undefined' ) AND ben.isDeactivate = 0 AND (:selectedVillage IS NULL OR villageId = :selectedVillage) GROUP BY ben.benId") fun getAllPNCMotherList( - selectedVillage: Int, - minPncDate: Long = 0, - maxPncDate: Long = Konstants.pncEcGap + selectedVillage: Int +// minPncDate: Long = 0, +// maxPncDate: Long = Konstants.pncEcGap ): Flow> - @Query("SELECT * FROM BEN_BASIC_CACHE WHERE CAST((strftime('%s','now') - dob/1000)/60/60/24/365 AS INTEGER) <= :max and villageId=:selectedVillage") + @Query("SELECT * FROM BEN_BASIC_CACHE WHERE CAST(((strftime('%s','now') - dob/1000)/60/60/24) AS INTEGER) <= :max and villageId=:selectedVillage and isDeactivate=0") fun getAllInfantList( selectedVillage: Int, max: Int = Konstants.maxAgeForInfant ): Flow> - @Query("SELECT * FROM BEN_BASIC_CACHE WHERE CAST((strftime('%s','now') - dob/1000)/60/60/24/365 AS INTEGER) BETWEEN :min and :max and villageId=:selectedVillage") + @Query("SELECT * FROM BEN_BASIC_CACHE WHERE benId = :benId LIMIT 1") + suspend fun getBenById(benId: Long): BenBasicCache? + + @Query("SELECT * FROM BEN_BASIC_CACHE WHERE CAST(((strftime('%s','now') - dob/1000)/60/60/24) AS INTEGER) <= :max and villageId=:selectedVillage and rchId is not null and rchId != ''") + fun getAllInfantWithRchList( + selectedVillage: Int, max: Int = Konstants.maxAgeForInfant + ): Flow> + + @Query("SELECT * FROM BEN_BASIC_CACHE WHERE CAST(((strftime('%s','now') - dob/1000)/60/60/24) AS INTEGER) BETWEEN :min and :max and villageId=:selectedVillage and isDeactivate =0") fun getAllChildList( selectedVillage: Int, min: Int = Konstants.minAgeForChild, max: Int = Konstants.maxAgeForChild ): Flow> - @Query("SELECT * FROM BEN_BASIC_CACHE WHERE CAST((strftime('%s','now') - dob/1000)/60/60/24/365 AS INTEGER) BETWEEN :min and :max and villageId=:selectedVillage") + @Query("SELECT * FROM BEN_BASIC_CACHE WHERE CAST(((strftime('%s','now') - dob/1000)/60/60/24) AS INTEGER) BETWEEN :min and :max and villageId=:selectedVillage and rchId is not null and rchId != ''") + fun getAllChildWithRchList( + selectedVillage: Int, + min: Int = Konstants.minAgeForChild, + max: Int = Konstants.maxAgeForChild + ): Flow> + + @Query("SELECT * FROM BEN_BASIC_CACHE WHERE CAST((strftime('%s','now') - dob/1000)/60/60/24/365 AS INTEGER) BETWEEN :min and :max and villageId=:selectedVillage and isDeactivate = 0") fun getAllAdolescentList( selectedVillage: Int, min: Int = Konstants.minAgeForAdolescent, - max: Int = Konstants.maxAgeForAdolescent - ): Flow> + max: Int = Konstants.maxAgeForAdolescentlist + ): Flow> - @Query("SELECT * FROM BEN_BASIC_CACHE WHERE isKid = 1 or reproductiveStatusId in (2, 3) and villageId=:selectedVillage") + @Query("SELECT * FROM BEN_BASIC_CACHE WHERE isKid = 1 or reproductiveStatusId in (2, 3) and villageId=:selectedVillage and isDeactivate=0 ") fun getAllImmunizationDueList(selectedVillage: Int): Flow> - @Query("SELECT * FROM BEN_BASIC_CACHE WHERE hrpStatus = 1 and villageId=:selectedVillage") + @Query("SELECT * FROM BEN_BASIC_CACHE WHERE hrpStatus = 1 and villageId=:selectedVillage and isDeactivate=0 ") fun getAllHrpCasesList(selectedVillage: Int): Flow> @Query("SELECT * FROM BEN_BASIC_CACHE WHERE reproductiveStatusId = 4 and hhId = :hhId") suspend fun getAllPNCMotherListFromHousehold(hhId: Long): List - @Query("SELECT * FROM BEN_BASIC_CACHE WHERE CAST((strftime('%s','now') - dob/1000)/60/60/24/365 AS INTEGER) <= :max and villageId=:selectedVillage") + @Query("SELECT * FROM BEN_BASIC_CACHE WHERE CAST((strftime('%s','now') - dob/1000)/60/60/24/365 AS INTEGER) <= :max and villageId=:selectedVillage AND isDeath = 1 and isDeactivate=0 " ) fun getAllCDRList( selectedVillage: Int, max: Int = Konstants.maxAgeForCdr ): Flow> + @Query("SELECT COUNT(*) FROM BEN_BASIC_CACHE WHERE CAST((strftime('%s','now') - dob/1000)/60/60/24/365 AS INTEGER) <= :max and villageId=:selectedVillage AND isDeath = 1 and isDeactivate=0" ) + fun getAllCDRListCount( + selectedVillage: Int, max: Int = Konstants.maxAgeForCdr + ): Flow + + @Transaction - @Query("SELECT * FROM BEN_BASIC_CACHE WHERE reproductiveStatusId in (2, 3, 4) and isMdsr = 1 and villageId=:selectedVillage") + @Query("SELECT * FROM BEN_BASIC_CACHE WHERE reproductiveStatusId in (2, 3, 4) and isMdsr = 1 and villageId=:selectedVillage and isDeactivate=0 ") fun getAllMDSRList(selectedVillage: Int): Flow> - + @Transaction + @Query("SELECT COUNT(*) FROM BEN_BASIC_CACHE WHERE reproductiveStatusId in (2, 3, 4) and isMdsr = 1 and villageId=:selectedVillage and isDeactivate=0") + fun getAllMDSRCount(selectedVillage: Int): Flow @Query("SELECT * FROM BEN_BASIC_CACHE WHERE CAST((strftime('%s','now') - dob/1000)/60/60/24/365 AS INTEGER)<=:max and villageId=:selectedVillage") fun getAllChildrenImmunizationList( - selectedVillage: Int, max: Int = Konstants.maxAgeForAdolescent + selectedVillage: Int, max: Int = Konstants.maxAgeForAdolescentlist ): Flow> - @Query("SELECT * FROM BEN_BASIC_CACHE WHERE reproductiveStatusId = 4 and villageId=:selectedVillage") + @Query("SELECT * FROM BEN_BASIC_CACHE WHERE reproductiveStatusId = 4 and villageId=:selectedVillage and isDeactivate=0 ") fun getAllMotherImmunizationList(selectedVillage: Int): Flow> - @Query("select ben.* from ben_basic_cache ben inner join pregnancy_register pwr on ben.benId = pwr.benId left outer join pregnancy_anc pwa on ben.benId = pwa.benId where ben.villageId = :villageId and pwr.isHrp =1 or pwa.hrpConfirmed = 1 order by pwa.visitNumber desc ") + @Query("select ben.* from ben_basic_cache ben inner join pregnancy_register pwr on ben.benId = pwr.benId left outer join pregnancy_anc pwa on ben.benId = pwa.benId where ben.villageId = :villageId and ben.isDeactivate=0 and pwr.isHrp =1 or pwa.hrpConfirmed = 1 order by pwa.visitNumber desc ") fun getHrpCases(villageId: Int): Flow> @Query("select * from BEN_BASIC_CACHE b inner join tb_screening t on b.benId = t.benId where villageId = :villageId and tbsnFilled = 1 and (t.bloodInSputum =1 or t.coughMoreThan2Weeks = 1 or feverMoreThan2Weeks = 1 or nightSweats = 1 or lossOfWeight = 1 or historyOfTb = 1)") fun getScreeningList(villageId: Int): Flow> @Transaction - @Query("select b.* from BEN_BASIC_CACHE b inner join tb_screening t on b.benId = t.benId where villageId = :villageId and tbsnFilled = 1 and (t.bloodInSputum =1 or t.coughMoreThan2Weeks = 1 or feverMoreThan2Weeks = 1 or nightSweats = 1 or lossOfWeight = 1 or historyOfTb = 1)") + @Query("select b.* from BEN_BASIC_CACHE b inner join tb_screening t on b.benId = t.benId LEFT JOIN TB_SUSPECTED ts ON b.benId = ts.benId where villageId = :villageId and isDeactivate=0 and tbsnFilled = 1 and (t.bloodInSputum =1 or t.coughMoreThan2Weeks = 1 or feverMoreThan2Weeks = 1 or nightSweats = 1 or lossOfWeight = 1 or historyOfTb = 1)AND (\n" + + " ts.benId IS NULL\n" + + " OR ts.isConfirmed = 0\n" + + " )") fun getTbScreeningList(villageId: Int): Flow> @Transaction - @Query("SELECT * FROM BEN_BASIC_CACHE b where CAST((strftime('%s','now') - b.dob/1000)/60/60/24/365 AS INTEGER) >= :min and b.reproductiveStatusId!=2 and b.villageId=:selectedVillage group by b.benId order by b.regDate desc") + @Query("select b.* from BEN_BASIC_CACHE b inner join TB_SUSPECTED t on b.benID = t.benId and t.isConfirmed =1 where villageId = :villageId and isDeactivate=0") + fun getTbConfirmedList(villageId: Int): Flow> + + + @Transaction + @Query("select b.*, t.slideTestName As slideTestName from BEN_BASIC_CACHE b inner join malaria_screening t on b.benId = t.benId where villageId = :villageId and isDeactivate=0 and t.caseStatus = 'Confirmed' ") + fun getMalariaConfirmedCasesList(villageId: Int): Flow> + + @Transaction + @Query("SELECT * FROM BEN_BASIC_CACHE b where CAST((strftime('%s','now') - b.dob/1000)/60/60/24/365 AS INTEGER) >= :min and b.reproductiveStatusId!=2 and b.isDeactivate=0 and b.villageId=:selectedVillage group by b.benId order by b.regDate desc") fun getBenWithCbac( selectedVillage: Int, min: Int = Konstants.minAgeForNcd ): Flow> - @Query("SELECT COUNT(*) FROM BEN_BASIC_CACHE b where CAST((strftime('%s','now') - b.dob/1000)/60/60/24/365 AS INTEGER) >= :min and b.reproductiveStatusId!=2 and b.villageId=:selectedVillage") + + + @Transaction + @Query(""" + SELECT r.*, b.* + FROM BEN_BASIC_CACHE b + INNER JOIN NCD_REFER r ON b.benId = r.benId + WHERE CAST((strftime('%s','now') - b.dob/1000)/60/60/24/365 AS INTEGER) >= :min + AND b.reproductiveStatusId != 2 + AND b.villageId = :selectedVillage + AND b.isDeactivate = 0 + ORDER BY b.regDate DESC +""") + fun getBenWithReferredCbac( + selectedVillage: Int, + min: Int = Konstants.minAgeForNcd + ): Flow> + + + @Query(""" + SELECT COUNT(b.benId) + FROM BEN_BASIC_CACHE b + INNER JOIN NCD_REFER r ON b.benId = r.benId + AND CAST((strftime('%s','now') - b.dob/1000)/60/60/24/365 AS INTEGER) >= :min + AND b.reproductiveStatusId != 2 + AND b.villageId = :selectedVillage + AND b.isDeactivate=0 +""") + fun getReferredBenCount( + selectedVillage: Int, + min: Int = Konstants.minAgeForNcd + ): Flow + + @Query(""" + SELECT COUNT(b.benId) + FROM BEN_BASIC_CACHE b + INNER JOIN NCD_REFER r ON b.benId = r.benId + AND b.villageId = :selectedVillage + AND r.type = "MATERNAL" + AND b.villageId = :selectedVillage + AND isDeactivate=0 +""") + fun getReferredHWCBenCount( + selectedVillage: Int, + ): Flow + + @Query(""" + SELECT r.*, b.* + FROM BEN_BASIC_CACHE b + INNER JOIN NCD_REFER r ON b.benId = r.benId + AND b.villageId = :selectedVillage + AND b.isDeactivate=0 + AND r.type = "MATERNAL" +""") + fun getReferredHWCBenList( + selectedVillage: Int, + ): Flow> + + + @Query("SELECT COUNT(*) FROM BEN_BASIC_CACHE b where CAST((strftime('%s','now') - b.dob/1000)/60/60/24/365 AS INTEGER) >= :min and b.reproductiveStatusId!=2 and isDeactivate=0 and b.villageId=:selectedVillage") fun getBenWithCbacCount( selectedVillage: Int, min: Int = Konstants.minAgeForNcd ): Flow @@ -274,27 +952,99 @@ interface BenDao { @Transaction - @Query("select * from BEN_BASIC_CACHE where villageId = :villageId and reproductiveStatusId = 2 and gender = 'FEMALE' and benId in (select benId from HRP_PREGNANT_ASSESS where isHighRisk = 1);") + @Query("select * from BEN_BASIC_CACHE where villageId = :villageId and reproductiveStatusId = 2 and isDeactivate = 0 and gender = 'FEMALE' and benId in (select benId from HRP_PREGNANT_ASSESS where isHighRisk = 1);") fun getAllHRPTrackingPregList(villageId: Int): Flow> @Transaction @Query("select * from BEN_BASIC_CACHE where benId = :benId;") fun getHRPTrackingPregForBen(benId: Long): BenWithHRPTrackingCache - @Query("select count(*) from BEN_BASIC_CACHE where villageId = :villageId and reproductiveStatusId = 2 and gender = 'FEMALE' and benId in (select benId from HRP_PREGNANT_ASSESS where isHighRisk = 1);") + @Query("select count(*) from BEN_BASIC_CACHE where villageId = :villageId and reproductiveStatusId = 2 and isDeactivate=0 and gender = 'FEMALE' and benId in (select benId from HRP_PREGNANT_ASSESS where isHighRisk = 1);") fun getAllHRPTrackingPregListCount(villageId: Int): Flow @Transaction - @Query("select * from BEN_BASIC_CACHE where villageId = :villageId and reproductiveStatusId = 1 and gender = 'FEMALE' and benId in (select benId from HRP_NON_PREGNANT_ASSESS where isHighRisk = 1);") + @Query("select * from BEN_BASIC_CACHE where villageId = :villageId and reproductiveStatusId = 1 and isDeactivate=0 and gender = 'FEMALE' and benId in (select benId from HRP_NON_PREGNANT_ASSESS where isHighRisk = 1);") fun getAllHRPTrackingNonPregList(villageId: Int): Flow> - @Query("select count(*) from BEN_BASIC_CACHE where villageId = :villageId and reproductiveStatusId = 1 and gender = 'FEMALE' and benId in (select benId from HRP_NON_PREGNANT_ASSESS where isHighRisk = 1);") + @Query("select count(*) from BEN_BASIC_CACHE where villageId = :villageId and reproductiveStatusId = 1 and isDeactivate=0 and gender = 'FEMALE' and benId in (select benId from HRP_NON_PREGNANT_ASSESS where isHighRisk = 1);") fun getAllHRPTrackingNonPregListCount(villageId: Int): Flow - @Query("select count(*) from INFANT_REG inf join ben_basic_cache ben on ben.benId = inf.motherBenId where isActive = 1 and weight < :lowWeightLimit and ben.villageId = :villageId") + @Query( + """ + SELECT count(*) FROM INFANT_REG inf + JOIN ben_basic_cache ben ON ben.benId = inf.motherBenId + JOIN delivery_outcome do ON do.benId = inf.motherBenId AND do.isActive = 1 + WHERE inf.isActive = 1 + AND ben.isDeactivate = 0 + AND inf.weight < :lowWeightLimit + AND ben.villageId = :villageId + AND do.dateOfDelivery IS NOT NULL + AND (strftime('%s','now') * 1000) - do.dateOfDelivery <= :maxAgeMillis + """ + ) fun getLowWeightBabiesCount( villageId: Int, - lowWeightLimit: Double = Konstants.babyLowWeight + lowWeightLimit: Double = Konstants.babyLowWeight, + maxAgeMillis: Long = Konstants.pncEcGap * 24 * 60 * 60 * 1000 ): Flow + @Transaction + @Query( + """ + SELECT DISTINCT ben.* FROM BEN_BASIC_CACHE ben + JOIN INFANT_REG inf ON ben.benId = inf.motherBenId + JOIN delivery_outcome do ON do.benId = inf.motherBenId AND do.isActive = 1 + WHERE inf.isActive = 1 + AND ben.isDeactivate = 0 + AND inf.weight < :lowWeightLimit + AND ben.villageId = :villageId + AND do.dateOfDelivery IS NOT NULL + AND (strftime('%s','now') * 1000) - do.dateOfDelivery <= :maxAgeMillis + """ + ) + fun getListForLowWeightInfantRegister( + villageId: Int, + lowWeightLimit: Double = Konstants.babyLowWeight, + maxAgeMillis: Long = Konstants.pncEcGap * 24 * 60 * 60 * 1000 + ): Flow> + + // Pregnancy Death + @Query("SELECT COUNT(*) FROM PREGNANCY_ANC WHERE benId = :benId AND deathDate IS NOT NULL AND isAborted = 0") + suspend fun checkPregnancyDeath(benId: Long): Boolean + + + // Abortion Death + @Query("SELECT COUNT(*) FROM PREGNANCY_ANC WHERE benId = :benId AND deathDate IS NOT NULL AND isAborted = 1") + suspend fun checkAbortionDeath(benId: Long): Boolean + + // Delivery Outcome + @Query("SELECT COUNT(*) FROM DELIVERY_OUTCOME WHERE benId = :benId AND dateOfDeath IS NOT NULL") + suspend fun checkDeliveryDeath(benId: Long): Boolean + + // PNC + @Query("SELECT COUNT(*) FROM PNC_VISIT WHERE benId = :benId AND deathDate IS NOT NULL") + suspend fun checkPncDeath(benId: Long): Boolean + + @Query(""" + SELECT EXISTS( + SELECT 1 FROM PNC_VISIT + WHERE benId = :benId + AND deathDate IS NOT NULL + AND causeOfDeath = :cause + ) +""") + suspend fun isDeathByCause(benId: Long, cause: String): Boolean + + @Query(""" + SELECT EXISTS( + SELECT 1 FROM PREGNANCY_ANC + WHERE benId = :benId + AND deathDate IS NOT NULL + AND maternalDeathProbableCause = :cause + ) +""") + suspend fun isDeathByCauseAnc(benId: Long,cause: String): Boolean + + @Query("UPDATE BENEFICIARY SET syncState = 0 WHERE syncState = 1") + suspend fun resetSyncingToUnsynced() } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/CbacDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/CbacDao.kt index e3202c273..8e7f05277 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/CbacDao.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/CbacDao.kt @@ -14,12 +14,16 @@ interface CbacDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun upsert(vararg cbacCache: CbacCache) + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertAll(cbacCache: List) @Update suspend fun update(vararg cbacCache: CbacCache) @Query("SELECT * FROM CBAC WHERE id = :cbacId LIMIT 1") suspend fun getCbacFromBenId(cbacId: Int): CbacCache? + + @Query("SELECT * FROM CBAC WHERE benId = :benId order by fillDate desc LIMIT 1") suspend fun getLastFilledCbacFromBenId(benId: Long): CbacCache? @@ -40,4 +44,9 @@ interface CbacDao { @Query("select count(*)>0 from cbac where createdDate between :createdDate-:range and :createdDate+:range") suspend fun sameCreateDateExists(createdDate: Long, range: Long): Boolean + @Query("UPDATE cbac SET isReffered = :status WHERE benId = :benId") + suspend fun updateReferralStatus(benId: Long, status: Boolean) + + @Query("UPDATE CBAC SET syncState = 0 WHERE syncState = 1") + suspend fun resetSyncingToUnsynced() } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/ChildRegistrationDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/ChildRegistrationDao.kt index f91433dbd..593c22d8e 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/ChildRegistrationDao.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/ChildRegistrationDao.kt @@ -14,7 +14,7 @@ interface ChildRegistrationDao { // @Query("SELECT b.*, c.* FROM BEN_BASIC_CACHE b join infant_reg i on b.benId = i.motherBenId left outer join CHILD_REG c on i.motherBenId= c.benId WHERE villageId=:selectedVillage") @Transaction - @Query("SELECT i.* FROM infant_reg i join beneficiary b on b.beneficiaryId = i.motherBenId where i.isActive = 1 and b.loc_village_id = :selectedVillage") + @Query("SELECT i.* FROM infant_reg i join beneficiary b on b.beneficiaryId = i.motherBenId where i.isActive = 1 and b.loc_village_id = :selectedVillage and b.isDeactivate=0") fun getAllRegisteredInfants(selectedVillage: Int): Flow> @Query("SELECT count(*) FROM infant_reg i join beneficiary b on b.beneficiaryId = i.motherBenId where i.isActive = 1 and b.loc_village_id = :selectedVillage") diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/DeliveryOutcomeDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/DeliveryOutcomeDao.kt index 9584d54c4..d61c3c0d2 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/DeliveryOutcomeDao.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/DeliveryOutcomeDao.kt @@ -29,4 +29,7 @@ interface DeliveryOutcomeDao { @MapInfo(keyColumn = "benId", valueColumn = "dateOfDelivery") @Query("select * from delivery_outcome where isActive = 1") suspend fun getAllBenIdAndDeliverDate(): Map + + @Query("UPDATE delivery_outcome SET syncState = 0 WHERE syncState = 1") + suspend fun resetSyncingToUnsynced() } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/EcrDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/EcrDao.kt index bdd25619b..ea53d7716 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/EcrDao.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/EcrDao.kt @@ -22,6 +22,10 @@ interface EcrDao { @Query("SELECT * FROM ELIGIBLE_COUPLE_REG WHERE benId =:benId limit 1") suspend fun getSavedECR(benId: Long): EligibleCoupleRegCache? +// @Query("SELECT noOfChildren FROM ELIGIBLE_COUPLE_REG WHERE benId = :benId LIMIT 1") + @Query("SELECT noOfLiveChildren FROM ELIGIBLE_COUPLE_REG WHERE benId = :benId LIMIT 1") + suspend fun getNoOfChildren(benId: Long): Int? + @Update suspend fun update(it: EligibleCoupleRegCache) @@ -41,5 +45,12 @@ interface EcrDao { @Query("select count(*)>0 from eligible_couple_tracking where createdDate=:createdDate") suspend fun ectWithsameCreateDateExists(createdDate: Long): Boolean + @Query("SELECT * FROM eligible_couple_tracking WHERE benId = :benId ORDER BY visitDate DESC") + suspend fun getAllAntraDoses(benId: Long): List + + @Query("UPDATE ELIGIBLE_COUPLE_REG SET syncState = 0 WHERE syncState = 1") + suspend fun resetRegSyncingToUnsynced() + @Query("UPDATE ELIGIBLE_COUPLE_TRACKING SET syncState = 0 WHERE syncState = 1") + suspend fun resetTrackingSyncingToUnsynced() } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/FilariaDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/FilariaDao.kt new file mode 100644 index 000000000..81f5768dd --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/FilariaDao.kt @@ -0,0 +1,25 @@ +package org.piramalswasthya.sakhi.database.room.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.model.FilariaScreeningCache + +@Dao +interface FilariaDao { + @Query("SELECT * FROM FILARIA_SCREENING WHERE benId =:benId limit 1") + suspend fun getFilariaScreening(benId: Long): FilariaScreeningCache? + + @Query("SELECT * FROM FILARIA_SCREENING WHERE benId =:benId and (mdaHomeVisitDate = :visitDate or mdaHomeVisitDate = :visitDateGMT) limit 1") + suspend fun getFilariaScreening(benId: Long, visitDate: Long, visitDateGMT: Long): FilariaScreeningCache? + + @Query("SELECT * FROM FILARIA_SCREENING WHERE syncState = :syncState") + suspend fun getFilariaScreening(syncState: SyncState): List + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun saveFilariaScreening(malariaScreeningCache: FilariaScreeningCache) + + @Query("UPDATE FILARIA_SCREENING SET syncState = 0 WHERE syncState = 1") + suspend fun resetSyncingToUnsynced() +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/GeneralOpdDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/GeneralOpdDao.kt new file mode 100644 index 000000000..35b3a37ac --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/GeneralOpdDao.kt @@ -0,0 +1,18 @@ +package org.piramalswasthya.sakhi.database.room.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import kotlinx.coroutines.flow.Flow +import org.piramalswasthya.sakhi.model.GeneralOPEDBeneficiary + +@Dao +interface GeneralOpdDao { + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertAll(beneficiaries: List) + + @Query("SELECT * FROM GENERAL_OPD_ACTIVITY") + fun getAll(): Flow> +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/HouseholdDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/HouseholdDao.kt index f69eaa9e3..3c66bcc23 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/HouseholdDao.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/HouseholdDao.kt @@ -6,6 +6,7 @@ import androidx.room.OnConflictStrategy import androidx.room.Query import androidx.room.Update import kotlinx.coroutines.flow.Flow +import org.piramalswasthya.sakhi.database.room.SyncState import org.piramalswasthya.sakhi.model.HouseholdBasicCache import org.piramalswasthya.sakhi.model.HouseholdCache @@ -18,6 +19,13 @@ interface HouseholdDao { @Update suspend fun update(household: HouseholdCache) + @Query("UPDATE HOUSEHOLD SET processed = :proccess , serverUpdatedStatus =:updateStatus WHERE householdId = :householdId") + suspend fun updateHouseholdToSync( + householdId: Long, + proccess: String, + updateStatus: Int + ) + @Query("SELECT * FROM HOUSEHOLD WHERE isDraft = 1 LIMIT 1") suspend fun getDraftHousehold(): HouseholdCache? @@ -27,7 +35,10 @@ interface HouseholdDao { @Query("SELECT h.*, count(b.householdId) as numMembers FROM HOUSEHOLD as h left outer join BENEFICIARY b on b.householdId = h.householdId WHERE h.isDraft = 0 and h.loc_village_Id = :selectedVillage group by h.householdId") fun getAllHouseholdWithNumMembers(selectedVillage: Int): Flow> - @Query("SELECT COUNT(*) FROM HOUSEHOLD WHERE isDraft = 0 and loc_village_Id = :selectedVillage") + @Query("SELECT h.*, count(b.householdId) as numMembers FROM HOUSEHOLD as h left outer join BENEFICIARY b on b.householdId = h.householdId WHERE h.isDraft = 0 and h.loc_village_Id = :selectedVillage AND h.registrationType = 'Yes' group by h.householdId") + fun getAllHouseholdForAshaFamilyMembers(selectedVillage: Int): Flow> + + @Query("SELECT COUNT(*) FROM HOUSEHOLD WHERE isDraft = 0 and loc_village_Id = :selectedVillage and isDeactivate =0") fun getAllHouseholdsCount(selectedVillage: Int): Flow @Query("SELECT * FROM HOUSEHOLD WHERE householdId =:hhId LIMIT 1") diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/HrpDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/HrpDao.kt index bc7f62bce..f72f0061e 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/HrpDao.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/HrpDao.kt @@ -19,9 +19,16 @@ interface HrpDao { @Query("select * from HRP_PREGNANT_ASSESS where syncState = :syncState") fun getHRPAssess(syncState: SyncState): List? + @Query("select * from HRP_MICRO_BIRTH_PLAN where syncState = :syncState") + fun getMicroBirthPlan(syncState: SyncState): List? + @Insert(onConflict = OnConflictStrategy.REPLACE) fun saveRecord(hrpPregnantAssessCache: HRPPregnantAssessCache) + @Query("select * from HRP_MICRO_BIRTH_PLAN where benId = :benId limit 1") + fun getSavedRecord(benId: Long): HRPMicroBirthPlanCache? + + @Query("select * from HRP_NON_PREGNANT_ASSESS where benId = :benId") fun getNonPregnantAssess(benId: Long): HRPNonPregnantAssessCache? @@ -101,4 +108,19 @@ interface HrpDao { @Query("select * from HRP_NON_PREGNANT_TRACK where benId = :benId and (visitDate = :visitDate or visitDate = :visitDateGMT)") fun getHRPNonTrack(benId: Long, visitDate: Long, visitDateGMT: Long): HRPNonPregnantTrackCache? + + @Query("UPDATE HRP_PREGNANT_ASSESS SET syncState = 0 WHERE syncState = 1") + suspend fun resetPregnantAssessSyncingToUnsynced() + + @Query("UPDATE HRP_PREGNANT_TRACK SET syncState = 0 WHERE syncState = 1") + suspend fun resetPregnantTrackSyncingToUnsynced() + + @Query("UPDATE HRP_NON_PREGNANT_ASSESS SET syncState = 0 WHERE syncState = 1") + suspend fun resetNonPregnantAssessSyncingToUnsynced() + + @Query("UPDATE HRP_NON_PREGNANT_TRACK SET syncState = 0 WHERE syncState = 1") + suspend fun resetNonPregnantTrackSyncingToUnsynced() + + @Query("UPDATE HRP_MICRO_BIRTH_PLAN SET syncState = 0 WHERE syncState = 1") + suspend fun resetMicroBirthPlanSyncingToUnsynced() } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/ImmunizationDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/ImmunizationDao.kt index 627f63cb3..f2eee6e4c 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/ImmunizationDao.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/ImmunizationDao.kt @@ -7,6 +7,7 @@ import androidx.room.Query import androidx.room.Transaction import kotlinx.coroutines.flow.Flow import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.model.ChildImmunizationCategory import org.piramalswasthya.sakhi.model.ChildImmunizationDetailsCache import org.piramalswasthya.sakhi.model.ImmunizationCache import org.piramalswasthya.sakhi.model.ImmunizationCategory @@ -25,6 +26,9 @@ interface ImmunizationDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun addImmunizationRecord(imm: ImmunizationCache) + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertImmunizationRecord( imm: List):LongArray + @Query("SELECT * FROM IMMUNIZATION WHERE beneficiaryId=:benId AND vaccineId =:vaccineId limit 1") suspend fun getImmunizationRecord(benId: Long, vaccineId: Int): ImmunizationCache? @@ -58,9 +62,15 @@ interface ImmunizationDao { @Query("SELECT * FROM VACCINE where category = :immCat order by vaccineId") suspend fun getVaccinesForCategory(immCat: ImmunizationCategory): List + @Query("SELECT * FROM VACCINE where category = :immCat order by vaccineId") + suspend fun getVaccinesForCategory(immCat: ChildImmunizationCategory): List + @Query("SELECT * FROM VACCINE WHERE vaccineId = :vaccineId limit 1") suspend fun getVaccineById(vaccineId: Int): Vaccine? @Query("SELECT * FROM VACCINE WHERE vaccineName = :name limit 1") suspend fun getVaccineByName(name: String): Vaccine? + + @Query("UPDATE IMMUNIZATION SET syncState = 0 WHERE syncState = 1") + suspend fun resetSyncingToUnsynced() } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/IncentiveDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/IncentiveDao.kt index 599dc764e..b652beee0 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/IncentiveDao.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/IncentiveDao.kt @@ -1,11 +1,11 @@ package org.piramalswasthya.sakhi.database.room.dao import androidx.room.Dao -import androidx.room.Insert -import androidx.room.OnConflictStrategy import androidx.room.Query +import androidx.room.Upsert import kotlinx.coroutines.flow.Flow import org.piramalswasthya.sakhi.model.IncentiveActivityCache +import org.piramalswasthya.sakhi.model.IncentiveActivityWithRecords import org.piramalswasthya.sakhi.model.IncentiveCache import org.piramalswasthya.sakhi.model.IncentiveRecordCache @@ -13,15 +13,18 @@ import org.piramalswasthya.sakhi.model.IncentiveRecordCache interface IncentiveDao { - @Insert(onConflict = OnConflictStrategy.REPLACE) + @Upsert suspend fun insert(vararg incentiveActivity: IncentiveActivityCache) - @Insert(onConflict = OnConflictStrategy.REPLACE) + @Upsert suspend fun insert(vararg incentiveRecord: IncentiveRecordCache) @Query("select * from INCENTIVE_RECORD") fun getAllRecords(): Flow> + @Query("select * from INCENTIVE_ACTIVITY") + fun getAllActivity() : Flow> + @Query("select * from INCENTIVE_RECORD where id = :recordId") fun getRecordById(recordId: Long): IncentiveRecordCache? diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/InfantRegDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/InfantRegDao.kt index 8d3c2cf62..8371a5fac 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/InfantRegDao.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/InfantRegDao.kt @@ -30,4 +30,7 @@ interface InfantRegDao { @Query("SELECT * FROM INFANT_REG WHERE motherBenId in (:eligBenIds) and isActive = 1") suspend fun getAllInfantRegs(eligBenIds: Set): List + + @Query("UPDATE INFANT_REG SET syncState = 0 WHERE syncState = 1") + suspend fun resetSyncingToUnsynced() } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/KalaAzarDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/KalaAzarDao.kt new file mode 100644 index 000000000..d2bc995c6 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/KalaAzarDao.kt @@ -0,0 +1,37 @@ +package org.piramalswasthya.sakhi.database.room.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.model.KalaAzarScreeningCache +@Dao +interface KalaAzarDao { + @Query("SELECT * FROM KALAZAR_SCREENING WHERE benId =:benId limit 1") + suspend fun getKalaAzarScreening(benId: Long): KalaAzarScreeningCache? + + @Query("SELECT * FROM KALAZAR_SCREENING WHERE benId =:benId and (visitDate = :visitDate or visitDate = :visitDateGMT) limit 1") + suspend fun getKalaAzarScreening(benId: Long, visitDate: Long, visitDateGMT: Long): KalaAzarScreeningCache? + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun saveKalaAzarScreening(tbScreeningCache: KalaAzarScreeningCache) + + @Query("SELECT * FROM KALAZAR_SCREENING WHERE benId =:benId limit 1") + suspend fun getKalaAzarSuspected(benId: Long): KalaAzarScreeningCache? + + @Query("SELECT * FROM KALAZAR_SCREENING WHERE benId =:benId and (visitDate = :visitDate or visitDate = :visitDateGMT) limit 1") + suspend fun getKalaAzarSuspected(benId: Long, visitDate: Long, visitDateGMT: Long): KalaAzarScreeningCache? + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun saveTbSuspected(tbSuspectedCache: KalaAzarScreeningCache) + + @Query("SELECT * FROM KALAZAR_SCREENING WHERE syncState = :syncState") + suspend fun getKalaAzarScreening(syncState: SyncState): List + + @Query("SELECT * FROM KALAZAR_SCREENING WHERE syncState = :syncState") + suspend fun getKalaAzarSuspected(syncState: SyncState): List + + @Query("UPDATE KALAZAR_SCREENING SET syncState = 0 WHERE syncState = 1") + suspend fun resetSyncingToUnsynced() +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/LeprosyDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/LeprosyDao.kt new file mode 100644 index 000000000..f4906df97 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/LeprosyDao.kt @@ -0,0 +1,74 @@ +package org.piramalswasthya.sakhi.database.room.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Transaction +import androidx.room.Update +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.model.BenWithLeprosyScreeningCache +import org.piramalswasthya.sakhi.model.LeprosyFollowUpCache +import org.piramalswasthya.sakhi.model.LeprosyScreeningCache + +@Dao +interface LeprosyDao { + @Query("SELECT * FROM LEPROSY_SCREENING WHERE benId =:benId limit 1") + suspend fun getLeprosyScreening(benId: Long): LeprosyScreeningCache? + + @Query("SELECT * FROM LEPROSY_SCREENING WHERE benId =:benId and (homeVisitDate = :visitDate or homeVisitDate = :visitDateGMT) limit 1") + suspend fun getLeprosyScreening(benId: Long, visitDate: Long, visitDateGMT: Long): LeprosyScreeningCache? + + @Query("SELECT * FROM LEPROSY_SCREENING WHERE syncState = :syncState") + suspend fun getLeprosyScreening(syncState: SyncState): List + + @Query("SELECT * FROM LEPROSY_FOLLOW_UP WHERE syncState = :syncState") + suspend fun getLeprosyFollowUP(syncState: SyncState): List + + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun saveLeprosyScreening(malariaScreeningCache: LeprosyScreeningCache) + + @Update + suspend fun updateLeprosyScreening(malariaScreeningCache: LeprosyScreeningCache) + + + + @Query("SELECT * FROM LEPROSY_FOLLOW_UP WHERE benId = :benId AND visitNumber = :visitNumber") + suspend fun getFollowUpByVisit(benId: Long, visitNumber: Int): LeprosyFollowUpCache? + + @Query("SELECT * FROM LEPROSY_FOLLOW_UP WHERE benId = :benId ORDER BY visitNumber") + suspend fun getAllFollowUpsByBenId(benId: Long): List + + @Query("DELETE FROM LEPROSY_FOLLOW_UP WHERE benId = :benId AND visitNumber = :visitNumber") + suspend fun deleteFollowUp(benId: Long, visitNumber: Int) + + @Query("SELECT * FROM LEPROSY_FOLLOW_UP WHERE benId = :benId ORDER BY visitNumber DESC, followUpDate DESC") + suspend fun getAllFollowUpsForBeneficiary(benId: Long): List + + @Query("SELECT * FROM LEPROSY_FOLLOW_UP WHERE benId = :benId AND visitNumber = :visitNumber ORDER BY followUpDate DESC") + suspend fun getFollowUpsByVisit(benId: Long, visitNumber: Int): List + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertFollowUp(followUp: LeprosyFollowUpCache) + + @Update + suspend fun updateFollowUp(followUp: LeprosyFollowUpCache) + + @Query("SELECT * FROM LEPROSY_FOLLOW_UP WHERE benId = :benId AND visitNumber = :visitNumber AND followUpDate = :followUpDate") + suspend fun getFollowUp(benId: Long, visitNumber: Int, followUpDate: Long): LeprosyFollowUpCache? + + + @Query("SELECT * FROM LEPROSY_FOLLOW_UP WHERE benId = :benId AND visitNumber = :visitNumber ORDER BY followUpDate DESC") + suspend fun getFollowUpsForVisit(benId: Long, visitNumber: Int): List + + + @Query("SELECT * FROM LEPROSY_FOLLOW_UP") + suspend fun getAllFollowUpsByBenId(): List + + @Query("UPDATE LEPROSY_SCREENING SET syncState = 0 WHERE syncState = 1") + suspend fun resetScreeningSyncingToUnsynced() + + @Query("UPDATE LEPROSY_FOLLOW_UP SET syncState = 0 WHERE syncState = 1") + suspend fun resetFollowUpSyncingToUnsynced() +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/MaaMeetingDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/MaaMeetingDao.kt new file mode 100644 index 000000000..1fe000d08 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/MaaMeetingDao.kt @@ -0,0 +1,36 @@ +package org.piramalswasthya.sakhi.database.room.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import kotlinx.coroutines.flow.Flow +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.model.MaaMeetingEntity + +@Dao +interface MaaMeetingDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insert(entity: MaaMeetingEntity): Long + + @Query("SELECT * FROM MAA_MEETING WHERE id = :id LIMIT 1") + suspend fun getMaaMeetingById(id: Long): MaaMeetingEntity? + + @Query("select * from MAA_MEETING where syncState = :state") + fun getBySyncState(state: SyncState): List + + @Query("update MAA_MEETING set syncState = :state where id = :id") + fun updateSyncState(id: Long, state: SyncState) + + @Query("select * from MAA_MEETING") + fun getAll(): List + + @Query("SELECT * FROM MAA_MEETING") + fun getAllMaaData(): Flow> + + @Query("delete from MAA_MEETING") + fun clearAll() + + @Query("UPDATE MAA_MEETING SET syncState = 0 WHERE syncState = 1") + suspend fun resetSyncingToUnsynced() +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/MalariaDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/MalariaDao.kt new file mode 100644 index 000000000..0e3c11e10 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/MalariaDao.kt @@ -0,0 +1,88 @@ +package org.piramalswasthya.sakhi.database.room.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Update +import kotlinx.coroutines.flow.Flow +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.model.HouseholdCache +import org.piramalswasthya.sakhi.model.IRSRoundScreening +import org.piramalswasthya.sakhi.model.MalariaConfirmedCasesCache +import org.piramalswasthya.sakhi.model.MalariaScreeningCache +import org.piramalswasthya.sakhi.model.PregnantWomanAncCache +import org.piramalswasthya.sakhi.model.TBScreeningCache +import org.piramalswasthya.sakhi.model.TBSuspectedCache + +@Dao +interface MalariaDao { + @Query("SELECT * FROM MALARIA_SCREENING WHERE benId =:benId limit 1") + suspend fun getMalariaScreening(benId: Long): MalariaScreeningCache? + + @Query("SELECT * FROM MALARIA_SCREENING WHERE benId = :benId ORDER BY visitId DESC") + fun getAllVisitsForBen(benId: Long): Flow> + + @Query("SELECT * FROM MALARIA_SCREENING WHERE benId = :benId ORDER BY visitId DESC LIMIT 1") + fun getLatestVisitForBen(benId: Long): MalariaScreeningCache? + + @Query("SELECT MAX(visitId) FROM MALARIA_SCREENING WHERE benId = :benId") + suspend fun getLastVisitIdForBen(benId: Long): Long? + + @Query("SELECT * FROM MALARIA_SCREENING WHERE benId =:benId and (caseDate = :visitDate or caseDate = :visitDateGMT) limit 1") + suspend fun getMalariaScreening(benId: Long, visitDate: Long, visitDateGMT: Long): MalariaScreeningCache? + + @Query("SELECT * FROM MALARIA_SCREENING WHERE syncState = :syncState") + suspend fun getMalariaScreening(syncState: SyncState): List + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun saveMalariaScreening(malariaScreeningCache: MalariaScreeningCache) + + @Query("SELECT * FROM MALARIA_CONFIRMED WHERE benId =:benId limit 1") + suspend fun getMalariaConfirmed(benId: Long): MalariaConfirmedCasesCache? + + @Query("SELECT * FROM MALARIA_CONFIRMED WHERE benId =:benId and (dateOfDiagnosis = :visitDate or dateOfDiagnosis = :visitDateGMT) limit 1") + suspend fun getMalariaConfirmed(benId: Long, visitDate: Long, visitDateGMT: Long): MalariaConfirmedCasesCache? + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun saveMalariaConfirmed(tbSuspectedCache: MalariaConfirmedCasesCache) + + @Query("SELECT * FROM MALARIA_CONFIRMED WHERE syncState = :syncState") + suspend fun getMalariaConfirmed(syncState: SyncState): List + + + + + @Query("SELECT * FROM IRS_ROUND WHERE householdId =:hhId limit 1") + suspend fun getIRSScreening(hhId: Long): IRSRoundScreening? + + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun saveIRSScreening(vararg ireScreeningCache: IRSRoundScreening) + + @Update + suspend fun update(ireScreeningCache: IRSRoundScreening) + + @Query("select * from IRS_ROUND where householdId = :hhId") + fun getAllActiveIRSRecords(hhId: Long): List + + @Update + suspend fun updateIRS(vararg it: IRSRoundScreening) + + @Query(""" + SELECT COUNT(*) FROM IRS_ROUND + WHERE householdId = :householdId + AND date BETWEEN :yearStart AND :yearEnd + """) + suspend fun countRoundsInYear( + householdId: Long, + yearStart: Long, + yearEnd: Long + ): Int + + @Query("UPDATE MALARIA_SCREENING SET syncState = 0 WHERE syncState = 1") + suspend fun resetScreeningSyncingToUnsynced() + + @Query("UPDATE MALARIA_CONFIRMED SET syncState = 0 WHERE syncState = 1") + suspend fun resetConfirmedSyncingToUnsynced() +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/MaternalHealthDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/MaternalHealthDao.kt index 038cadd21..64e25905d 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/MaternalHealthDao.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/MaternalHealthDao.kt @@ -16,12 +16,24 @@ interface MaternalHealthDao { @Query("select * from pregnancy_anc where benId = :benId and visitNumber = :visitNumber limit 1") fun getSavedRecord(benId: Long, visitNumber: Int): PregnantWomanAncCache? + @Query("select * from pregnancy_anc where benId = :benId limit 1") + fun getSavedRecordANC(benId: Long): PregnantWomanAncCache? + @Query("select * from pregnancy_anc where isActive== 1 and benId = :benId") fun getAllActiveAncRecords(benId: Long): List + @Query("SELECT * FROM pregnancy_anc WHERE isActive== 0 and benId = :benId") + fun getAllInActiveAncRecords(benId: Long): List + + + + + @Query("select * from pregnancy_anc where benId in (:benId) and isActive = 1") fun getAllActiveAncRecords(benId: Set): List + + @Query("select * from pregnancy_register where benId in (:benId)") fun getAllActivePwrRecords(benId: Set): List @@ -62,4 +74,10 @@ interface MaternalHealthDao { @Query("select * from HRP_PREGNANT_ASSESS assess where ((select count(*) from BEN_BASIC_CACHE b where benId = assess.benId and b.reproductiveStatusId = 2) = 1)") fun getAllPregnancyAssessRecords(): Flow> + + @Query("UPDATE pregnancy_register SET syncState = 0 WHERE syncState = 1") + suspend fun resetPwrSyncingToUnsynced() + + @Query("UPDATE pregnancy_anc SET syncState = 0 WHERE syncState = 1") + suspend fun resetAncSyncingToUnsynced() } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/MosquitoNetFormResponseDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/MosquitoNetFormResponseDao.kt new file mode 100644 index 000000000..cd68f2280 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/MosquitoNetFormResponseDao.kt @@ -0,0 +1,83 @@ +package org.piramalswasthya.sakhi.database.room.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Transaction +import org.piramalswasthya.sakhi.model.dynamicEntity.mosquitonetEntity.MosquitoNetFormResponseJsonEntity +import org.piramalswasthya.sakhi.utils.StringMappingUtil +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Locale + +@Dao +interface MosquitoNetFormResponseDao { + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertFormResponse(response: MosquitoNetFormResponseJsonEntity) + + @Query("SELECT COUNT(*) FROM mosquito_net_visit WHERE hhId = :hhId AND visitDate = :visitDate") + suspend fun exists(hhId: Long, visitDate: String): Int + + + @Transaction + suspend fun insertWithLimit(entity: MosquitoNetFormResponseJsonEntity): Boolean { + val visitYear = extractYear(entity.visitDate) + val count = getCountForYear(entity.hhId, entity.formId, visitYear) + + val alreadyExists = exists(entity.hhId, entity.visitDate) > 0 + if (alreadyExists) return false + + if (count < 4) { + insertFormResponse(entity) + return true + } else { + val oldest = getOldestForYear(entity.hhId, entity.formId, visitYear) + if (oldest != null) { + val updated = entity.copy(id = oldest.id) + insertFormResponse(updated) + return true + } + } + return false + } + + @Query("UPDATE mosquito_net_visit SET isSynced = 1, syncedAt = :syncedAt WHERE id = :id") + suspend fun markAsSynced(id: Int, syncedAt: String) + + @Query("SELECT * FROM mosquito_net_visit WHERE hhId = :hhId AND formId = :formId ORDER BY visitDate DESC LIMIT 1") + suspend fun getLatestForHhForm(hhId: Long, formId: String): MosquitoNetFormResponseJsonEntity? + + @Query("SELECT * FROM mosquito_net_visit WHERE hhId = :hhId") + suspend fun getAllByHhId(hhId: Long): List + + @Query("SELECT * FROM mosquito_net_visit WHERE isSynced = 0 AND formId = :formId") + suspend fun getUnsyncedForms(formId: String): List + + @Query("SELECT COUNT(*) FROM mosquito_net_visit WHERE hhId = :hhId AND formId = :formId AND strftime('%Y', visitDate) = :year") + suspend fun getCountForYear(hhId: Long, formId: String, year: String): Int + + @Query("SELECT * FROM mosquito_net_visit WHERE hhId = :hhId AND formId = :formId AND strftime('%Y', visitDate) = :year ORDER BY visitDate ASC LIMIT 1") + suspend fun getOldestForYear(hhId: Long, formId: String, year: String): MosquitoNetFormResponseJsonEntity? + + @Query("SELECT formDataJson FROM mosquito_net_visit WHERE hhId = :hhId ORDER BY substr(visitDate, 7, 4) || '-' || substr(visitDate, 4, 2) || '-' || substr(visitDate, 1, 2) DESC LIMIT 3") + suspend fun getFormJsonList(hhId: Long): List + private fun extractYear(dateStr: String): String { + return try { + val englishDateStr = StringMappingUtil.convertDigits(dateStr) + val inputFormats = listOf("yyyy-MM-dd", "dd-MM-yyyy") + for (fmt in inputFormats) { + try { + val parsed = SimpleDateFormat(fmt, Locale.ENGLISH).parse(englishDateStr) + if (parsed != null) { + return SimpleDateFormat("yyyy", Locale.ENGLISH).format(parsed) + } + } catch (_: Exception) {} + } + Calendar.getInstance().get(Calendar.YEAR).toString() + } catch (_: Exception) { + Calendar.getInstance().get(Calendar.YEAR).toString() + } + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/PmsmaDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/PmsmaDao.kt index fadc4f382..47be7f603 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/PmsmaDao.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/PmsmaDao.kt @@ -2,6 +2,7 @@ package org.piramalswasthya.sakhi.database.room.dao import androidx.room.* import org.piramalswasthya.sakhi.model.PMSMACache +import org.piramalswasthya.sakhi.model.PregnantWomanAncCache @Dao interface PmsmaDao { @@ -18,10 +19,23 @@ interface PmsmaDao { @Query("SELECT * FROM PMSMA WHERE benId =:benId and isActive = 1 LIMIT 1") suspend fun getPmsma(benId: Long): PMSMACache? + @Query("select * from PMSMA where benId = :benId and visitNumber = :visitNumber limit 1") + fun getSavedRecord(benId: Long, visitNumber: Int): PMSMACache? + + @Query("SELECT COUNT(*) FROM pregnancy_anc WHERE benId IN (:benIds) AND isActive = 1") + fun getActiveAncCountForBenIds(benIds: Long): Int + + @Query("SELECT * FROM PMSMA WHERE benId = :benId ORDER BY visitDate DESC LIMIT 1") + fun getLastPmsmaVisit(benId: Long): PMSMACache? + + + @Query("SELECT * FROM PMSMA WHERE benId IN(:benId) and isActive = 1") suspend fun getAllPmsma(benId: Set): List @Update fun updatePmsmaRecord(it: PMSMACache) + @Query("UPDATE PMSMA SET syncState = 0 WHERE syncState = 1") + suspend fun resetSyncingToUnsynced() } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/PncDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/PncDao.kt index db719ec3a..a81f795fb 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/PncDao.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/PncDao.kt @@ -29,4 +29,9 @@ interface PncDao { suspend fun getAllPNCs(eligBenIds: Set): List + @Query("SELECT * FROM PNC_VISIT WHERE benId = :benId") + suspend fun getPncVisitsByBenId(benId: Long): List + + @Query("UPDATE PNC_VISIT SET syncState = 0 WHERE syncState = 1") + suspend fun resetSyncingToUnsynced() } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/ProfileDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/ProfileDao.kt new file mode 100644 index 000000000..692f56610 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/ProfileDao.kt @@ -0,0 +1,15 @@ +package org.piramalswasthya.sakhi.database.room.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import org.piramalswasthya.sakhi.model.ProfileActivityCache +@Dao +interface ProfileDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(vararg profileActivity: ProfileActivityCache) + + @Query("select * from PROFILE_ACTIVITY where employeeId = :id") + fun getProfileActivityById(id: Long): ProfileActivityCache? +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/SaasBahuSammelanDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/SaasBahuSammelanDao.kt new file mode 100644 index 000000000..d3e52bcca --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/SaasBahuSammelanDao.kt @@ -0,0 +1,46 @@ +package org.piramalswasthya.sakhi.database.room.dao + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Update +import kotlinx.coroutines.flow.Flow +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.model.SaasBahuSammelanCache + +@Dao +interface SaasBahuSammelanDao { + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertSammelan(data: SaasBahuSammelanCache) + + @Update + suspend fun updateSammelan(data: SaasBahuSammelanCache) + + @Delete + suspend fun deleteSammelan(data: SaasBahuSammelanCache) + + @Query("select * from SAAS_BAHU_ACTIVITY where syncState = :state") + fun getBySyncState(state: SyncState): List + + @Query("SELECT * FROM SAAS_BAHU_ACTIVITY ORDER BY date DESC") + fun getAllSammelan(): Flow> + + @Query("SELECT * FROM SAAS_BAHU_ACTIVITY WHERE id = :id LIMIT 1") + suspend fun getById(id: Long): SaasBahuSammelanCache? + + @Query(""" + SELECT * FROM SAAS_BAHU_ACTIVITY + WHERE ashaId = :ashaId + ORDER BY date DESC + LIMIT 1 """) + suspend fun getLastUpdatedSammelan(ashaId: Int): SaasBahuSammelanCache? + + @Query("DELETE FROM SAAS_BAHU_ACTIVITY") + suspend fun clearAll() + + @Query("UPDATE SAAS_BAHU_ACTIVITY SET syncState = 0 WHERE syncState = 1") + suspend fun resetSyncingToUnsynced() +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/SyncDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/SyncDao.kt index 56a58c1f2..ec1c95bfb 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/SyncDao.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/SyncDao.kt @@ -73,7 +73,39 @@ interface SyncDao { " SELECT 13 as id, 'HRP Assess' as name, hrpa.syncState as syncState " + " FROM HRP_PREGNANT_ASSESS hrpa " + " INNER JOIN beneficiary b ON b.beneficiaryId = hrpa.benId " + + + " UNION ALL " + + " SELECT 20 as id, 'Micro Birth Plan' as name, hrpa.syncState as syncState " + + " FROM HRP_MICRO_BIRTH_PLAN hrpa " + + " INNER JOIN beneficiary b ON b.beneficiaryId = hrpa.benId " + + + +// " WHERE b.loc_village_id = :villageId " + + " UNION ALL " + + " SELECT 21 as id, 'VHND' as name, vhnd.syncState as syncState " + + " FROM VHND vhnd " + + + " UNION ALL " + + " SELECT 22 as id, 'VHSNC' as name, vhnc.syncState as syncState " + + " FROM VHNC vhnc " + + + " UNION ALL " + + " SELECT 23 as id, 'PHC' as name, phc.syncState as syncState " + + " FROM PHCReviewMeeting phc " + + + " UNION ALL " + + " SELECT 24 as id, 'AHD' as name, ahd.syncState as syncState " + + " FROM AHDMeeting ahd " + + + " UNION ALL " + + " SELECT 25 as id, 'Deworming' as name, deworming.syncState as syncState " + + " FROM DewormingMeeting deworming " + + +// " INNER JOIN beneficiary b ON b.beneficiaryId = hrpa.benId " + // " WHERE b.loc_village_id = :villageId " + + + + " UNION ALL " + " SELECT 14 as id, 'HRP Track' as name, hrpt.syncState as syncState " + " FROM HRP_PREGNANT_TRACK hrpt " + @@ -95,13 +127,20 @@ interface SyncDao { " INNER JOIN beneficiary b ON b.beneficiaryId = imm.beneficiaryId " + // " WHERE b.loc_village_id = :villageId " + " UNION ALL " + - " SELECT 18 as id, 'HBYC' as name, hbyc.syncState as syncState " + - " FROM HBYC hbyc " + + " SELECT 18 as id, 'HBYC' as name, CASE \n" + + " WHEN hbyc.isSynced = 1 THEN 2 ELSE hbyc.isSynced END AS syncState " + + " FROM all_visit_history_hbyc hbyc " + " INNER JOIN beneficiary b ON b.beneficiaryId = hbyc.benId " + " UNION ALL " + - " SELECT 19 as id, 'HBNC' as name, hbnc.syncState as syncState " + - " FROM HBNC hbnc " + + " SELECT 19 as id, 'HBNC' as name, CASE \n" + + " WHEN hbnc.isSynced = 1 THEN 2 ELSE hbnc.isSynced END AS syncState " + + " FROM all_visit_history hbnc " + " INNER JOIN beneficiary b ON b.beneficiaryId = hbnc.benId " + + " UNION ALL"+ + " SELECT 26 as id, 'NCD Follow Up' as name, CASE \n" + + " WHEN ncd.isSynced = 1 THEN 2 ELSE ncd.isSynced END AS syncState " + + " FROM ncd_referal_all_visit ncd " + + " INNER JOIN beneficiary b ON b.beneficiaryId = ncd.benId " + ") AS combined_data " + "GROUP BY id, name, syncState " + "ORDER BY id; " diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/TBDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/TBDao.kt index 2f3005839..55b148125 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/TBDao.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/TBDao.kt @@ -5,6 +5,8 @@ import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.model.LeprosyFollowUpCache +import org.piramalswasthya.sakhi.model.TBConfirmedTreatmentCache import org.piramalswasthya.sakhi.model.TBScreeningCache import org.piramalswasthya.sakhi.model.TBSuspectedCache @@ -23,16 +25,53 @@ interface TBDao { @Query("SELECT * FROM TB_SUSPECTED WHERE benId =:benId limit 1") suspend fun getTbSuspected(benId: Long): TBSuspectedCache? + @Query(""" + SELECT * + FROM TB_CONFIRMED_TREATMENT + WHERE benId = :benId + AND followUpDate IS NOT NULL + ORDER BY followUpDate DESC + LIMIT 1 +""") suspend fun getTbConfirmed(benId: Long): TBConfirmedTreatmentCache? + + @Query(""" + SELECT * + FROM TB_CONFIRMED_TREATMENT + WHERE benId = :benId + AND followUpDate IS NOT NULL + ORDER BY followUpDate DESC + limit 1 +""") suspend fun getALLTbConfirmed(benId: Long): TBConfirmedTreatmentCache? + @Query("SELECT * FROM TB_SUSPECTED WHERE benId =:benId and (visitDate = :visitDate or visitDate = :visitDateGMT) limit 1") suspend fun getTbSuspected(benId: Long, visitDate: Long, visitDateGMT: Long): TBSuspectedCache? @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun saveTbSuspected(tbSuspectedCache: TBSuspectedCache) + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun saveTbConfirmed(tbConfirmedCache: TBConfirmedTreatmentCache) + + + @Query("SELECT * FROM TB_SCREENING WHERE syncState = :syncState") suspend fun getTBScreening(syncState: SyncState): List @Query("SELECT * FROM TB_SUSPECTED WHERE syncState = :syncState") suspend fun getTbSuspected(syncState: SyncState): List + @Query("SELECT * FROM TB_CONFIRMED_TREATMENT WHERE syncState = :syncState") + suspend fun getTbConfirmed(syncState: SyncState): List + + @Query("SELECT * FROM TB_CONFIRMED_TREATMENT WHERE benId = :benId ORDER BY followUpDate DESC") + suspend fun getAllFollowUpsForBeneficiary(benId: Long): List + + @Query("UPDATE TB_SCREENING SET syncState = 0 WHERE syncState = 1") + suspend fun resetScreeningSyncingToUnsynced() + + @Query("UPDATE TB_SUSPECTED SET syncState = 0 WHERE syncState = 1") + suspend fun resetSuspectedSyncingToUnsynced() + + @Query("UPDATE TB_CONFIRMED_TREATMENT SET syncState = 0 WHERE syncState = 1") + suspend fun resetConfirmedSyncingToUnsynced() } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/UwinDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/UwinDao.kt new file mode 100644 index 000000000..2c732bd4c --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/UwinDao.kt @@ -0,0 +1,49 @@ +package org.piramalswasthya.sakhi.database.room.dao + +import androidx.lifecycle.LiveData +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Transaction +import androidx.room.Update +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.model.UwinCache + +@Dao +interface UwinDao { + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(session: UwinCache) + + @Update + suspend fun update(session: UwinCache) + + @Query("SELECT * FROM UWIN_SESSION") + suspend fun getAllSessions(): List + + @Query("SELECT * FROM UWIN_SESSION WHERE id = :id LIMIT 1") + suspend fun getUwinById(id: Int): UwinCache? + + @Query("SELECT * FROM UWIN_SESSION WHERE syncState != :synced") + suspend fun getUnsyncedSessions(synced: SyncState = SyncState.SYNCED): List + + + @Query("UPDATE UWIN_SESSION SET syncState = :syncState WHERE id = :id") + suspend fun updateSyncState(id: Int, syncState: SyncState) + + @Query("SELECT * FROM UWIN_SESSION ORDER BY id DESC") + fun getAllUwinRecords(): LiveData> + + @Query("DELETE FROM UWIN_SESSION") + suspend fun clearAll() + + @Transaction + suspend fun replaceAll(newList: List) { + clearAll() + newList.forEach { insert(it) } + } + + @Query("UPDATE UWIN_SESSION SET syncState = 0 WHERE syncState = 1") + suspend fun resetSyncingToUnsynced() +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/VLFDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/VLFDao.kt new file mode 100644 index 000000000..4c4d69ca5 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/VLFDao.kt @@ -0,0 +1,205 @@ +package org.piramalswasthya.sakhi.database.room.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import kotlinx.coroutines.flow.Flow +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.model.DewormingCache +import org.piramalswasthya.sakhi.model.AHDCache +import org.piramalswasthya.sakhi.model.PHCReviewMeetingCache +import org.piramalswasthya.sakhi.model.VHNCCache +import org.piramalswasthya.sakhi.model.VHNDCache +import org.piramalswasthya.sakhi.model.PulsePolioCampaignCache +import org.piramalswasthya.sakhi.model.ORSCampaignCache +import org.piramalswasthya.sakhi.model.dynamicEntity.filariaaMdaCampaign.FilariaMDACampaignFormResponseJsonEntity + +@Dao +interface VLFDao { + + @Query("select * from VHND where id = :id limit 1") + fun getVHND(id: Int): VHNDCache? + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun saveRecord(vhndCache: VHNDCache) + + @Query("SELECT * FROM VHND") + fun getAllVHND(): Flow> + + @Query("select * from VHNC where id = :id limit 1") + fun getVHNC(id: Int): VHNCCache? + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun saveRecord(vhndCache: VHNCCache) + + @Query("SELECT * FROM VHNC") + fun getAllVHNC(): Flow> + + @Query("select * from PHCReviewMeeting where id = :id limit 1") + fun getPHC(id: Int): PHCReviewMeetingCache? + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun saveRecord(phcCache: PHCReviewMeetingCache) + + @Query("SELECT * FROM PHCReviewMeeting") + fun getAllPHC(): Flow> + + @Query("SELECT * FROM AHDMeeting WHERE id = :id") + suspend fun getAHD(id: Int): AHDCache? + + @Query("SELECT * FROM AHDMeeting") + fun getAllAHD(): Flow> + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun saveRecord(ahdCache: AHDCache) + + + @Query("SELECT * FROM DewormingMeeting WHERE id = :id") + suspend fun getDeworming(id: Int): DewormingCache? + + @Query("SELECT * FROM DewormingMeeting ") + fun getAllDeworming(): Flow> + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun saveRecord(dewormingCache: DewormingCache) + + + @Query("select * from VHND where syncState = :syncState") + fun getVHND(syncState: SyncState): List? + + + @Query("select * from VHNC where syncState = :syncState") + fun getVHNC(syncState: SyncState): List? + + @Query("select * from PHCReviewMeeting where syncState = :syncState") + fun getPHC(syncState: SyncState): List? + + + @Query("select * from AHDMeeting where syncState = :syncState") + fun getAHD(syncState: SyncState): List? + + @Query("select * from DewormingMeeting where syncState = :syncState") + fun getDeworming(syncState: SyncState): List? + + + @Query("SELECT COUNT(*) FROM VHND WHERE vhndDate BETWEEN :startDate AND :endDate") + fun countVHNDFormsInDateRange(startDate: String, endDate: String): Flow + + @Query("SELECT COUNT(*) FROM VHNC WHERE vhncDate BETWEEN :startDate AND :endDate") + fun countVHNCFormsInDateRange(startDate: String, endDate: String): Flow + + @Query("SELECT COUNT(*) FROM PHCReviewMeeting WHERE phcReviewDate BETWEEN :startDate AND :endDate") + fun countPHCFormsInDateRange(startDate: String, endDate: String): Flow + + @Query("SELECT COUNT(*) FROM AHDMeeting WHERE ahdDate BETWEEN :startDate AND :endDate") + fun countAHDFormsInDateRange(startDate: String, endDate: String): Flow + @Query(""" + SELECT COUNT(*) + FROM DewormingMeeting + WHERE date( + substr(dewormingDate, 7, 4) || '-' || + substr(dewormingDate, 4, 2) || '-' || + substr(dewormingDate, 1, 2) + ) >= date('now', '-6 months') +""") + fun countDewormingInLastSixMonths(): Flow + + // For VHND form + @Query("SELECT MAX(vhndDate) FROM VHND") + fun getLastVHNDSubmissionDate(): Flow + + // For VHNC form + @Query("SELECT MAX(vhncDate) FROM VHNC") + fun getLastVHNCSubmissionDate(): Flow + + // For PHC form + @Query("SELECT MAX(phcReviewDate) FROM PHCReviewMeeting") + fun getLastPHCSubmissionDate(): Flow + + // For AHD form + @Query("SELECT MAX(ahdDate) FROM AHDMeeting") + fun getLastAHDSubmissionDate(): Flow + + // For Deworming form + @Query("SELECT MAX(regDate) FROM DewormingMeeting") + fun getLastDewormingSubmissionDate(): Flow + + // For Pulse Polio Campaign form + @Query("SELECT * FROM PulsePolioCampaign WHERE id = :id") + suspend fun getPulsePolioCampaign(id: Int): PulsePolioCampaignCache? + + @Query("SELECT * FROM PulsePolioCampaign") + fun getAllPulsePolioCampaign(): Flow> + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun saveRecord(pulsePolioCampaignCache: PulsePolioCampaignCache) + + @Query("SELECT * FROM PulsePolioCampaign WHERE syncState = :syncState") + fun getPulsePolioCampaign(syncState: SyncState): List? + + @Query("SELECT * FROM PulsePolioCampaign") + fun getAllPulsePolioCampaignForDate(): Flow> + + @Query("SELECT * FROM PulsePolioCampaign") + suspend fun getAllPulsePolioCampaigns(): List + + + // For ORS Campaign form + @Query("SELECT * FROM ORSCampaign WHERE id = :id") + suspend fun getORSCampaign(id: Int): ORSCampaignCache? + + @Query("SELECT * FROM ORSCampaign") + fun getAllORSCampaign(): Flow> + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun saveRecord(orsCampaignCache: ORSCampaignCache) + + @Query("SELECT * FROM ORSCampaign WHERE syncState = :syncState") + fun getORSCampaign(syncState: SyncState): List? + + @Query("SELECT * FROM ORSCampaign") + fun getAllORSCampaignForDate(): Flow> + + @Query("SELECT * FROM ORSCampaign") + suspend fun getAllORSCampaigns(): List + + @Query("SELECT * FROM FILARIA_MDA_CAMPAIGN_HISTORY WHERE id = :id") + suspend fun getFilariaMdaCampaign(id: Int): FilariaMDACampaignFormResponseJsonEntity? + + @Query("SELECT * FROM FILARIA_MDA_CAMPAIGN_HISTORY") + fun getAllFilariaMdaCampaign(): Flow> + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun saveRecord(filariaMdaCampaignCache: FilariaMDACampaignFormResponseJsonEntity) + + @Query("SELECT * FROM FILARIA_MDA_CAMPAIGN_HISTORY WHERE syncState = :syncState") + fun getFilariaMdaCampaign(syncState: SyncState): List? + + @Query("SELECT * FROM FILARIA_MDA_CAMPAIGN_HISTORY") + fun getAllFilariaMdaCampaignForDate(): Flow> + + @Query("UPDATE VHND SET syncState = 0 WHERE syncState = 1") + suspend fun resetVhndSyncingToUnsynced() + + @Query("UPDATE VHNC SET syncState = 0 WHERE syncState = 1") + suspend fun resetVhncSyncingToUnsynced() + + @Query("UPDATE PHCReviewMeeting SET syncState = 0 WHERE syncState = 1") + suspend fun resetPhcSyncingToUnsynced() + + @Query("UPDATE AHDMeeting SET syncState = 0 WHERE syncState = 1") + suspend fun resetAhdSyncingToUnsynced() + + @Query("UPDATE DewormingMeeting SET syncState = 0 WHERE syncState = 1") + suspend fun resetDewormingSyncingToUnsynced() + + @Query("UPDATE PulsePolioCampaign SET syncState = 0 WHERE syncState = 1") + suspend fun resetPulsePolioSyncingToUnsynced() + + @Query("UPDATE ORSCampaign SET syncState = 0 WHERE syncState = 1") + suspend fun resetOrsSyncingToUnsynced() + + @Query("UPDATE FILARIA_MDA_CAMPAIGN_HISTORY SET syncState = 0 WHERE syncState = 1") + suspend fun resetFilariaMdaSyncingToUnsynced() +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/BenIfaFormResponseJsonDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/BenIfaFormResponseJsonDao.kt new file mode 100644 index 000000000..8296d7ad5 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/BenIfaFormResponseJsonDao.kt @@ -0,0 +1,40 @@ +package org.piramalswasthya.sakhi.database.room.dao.dynamicSchemaDao + +import androidx.room.Dao +import androidx.room.Query +import androidx.room.Upsert +import org.piramalswasthya.sakhi.model.dynamicEntity.ben_ifa.BenIfaFormResponseJsonEntity + +@Dao +interface BenIfaFormResponseJsonDao { + + @Upsert + suspend fun insertFormResponse(response: BenIfaFormResponseJsonEntity) + + @Query("SELECT * FROM ALL_BEN_IFA_VISIT_HISTORY WHERE benId = :benId AND visitDate = :visitDate LIMIT 1") + suspend fun getFormResponse(benId: Long, visitDate: String): BenIfaFormResponseJsonEntity? + + @Query("DELETE FROM ALL_BEN_IFA_VISIT_HISTORY WHERE benId = :benId AND visitDate = :visitDate") + suspend fun deleteFormResponse(benId: Long, visitDate: String) + + @Query("SELECT * FROM ALL_BEN_IFA_VISIT_HISTORY WHERE isSynced = 0 AND formId = :formId") + suspend fun getUnsyncedForms(formId: String): List + + @Query("UPDATE ALL_BEN_IFA_VISIT_HISTORY SET isSynced = 1, syncedAt = :syncedAt WHERE id = :id") + suspend fun markAsSynced(id: Int, syncedAt: String) + + @Query("SELECT * FROM ALL_BEN_IFA_VISIT_HISTORY WHERE benId = :benId") + suspend fun getSyncedVisitsByRchId(benId: Long): List + + @Query("UPDATE ALL_BEN_IFA_VISIT_HISTORY SET benId = :newBenId WHERE benId = :oldBenId") + suspend fun updateVisitBenId(oldBenId: Long, newBenId: Long) + + @Query("SELECT DISTINCT benId FROM ALL_BEN_IFA_VISIT_HISTORY") + suspend fun getAllUniqueBenIds(): List + + @Query("SELECT formDataJson FROM ALL_BEN_IFA_VISIT_HISTORY WHERE benId = :benId AND formId = :formId") + suspend fun getFormJsonList(benId: Long, formId: String): List + + @Query("SELECT COUNT(*) FROM ALL_BEN_IFA_VISIT_HISTORY WHERE benId = :benId AND strftime('%m-%Y', substr(visitDate, 4, 2) || '-' || substr(visitDate, 7, 4)) = strftime('%m-%Y', 'now')") + suspend fun hasVisitThisMonth(benId: Long): Int +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/CUFYFormResponseDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/CUFYFormResponseDao.kt new file mode 100644 index 000000000..08c01643e --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/CUFYFormResponseDao.kt @@ -0,0 +1,20 @@ +package org.piramalswasthya.sakhi.database.room.dao.dynamicSchemaDao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import org.piramalswasthya.sakhi.model.dynamicEntity.CUFYFormResponseJsonEntity + +@Dao +interface CUFYFormResponseDao { + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertAll(responses: List) + + @Query("SELECT * FROM children_under_five_all_visit WHERE benId = :benId AND visitDate = :visitDate") + suspend fun getResponsesForVisit(benId: Long, visitDate: String): List + + @Query("DELETE FROM children_under_five_all_visit WHERE benId = :benId AND visitDate = :visitDate") + suspend fun deleteResponsesForVisit(benId: Long, visitDate: String) +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/CUFYFormResponseJsonDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/CUFYFormResponseJsonDao.kt new file mode 100644 index 000000000..f03397487 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/CUFYFormResponseJsonDao.kt @@ -0,0 +1,49 @@ +package org.piramalswasthya.sakhi.database.room.dao.dynamicSchemaDao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Update +import androidx.room.Upsert +import org.piramalswasthya.sakhi.model.BottleItem +import org.piramalswasthya.sakhi.model.dynamicEntity.CUFYFormResponseJsonEntity + + +@Dao +interface CUFYFormResponseJsonDao { + + @Upsert + suspend fun insertFormResponse(response: CUFYFormResponseJsonEntity) + + @Update + suspend fun updateFormResponse(response: CUFYFormResponseJsonEntity): Int + + @Query("SELECT * FROM children_under_five_all_visit WHERE id = :id") + suspend fun getFormResponseById(id: Int): CUFYFormResponseJsonEntity? + + @Query("SELECT * FROM children_under_five_all_visit WHERE benId = :benId AND visitDate = :visitDate LIMIT 1") + suspend fun getFormResponse(benId: Long, visitDate: String): CUFYFormResponseJsonEntity? + + @Query("DELETE FROM children_under_five_all_visit WHERE benId = :benId AND visitDate = :visitDate") + suspend fun deleteFormResponse(benId: Long, visitDate: String) + + @Query("SELECT * FROM children_under_five_all_visit WHERE isSynced = 0 AND formId = :formId") + suspend fun getUnsyncedForms(formId: String): List + + @Query("SELECT * FROM children_under_five_all_visit WHERE formId = :formId AND benId = :benId") + suspend fun getFormsDataByFormID(formId: String, benId: Long): List + + @Query("UPDATE children_under_five_all_visit SET isSynced = 1, syncedAt = :syncedAt WHERE id = :id") + suspend fun markAsSynced(id: Int, syncedAt: String) + + @Query("SELECT * FROM children_under_five_all_visit WHERE benId = :benId") + suspend fun getSyncedVisitsByRchId(benId: Long): List + + @Query("UPDATE children_under_five_all_visit SET benId = :newBenId WHERE benId = :oldBenId") + suspend fun updateVisitBenId(oldBenId: Long, newBenId: Long) + + @Query("SELECT formDataJson FROM children_under_five_all_visit WHERE benId = :benId AND formId = :formId") + suspend fun getFormJsonList(benId: Long, formId: String): List + +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/EyeSurgeryFormResponseJsonDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/EyeSurgeryFormResponseJsonDao.kt new file mode 100644 index 000000000..e9e9d4ee8 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/EyeSurgeryFormResponseJsonDao.kt @@ -0,0 +1,57 @@ +package org.piramalswasthya.sakhi.database.room.dao.dynamicSchemaDao + + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Transaction +import org.piramalswasthya.sakhi.model.dynamicEntity.eye_surgery.EyeSurgeryFormResponseJsonEntity + +@Dao +interface EyeSurgeryFormResponseJsonDao { + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertFormResponse(response: EyeSurgeryFormResponseJsonEntity) + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertAll(responses: List) + + @Query("SELECT * FROM ALL_EYE_SURGERY_VISIT_HISTORY WHERE benId = :benId AND visitDate = :visitDate LIMIT 1") + suspend fun getFormResponse(benId: Long, visitDate: String): EyeSurgeryFormResponseJsonEntity? + + @Query("DELETE FROM ALL_EYE_SURGERY_VISIT_HISTORY WHERE benId = :benId AND visitDate = :visitDate") + suspend fun deleteFormResponse(benId: Long, visitDate: String) + + @Query("SELECT * FROM ALL_EYE_SURGERY_VISIT_HISTORY WHERE isSynced = 0 AND formId = :formId") + suspend fun getUnsyncedForms(formId: String): List + + @Query("UPDATE ALL_EYE_SURGERY_VISIT_HISTORY SET isSynced = 1, syncedAt = :syncedAt WHERE id = :id") + suspend fun markAsSynced(id: Int, syncedAt: String) + + @Query("SELECT * FROM ALL_EYE_SURGERY_VISIT_HISTORY WHERE benId = :benId") + suspend fun getSyncedVisitsByRchId(benId: Long): List + + @Query("UPDATE ALL_EYE_SURGERY_VISIT_HISTORY SET benId = :newBenId WHERE benId = :oldBenId") + suspend fun updateVisitBenId(oldBenId: Long, newBenId: Long) + + @Query("SELECT DISTINCT benId FROM ALL_EYE_SURGERY_VISIT_HISTORY") + suspend fun getAllUniqueBenIds(): List + + @Query("SELECT formDataJson FROM ALL_EYE_SURGERY_VISIT_HISTORY WHERE benId = :benId AND formId = :formId") + suspend fun getFormJsonList(benId: Long, formId: String): List + + // NEW: month-keyed helpers for uniqueness/upsert + @Query("SELECT * FROM ALL_EYE_SURGERY_VISIT_HISTORY WHERE benId = :benId AND formId = :formId AND visitMonth = :visitMonth LIMIT 1") + suspend fun getByBenFormMonth(benId: Long, formId: String, visitMonth: String): EyeSurgeryFormResponseJsonEntity? + + @Query("SELECT * FROM ALL_EYE_SURGERY_VISIT_HISTORY WHERE benId = :benId AND formId = :formId ORDER BY visitMonth DESC LIMIT 1") + suspend fun getLatestForBenForm(benId: Long, formId: String): EyeSurgeryFormResponseJsonEntity? + + @Transaction + suspend fun upsertByMonth(entity: EyeSurgeryFormResponseJsonEntity) { + val existing = getByBenFormMonth(entity.benId, entity.formId, entity.visitMonth) + val toSave = existing?.let { entity.copy(id = it.id) } ?: entity + insertFormResponse(toSave) + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/FilariaMDAFormResponseJsonDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/FilariaMDAFormResponseJsonDao.kt new file mode 100644 index 000000000..99752aeeb --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/FilariaMDAFormResponseJsonDao.kt @@ -0,0 +1,68 @@ +package org.piramalswasthya.sakhi.database.room.dao.dynamicSchemaDao + + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Transaction +import org.piramalswasthya.sakhi.model.dynamicEntity.FilariaMDA.FilariaMDAFormResponseJsonEntity + +@Dao +interface FilariaMDAFormResponseJsonDao { + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertFormResponse(response: FilariaMDAFormResponseJsonEntity) + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertAll(responses: List) + + @Query("SELECT * FROM FILARIA_MDA_VISIT_HISTORY WHERE hhId = :hhId AND visitDate = :visitDate LIMIT 1") + suspend fun getFormResponse(hhId: Long, visitDate: String): FilariaMDAFormResponseJsonEntity? + + @Query("DELETE FROM FILARIA_MDA_VISIT_HISTORY WHERE hhId = :hhId AND visitDate = :visitDate") + suspend fun deleteFormResponse(hhId: Long, visitDate: String) + + @Query("SELECT * FROM FILARIA_MDA_VISIT_HISTORY WHERE isSynced = 0 AND formId = :formId") + suspend fun getUnsyncedForms(formId: String): List + + @Query("UPDATE FILARIA_MDA_VISIT_HISTORY SET isSynced = 1, syncedAt = :syncedAt WHERE id = :id") + suspend fun markAsSynced(id: Int, syncedAt: String) + + @Query("SELECT * FROM FILARIA_MDA_VISIT_HISTORY WHERE hhId = :hhId") + suspend fun getSyncedVisitsByRchId(hhId: Long): List + + @Query("UPDATE FILARIA_MDA_VISIT_HISTORY SET hhId = :newHhId WHERE hhId = :oldHhId") + suspend fun updateVisitBenId(newHhId: Long, oldHhId: Long) + + @Query("SELECT formDataJson FROM FILARIA_MDA_VISIT_HISTORY WHERE hhId = :hhId ORDER BY substr(visitDate, 7, 4) || '-' || substr(visitDate, 4, 2) || '-' || substr(visitDate, 1, 2) DESC LIMIT 3") + suspend fun getFormJsonList(hhId: Long): List + @Query("SELECT formDataJson FROM FILARIA_MDA_VISIT_HISTORY WHERE hhId = :hhId ORDER BY createdAt DESC LIMIT 3") + suspend fun getLatest3Json(hhId: Long): List + + @Query("SELECT * FROM FILARIA_MDA_VISIT_HISTORY WHERE hhId = :hhId AND formId = :formId AND visitMonth = :visitMonth LIMIT 1") + suspend fun getByBenFormMonth(hhId: Long, formId: String, visitMonth: String): FilariaMDAFormResponseJsonEntity? + + @Query("SELECT * FROM FILARIA_MDA_VISIT_HISTORY WHERE hhId = :hhId AND formId = :formId ORDER BY visitMonth DESC LIMIT 1") + suspend fun getLatestForBenForm(hhId: Long, formId: String): FilariaMDAFormResponseJsonEntity? + + @Transaction + suspend fun upsertByMonth(entity: FilariaMDAFormResponseJsonEntity) { + val existing = getByBenFormMonth(entity.hhId, entity.formId, entity.visitMonth) + val toSave = existing?.let { entity.copy(id = it.id) } ?: entity + insertFormResponse(toSave) + } + + @Transaction + suspend fun insertOncePerMonth(entity: FilariaMDAFormResponseJsonEntity): Boolean { + val existing = getByBenFormMonth(entity.hhId, entity.formId, entity.visitMonth) + + return if (existing != null) { + false + } else { + insertFormResponse(entity) + true + } + } + +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/FilariaMdaCampaignJsonDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/FilariaMdaCampaignJsonDao.kt new file mode 100644 index 000000000..9524b64d1 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/FilariaMdaCampaignJsonDao.kt @@ -0,0 +1,64 @@ +package org.piramalswasthya.sakhi.database.room.dao.dynamicSchemaDao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Transaction +import org.piramalswasthya.sakhi.model.dynamicEntity.filariaaMdaCampaign.FilariaMDACampaignFormResponseJsonEntity + +@Dao +interface FilariaMdaCampaignJsonDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertCampaignFormResponse(response: FilariaMDACampaignFormResponseJsonEntity) + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertCampaignAll(responses: List) + + @Query("SELECT * FROM FILARIA_MDA_CAMPAIGN_HISTORY WHERE visitDate = :visitDate LIMIT 1") + suspend fun getCampaignFormResponse(visitDate: String): FilariaMDACampaignFormResponseJsonEntity? + + @Query("DELETE FROM FILARIA_MDA_CAMPAIGN_HISTORY WHERE visitDate = :visitDate") + suspend fun deleteCampaignFormResponse( visitDate: String) + + @Query("SELECT * FROM FILARIA_MDA_CAMPAIGN_HISTORY WHERE isSynced = 0 AND formId = :formId") + suspend fun getUnsyncedCampaignForms(formId: String): List + + @Query("UPDATE FILARIA_MDA_CAMPAIGN_HISTORY SET isSynced = 1, syncedAt = :syncedAt WHERE id = :id") + suspend fun markCampaignAsSynced(id: Int, syncedAt: String) + + @Query("SELECT * FROM FILARIA_MDA_CAMPAIGN_HISTORY") + suspend fun getCampaignSyncedVisitsByRchId(): List + + + @Query("SELECT formDataJson FROM FILARIA_MDA_CAMPAIGN_HISTORY ORDER BY substr(visitDate, 7, 4) || '-' || substr(visitDate, 4, 2) || '-' || substr(visitDate, 1, 2) DESC LIMIT 3") + suspend fun getCampaignFormJsonList(): List + @Query("SELECT formDataJson FROM FILARIA_MDA_CAMPAIGN_HISTORY ORDER BY createdAt DESC LIMIT 3") + suspend fun getCampaignLatest3Json(): List + + @Query("SELECT * FROM FILARIA_MDA_CAMPAIGN_HISTORY WHERE formId = :formId AND visitYear = :visitYear LIMIT 1") + suspend fun getCampaignByBenFormYear(formId: String, visitYear: String): FilariaMDACampaignFormResponseJsonEntity? + + @Query("SELECT * FROM FILARIA_MDA_CAMPAIGN_HISTORY WHERE visitDate = :visitDate") + suspend fun getCampaignLatestForBenForm(visitDate: String): FilariaMDACampaignFormResponseJsonEntity? + + @Transaction + suspend fun upsertByYear(entity: FilariaMDACampaignFormResponseJsonEntity) { + val existing = getCampaignByBenFormYear(entity.formId, entity.visitYear) + val toSave = existing?.let { entity.copy(id = it.id) } ?: entity + insertCampaignFormResponse(toSave) + } + + @Transaction + suspend fun insertOncePerYear(entity: FilariaMDACampaignFormResponseJsonEntity): Boolean { + val existing = getCampaignByBenFormYear(entity.formId, entity.visitYear) + + return if (existing != null) { + false + } else { + insertCampaignFormResponse(entity) + true + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/FormResponseANCJsonDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/FormResponseANCJsonDao.kt new file mode 100644 index 000000000..5ac729e04 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/FormResponseANCJsonDao.kt @@ -0,0 +1,46 @@ +package org.piramalswasthya.sakhi.database.room.dao.dynamicSchemaDao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import org.piramalswasthya.sakhi.model.dynamicEntity.anc.ANCFormResponseJsonEntity + +@Dao +interface FormResponseANCJsonDao { + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertFormResponse(response: ANCFormResponseJsonEntity) + + @Query("SELECT * FROM ALL_VISIT_HISTORY_ANC WHERE benId = :benId AND visitDate = :visitDate LIMIT 1") + suspend fun getFormResponse(benId: Long, visitDate: String): ANCFormResponseJsonEntity? + + @Query("DELETE FROM ALL_VISIT_HISTORY_ANC WHERE benId = :benId AND visitDay = :visitDay") + suspend fun deleteFormResponse(benId: Long, visitDay: String) + + @Query("SELECT * FROM ALL_VISIT_HISTORY_ANC WHERE isSynced = 0") + suspend fun getUnsyncedForms(): List + + @Query("UPDATE ALL_VISIT_HISTORY_ANC SET isSynced = 1, syncedAt = :syncedAt WHERE id = :id") + suspend fun markAsSynced(id: Int, syncedAt: String) + + @Query("SELECT * FROM ALL_VISIT_HISTORY_ANC WHERE benId = :benId") + suspend fun getSyncedVisitsByRchId(benId: Long): List + + @Query("UPDATE ALL_VISIT_HISTORY_ANC SET benId = :newBenId WHERE benId = :oldBenId") + suspend fun updateVisitBenId(oldBenId: Long, newBenId: Long) + + + @Query(""" + SELECT * FROM ALL_VISIT_HISTORY_ANC + WHERE benId = :benId + ORDER BY visitDate DESC + """) + suspend fun getVisitsForBen(benId: Long): List + + @Query(""" + SELECT COUNT(*) FROM ALL_VISIT_HISTORY_ANC + WHERE benId = :benId + """) + suspend fun getVisitCount(benId: Long): Int +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/FormResponseDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/FormResponseDao.kt new file mode 100644 index 000000000..fdee587e0 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/FormResponseDao.kt @@ -0,0 +1,17 @@ +package org.piramalswasthya.sakhi.database.room.dao.dynamicSchemaDao + +import androidx.room.* +import org.piramalswasthya.sakhi.model.dynamicEntity.FormResponseJsonEntity + +@Dao +interface FormResponseDao { + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertAll(responses: List) + + @Query("SELECT * FROM all_visit_history WHERE benId = :benId AND visitDay = :visitDay") + suspend fun getResponsesForVisit(benId: Long, visitDay: String): List + + @Query("DELETE FROM all_visit_history WHERE benId = :benId AND visitDay = :visitDay") + suspend fun deleteResponsesForVisit(benId: Long, visitDay: String) +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/FormResponseJsonDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/FormResponseJsonDao.kt new file mode 100644 index 000000000..1dbf6c14b --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/FormResponseJsonDao.kt @@ -0,0 +1,31 @@ +package org.piramalswasthya.sakhi.database.room.dao.dynamicSchemaDao + +import android.util.Log +import androidx.room.* +import org.piramalswasthya.sakhi.model.dynamicEntity.FormResponseJsonEntity + +@Dao +interface FormResponseJsonDao { + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertFormResponse(response: FormResponseJsonEntity) + + @Query("SELECT * FROM all_visit_history WHERE benId = :benId AND visitDay = :visitDay LIMIT 1") + suspend fun getFormResponse(benId: Long, visitDay: String): FormResponseJsonEntity? + + @Query("DELETE FROM all_visit_history WHERE benId = :benId AND visitDay = :visitDay") + suspend fun deleteFormResponse(benId: Long, visitDay: String) + + @Query("SELECT * FROM all_visit_history WHERE isSynced = 0") + suspend fun getUnsyncedForms(): List + + @Query("UPDATE all_visit_history SET isSynced = 1, syncedAt = :syncedAt WHERE id = :id") + suspend fun markAsSynced(id: Int, syncedAt: String) + + @Query("SELECT * FROM all_visit_history WHERE benId = :benId") + suspend fun getSyncedVisitsByRchId(benId: Long): List + + @Query("UPDATE all_visit_history SET benId = :newBenId WHERE benId = :oldBenId") + suspend fun updateVisitBenId(oldBenId: Long, newBenId: Long) + +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/FormResponseJsonDaoHBYC.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/FormResponseJsonDaoHBYC.kt new file mode 100644 index 000000000..273d6c045 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/FormResponseJsonDaoHBYC.kt @@ -0,0 +1,29 @@ +package org.piramalswasthya.sakhi.database.room.dao.dynamicSchemaDao + +import androidx.room.* +import org.piramalswasthya.sakhi.model.dynamicEntity.hbyc.FormResponseJsonEntityHBYC + +@Dao +interface FormResponseJsonDaoHBYC { + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertFormResponse(response: FormResponseJsonEntityHBYC) + + @Query("SELECT * FROM ALL_VISIT_HISTORY_HBYC WHERE benId = :benId AND visitDay = :visitDay LIMIT 1") + suspend fun getFormResponse(benId: Long, visitDay: String): FormResponseJsonEntityHBYC? + + @Query("DELETE FROM ALL_VISIT_HISTORY_HBYC WHERE benId = :benId AND visitDay = :visitDay") + suspend fun deleteFormResponse(benId: Long, visitDay: String) + + @Query("SELECT * FROM ALL_VISIT_HISTORY_HBYC WHERE isSynced = 0") + suspend fun getUnsyncedForms(): List + + @Query("UPDATE ALL_VISIT_HISTORY_HBYC SET isSynced = 1, syncedAt = :syncedAt WHERE id = :id") + suspend fun markAsSynced(id: Int, syncedAt: String) + + @Query("SELECT * FROM ALL_VISIT_HISTORY_HBYC WHERE benId = :benId") + suspend fun getSyncedVisitsByRchId(benId: Long): List + + @Query("UPDATE ALL_VISIT_HISTORY_HBYC SET benId = :newBenId WHERE benId = :oldBenId") + suspend fun updateVisitBenId(oldBenId: Long, newBenId: Long) +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/FormSchemaDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/FormSchemaDao.kt new file mode 100644 index 000000000..f603fbea6 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/FormSchemaDao.kt @@ -0,0 +1,18 @@ +package org.piramalswasthya.sakhi.database.room.dao.dynamicSchemaDao + +import androidx.lifecycle.LiveData +import androidx.room.* +import org.piramalswasthya.sakhi.model.dynamicEntity.FormSchemaEntity + +@Dao +interface FormSchemaDao { + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertOrUpdate(schema: FormSchemaEntity) + + @Query("SELECT * FROM form_schema WHERE formId = :formId LIMIT 1") + fun getFormSchemaLive(formId: String): LiveData + + @Query("SELECT * FROM form_schema WHERE formId = :formId LIMIT 1") + suspend fun getSchema(formId: String): FormSchemaEntity? +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/InfantDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/InfantDao.kt new file mode 100644 index 000000000..0d5ab70c3 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/InfantDao.kt @@ -0,0 +1,18 @@ +package org.piramalswasthya.sakhi.database.room.dao.dynamicSchemaDao + +import androidx.lifecycle.LiveData +import androidx.room.* +import org.piramalswasthya.sakhi.model.dynamicEntity.InfantEntity + +@Dao +interface InfantDao { + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertInfant(infant: InfantEntity) + + @Query("SELECT * FROM infant WHERE rchId = :rchId LIMIT 1") + suspend fun getInfantByRchId(rchId: String): InfantEntity? + + @Query("SELECT * FROM infant") + fun getAllInfants(): LiveData> +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/NCDReferalFormResponseJsonDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/NCDReferalFormResponseJsonDao.kt new file mode 100644 index 000000000..923a1e5d0 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/room/dao/dynamicSchemaDao/NCDReferalFormResponseJsonDao.kt @@ -0,0 +1,109 @@ +package org.piramalswasthya.sakhi.database.room.dao.dynamicSchemaDao + +import androidx.room.Dao +import androidx.room.Update +import androidx.room.Upsert +import androidx.room.Query +import org.piramalswasthya.sakhi.model.dynamicEntity.NCDReferalFormResponseJsonEntity + +@Dao +interface NCDReferalFormResponseJsonDao { + + /* ---------------- INSERT / UPDATE ---------------- */ + @Upsert + suspend fun insertFormResponse( + response: NCDReferalFormResponseJsonEntity + ) + + @Update + suspend fun updateFormResponse( + response: NCDReferalFormResponseJsonEntity + ): Int + + /* ---------------- GET SINGLE ---------------- */ + @Query("SELECT * FROM ncd_referal_all_visit WHERE id = :id") + suspend fun getFormResponseById(id: Int): NCDReferalFormResponseJsonEntity? + + // ✅ Updated to fetch by visitNo + followUpNo + @Query(""" + SELECT * FROM ncd_referal_all_visit + WHERE benId = :benId AND visitNo = :visitNo AND followUpNo = :followUpNo + LIMIT 1 + """) + suspend fun getFormResponse( + benId: Long, + visitNo: Int, + followUpNo: Int + ): NCDReferalFormResponseJsonEntity? + + /* ---------------- DELETE ---------------- */ + @Query(""" + DELETE FROM ncd_referal_all_visit + WHERE benId = :benId AND visitNo = :visitNo AND followUpNo = :followUpNo + """) + suspend fun deleteFormResponse( + benId: Long, + visitNo: Int, + followUpNo: Int + ) + + /* ---------------- SYNC ---------------- */ + @Query(""" + SELECT * FROM ncd_referal_all_visit + WHERE isSynced = 0 AND formId = :formId + """) + suspend fun getUnsyncedForms( + formId: String + ): List + + @Query(""" + UPDATE ncd_referal_all_visit + SET isSynced = 1, syncedAt = :syncedAt + WHERE id = :id + """) + suspend fun markAsSynced( + id: Int, + syncedAt: Long + ) + + /* ---------------- LIST / HISTORY ---------------- */ + @Query(""" + SELECT * FROM ncd_referal_all_visit + WHERE formId = :formId AND benId = :benId + ORDER BY visitNo ASC, followUpNo ASC + """) + suspend fun getAllVisitsByBeneficiary( + benId: Long, + formId: String + ): List + + @Query(""" + SELECT * FROM ncd_referal_all_visit + WHERE benId = :benId + ORDER BY visitNo ASC, followUpNo ASC + """) + suspend fun getSyncedVisitsByRchId( + benId: Long + ): List + + /* ---------------- UTIL ---------------- */ + @Query(""" + UPDATE ncd_referal_all_visit + SET benId = :newBenId + WHERE benId = :oldBenId + """) + suspend fun updateVisitBenId( + oldBenId: Long, + newBenId: Long + ) + + @Query(""" + SELECT formDataJson + FROM ncd_referal_all_visit + WHERE benId = :benId AND formId = :formId + """) + suspend fun getFormJsonList( + benId: Long, + formId: String + ): List +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/shared_preferences/PreferenceDao.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/shared_preferences/PreferenceDao.kt index 24e11e073..7b769cfc7 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/database/shared_preferences/PreferenceDao.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/shared_preferences/PreferenceDao.kt @@ -4,6 +4,7 @@ import android.content.Context import android.net.Uri import com.google.gson.Gson import dagger.hilt.android.qualifiers.ApplicationContext +import org.piramalswasthya.sakhi.BuildConfig import org.piramalswasthya.sakhi.R import org.piramalswasthya.sakhi.helpers.Konstants import org.piramalswasthya.sakhi.helpers.Languages @@ -24,6 +25,13 @@ class PreferenceDao @Inject constructor(@ApplicationContext private val context: editor.apply() } + fun deleteJWTToken() { + val editor = pref.edit() + val prefKey = context.getString(R.string.PREF_primary_JWT_API_KEY) + editor.remove(prefKey) + editor.apply() + } + fun getAmritToken(): String? { val prefKey = context.getString(R.string.PREF_primary_API_KEY) return pref.getString(prefKey, null) @@ -36,6 +44,31 @@ class PreferenceDao @Inject constructor(@ApplicationContext private val context: editor.apply() } + + fun getJWTAmritToken(): String? { + val prefKey = context.getString(R.string.PREF_primary_JWT_API_KEY) + return pref.getString(prefKey, null) + } + + fun registerJWTAmritToken(token: String) { + val editor = pref.edit() + val prefKey = context.getString(R.string.PREF_primary_JWT_API_KEY) + editor.putString(prefKey, token) + editor.apply() + } + + fun registerRefreshToken(token: String) { + val editor = pref.edit() + val prefKey = context.getString(R.string.PREF_primary_REFRESH_TOKEN) + editor.putString(prefKey, token) + editor.apply() + } + + fun getRefreshToken(): String? { + val prefKey = context.getString(R.string.PREF_primary_REFRESH_TOKEN) + return pref.getString(prefKey, null) + } + fun registerLoginCred(userName: String, password: String) { val editor = pref.edit() val prefUserKey = context.getString(R.string.PREF_rem_me_uname) @@ -199,4 +232,11 @@ class PreferenceDao @Inject constructor(@ApplicationContext private val context: set(value) { pref.edit().putLong("last incentive update timestamp ", value).apply() } + + var lastAshaPullTimestamp: Long + get() = pref.getLong("last asha update timestamp ", Konstants.defaultTimeStamp) + set(value) { + pref.edit().putLong("last asha update timestamp ", value).apply() + } + } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/shared_preferences/PreferenceManager.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/shared_preferences/PreferenceManager.kt index 69869a528..2011793d5 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/database/shared_preferences/PreferenceManager.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/shared_preferences/PreferenceManager.kt @@ -2,29 +2,67 @@ package org.piramalswasthya.sakhi.database.shared_preferences import android.content.Context import android.content.SharedPreferences +import androidx.security.crypto.EncryptedSharedPreferences +import androidx.security.crypto.MasterKeys import org.piramalswasthya.sakhi.R +import timber.log.Timber +import java.security.KeyStore class PreferenceManager private constructor() { companion object { + private const val MASTER_KEY_ALIAS = "_androidx_security_master_key_" + @Volatile private var INSTANCE: SharedPreferences? = null internal fun getInstance(context: Context): SharedPreferences { synchronized(this) { - var instance = INSTANCE - if (instance == null) { - instance = context.getSharedPreferences( - context.resources.getString(R.string.PREF_NAME), - Context.MODE_PRIVATE - ) - INSTANCE = instance + INSTANCE?.let { return it } + + val instance: SharedPreferences = try { + createEncryptedPreferences(context) + } catch (e: Exception) { + Timber.e(e, "EncryptedSharedPreferences failed, recovering") + clearCorruptedKeystore(context) + createEncryptedPreferences(context) } - return instance!! + INSTANCE = instance + return instance } } + + private fun createEncryptedPreferences(context: Context): SharedPreferences { + val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC) + return EncryptedSharedPreferences.create( + context.resources.getString(R.string.PREF_NAME), + masterKeyAlias, + context, + EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM + ) + } + + private fun clearCorruptedKeystore(context: Context) { + try { + val keyStore = KeyStore.getInstance("AndroidKeyStore") + keyStore.load(null) + if (keyStore.containsAlias(MASTER_KEY_ALIAS)) { + keyStore.deleteEntry(MASTER_KEY_ALIAS) + } + } catch (e: Exception) { + Timber.e(e, "Failed to delete master key from KeyStore") + } + try { + val prefName = context.resources.getString(R.string.PREF_NAME) + val prefsFile = java.io.File(context.filesDir.parent, "shared_prefs/$prefName.xml") + if (prefsFile.exists()) prefsFile.delete() + } catch (e: Exception) { + Timber.e(e, "Failed to clear preferences file") + } + } } } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/database/shared_preferences/ReferralStatusManager.kt b/app/src/main/java/org/piramalswasthya/sakhi/database/shared_preferences/ReferralStatusManager.kt new file mode 100644 index 000000000..41822bf21 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/database/shared_preferences/ReferralStatusManager.kt @@ -0,0 +1,28 @@ +package org.piramalswasthya.sakhi.database.shared_preferences + +import android.content.Context +import android.content.SharedPreferences +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ReferralStatusManager @Inject constructor( + @ApplicationContext private val context: Context +) { + private val prefs: SharedPreferences by lazy { + context.getSharedPreferences("referral_status", Context.MODE_PRIVATE) + } + + fun isReferred(benId: Long, referralType: String): Boolean { + return prefs.getBoolean("${benId}_${referralType}", false) + } + + fun markAsReferred(benId: Long, referralType: String) { + prefs.edit().putBoolean("${benId}_${referralType}", true).apply() + } + + fun clearReferralStatus(benId: Long, referralType: String) { + prefs.edit().remove("${benId}_${referralType}").apply() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/di/AppModule.kt b/app/src/main/java/org/piramalswasthya/sakhi/di/AppModule.kt index 655e61e74..6ceb3712a 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/di/AppModule.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/di/AppModule.kt @@ -10,15 +10,64 @@ import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor +import org.piramalswasthya.sakhi.BuildConfig import org.piramalswasthya.sakhi.database.room.InAppDb -import org.piramalswasthya.sakhi.database.room.dao.* +import org.piramalswasthya.sakhi.database.room.NcdReferalDao +import org.piramalswasthya.sakhi.database.room.dao.ABHAGenratedDao +import org.piramalswasthya.sakhi.database.room.dao.AdolescentHealthDao +import org.piramalswasthya.sakhi.database.room.dao.AesDao +import org.piramalswasthya.sakhi.database.room.dao.BenDao +import org.piramalswasthya.sakhi.database.room.dao.BeneficiaryIdsAvailDao +import org.piramalswasthya.sakhi.database.room.dao.CbacDao +import org.piramalswasthya.sakhi.database.room.dao.CdrDao +import org.piramalswasthya.sakhi.database.room.dao.ChildRegistrationDao +import org.piramalswasthya.sakhi.database.room.dao.DeliveryOutcomeDao +import org.piramalswasthya.sakhi.database.room.dao.FilariaDao +import org.piramalswasthya.sakhi.database.room.dao.GeneralOpdDao +import org.piramalswasthya.sakhi.database.room.dao.HbncDao +import org.piramalswasthya.sakhi.database.room.dao.HbycDao +import org.piramalswasthya.sakhi.database.room.dao.HouseholdDao +import org.piramalswasthya.sakhi.database.room.dao.ImmunizationDao +import org.piramalswasthya.sakhi.database.room.dao.IncentiveDao +import org.piramalswasthya.sakhi.database.room.dao.InfantRegDao +import org.piramalswasthya.sakhi.database.room.dao.KalaAzarDao +import org.piramalswasthya.sakhi.database.room.dao.LeprosyDao +import org.piramalswasthya.sakhi.database.room.dao.MaaMeetingDao +import org.piramalswasthya.sakhi.database.room.dao.MalariaDao +import org.piramalswasthya.sakhi.database.room.dao.MaternalHealthDao +import org.piramalswasthya.sakhi.database.room.dao.MdsrDao +import org.piramalswasthya.sakhi.database.room.dao.MosquitoNetFormResponseDao +import org.piramalswasthya.sakhi.database.room.dao.PmsmaDao +import org.piramalswasthya.sakhi.database.room.dao.PncDao +import org.piramalswasthya.sakhi.database.room.dao.ProfileDao +import org.piramalswasthya.sakhi.database.room.dao.SaasBahuSammelanDao +import org.piramalswasthya.sakhi.database.room.dao.SyncDao +import org.piramalswasthya.sakhi.database.room.dao.TBDao +import org.piramalswasthya.sakhi.database.room.dao.UwinDao +import org.piramalswasthya.sakhi.database.room.dao.VLFDao +import org.piramalswasthya.sakhi.database.room.dao.dynamicSchemaDao.BenIfaFormResponseJsonDao +import org.piramalswasthya.sakhi.database.room.dao.dynamicSchemaDao.CUFYFormResponseJsonDao +import org.piramalswasthya.sakhi.database.room.dao.dynamicSchemaDao.EyeSurgeryFormResponseJsonDao +import org.piramalswasthya.sakhi.database.room.dao.dynamicSchemaDao.FilariaMDAFormResponseJsonDao +import org.piramalswasthya.sakhi.database.room.dao.dynamicSchemaDao.FilariaMdaCampaignJsonDao +import org.piramalswasthya.sakhi.database.room.dao.dynamicSchemaDao.FormResponseANCJsonDao +import org.piramalswasthya.sakhi.database.room.dao.dynamicSchemaDao.FormResponseJsonDao +import org.piramalswasthya.sakhi.database.room.dao.dynamicSchemaDao.FormResponseJsonDaoHBYC import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao +import org.piramalswasthya.sakhi.helpers.AnalyticsHelper +import org.piramalswasthya.sakhi.helpers.ApiAnalyticsInterceptor +import org.piramalswasthya.sakhi.helpers.TokenExpiryManager import org.piramalswasthya.sakhi.network.AbhaApiService import org.piramalswasthya.sakhi.network.AmritApiService +import org.piramalswasthya.sakhi.network.interceptors.AccountDeactivationInterceptor import org.piramalswasthya.sakhi.network.interceptors.ContentTypeInterceptor +import org.piramalswasthya.sakhi.network.interceptors.LoggingInterceptor +import org.piramalswasthya.sakhi.network.interceptors.TokenAuthenticator import org.piramalswasthya.sakhi.network.interceptors.TokenInsertAbhaInterceptor import org.piramalswasthya.sakhi.network.interceptors.TokenInsertTmcInterceptor +import org.piramalswasthya.sakhi.utils.KeyUtils import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.moshi.MoshiConverterFactory import java.util.concurrent.TimeUnit import javax.inject.Named @@ -28,89 +77,188 @@ import javax.inject.Singleton @InstallIn(SingletonComponent::class) object AppModule { - private const val baseTmcUrl = -// "https://assamtmc.piramalswasthya.org/" -// "http://uatamrit.piramalswasthya.org:8080/" - "https://amritdemo.piramalswasthya.org/" -// "http://192.168.1.109:8080/" - - private const val baseAbhaUrl = "https://healthidsbx.abdm.gov.in/api/" - - private val baseClient = - OkHttpClient.Builder() - .addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) - .addInterceptor(ContentTypeInterceptor()) - .build() + // Named qualifiers + const val AUTH_CLIENT = "authClient" + const val AUTH_API = "authApi" + const val UAT_CLIENT = "uatClient" + const val ABHA_CLIENT = "abhaClient" + // AUTH client (NO interceptors, for refresh calls only) @Singleton @Provides - fun provideMoshiInstance(): Moshi { - return Moshi.Builder() - .add(KotlinJsonAdapterFactory()) + @Named(AUTH_CLIENT) + fun provideAuthClient( + loggingInterceptor: HttpLoggingInterceptor, + accountDeactivationInterceptor: AccountDeactivationInterceptor + ): OkHttpClient { + return OkHttpClient.Builder() + .connectTimeout(60, TimeUnit.SECONDS) + .readTimeout(60, TimeUnit.SECONDS) + .writeTimeout(60, TimeUnit.SECONDS) + .addInterceptor(loggingInterceptor) + .addInterceptor(accountDeactivationInterceptor) .build() } @Singleton @Provides - @Named("uatClient") - fun provideTmcHttpClient(): OkHttpClient { - return baseClient - .newBuilder() - .connectTimeout(60, TimeUnit.SECONDS) - .readTimeout(60, TimeUnit.SECONDS) - .writeTimeout(60, TimeUnit.SECONDS) - .addInterceptor(TokenInsertTmcInterceptor()) + @Named(AUTH_API) + fun provideAuthApiService( + moshi: Moshi, + @Named(AUTH_CLIENT) httpClient: OkHttpClient + ): AmritApiService { + return Retrofit.Builder() + .baseUrl(KeyUtils.baseTMCUrl()) + .addConverterFactory(MoshiConverterFactory.create(moshi)) + .client(httpClient) .build() + .create(AmritApiService::class.java) } + // Main UAT client (with interceptors + authenticator) @Singleton @Provides - @Named("abhaClient") - fun provideAbhaHttpClient(): OkHttpClient { + @Named(UAT_CLIENT) + fun provideUatHttpClient( + apiAnalyticsInterceptor: ApiAnalyticsInterceptor, + tokenInsertTmcInterceptor: TokenInsertTmcInterceptor, + tokenAuthenticator: TokenAuthenticator, + loggingInterceptor: HttpLoggingInterceptor, + accountDeactivationInterceptor: AccountDeactivationInterceptor + ): OkHttpClient { return baseClient .newBuilder() - .connectTimeout(20, TimeUnit.SECONDS) - .readTimeout(20, TimeUnit.SECONDS) - .writeTimeout(20, TimeUnit.SECONDS) - .addInterceptor(TokenInsertAbhaInterceptor()) + .addInterceptor(tokenInsertTmcInterceptor) + .addInterceptor(apiAnalyticsInterceptor) + .addInterceptor(loggingInterceptor) + .addInterceptor(accountDeactivationInterceptor) + .authenticator(tokenAuthenticator) // attach authenticator for 401 handling .build() } - @Singleton @Provides fun provideAmritApiService( moshi: Moshi, - @Named("uatClient") httpClient: OkHttpClient + @Named(UAT_CLIENT) httpClient: OkHttpClient ): AmritApiService { return Retrofit.Builder() + .baseUrl(KeyUtils.baseTMCUrl()) .addConverterFactory(MoshiConverterFactory.create(moshi)) - //.addConverterFactory(GsonConverterFactory.create()) - .baseUrl(baseTmcUrl) .client(httpClient) .build() .create(AmritApiService::class.java) } + @Singleton + @Provides + fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor { + val loggingInterceptor = HttpLoggingInterceptor(LoggingInterceptor()).apply { + level = + //if (BuildConfig.DEBUG) + HttpLoggingInterceptor.Level.BODY + //else + //HttpLoggingInterceptor.Level.NONE + } + return loggingInterceptor + } + + // TokenAuthenticator provider + @Singleton + @Provides + fun provideTokenAuthenticator( + pref: PreferenceDao, + @Named(AUTH_API) authApi: AmritApiService, + tokenExpiryManager: TokenExpiryManager + ): TokenAuthenticator { + return TokenAuthenticator(pref, authApi, tokenExpiryManager) + } + + @Singleton + @Provides + @Named(ABHA_CLIENT) + fun provideAbhaHttpClient(loggingInterceptor: HttpLoggingInterceptor): OkHttpClient { + return baseClient + .newBuilder() + .addInterceptor(TokenInsertAbhaInterceptor()) + .addInterceptor(loggingInterceptor) + .build() + } + @Singleton @Provides fun provideAbhaApiService( moshi: Moshi, - @Named("abhaClient") httpClient: OkHttpClient + @Named(ABHA_CLIENT) httpClient: OkHttpClient ): AbhaApiService { return Retrofit.Builder() .addConverterFactory(MoshiConverterFactory.create(moshi)) //.addConverterFactory(GsonConverterFactory.create()) - .baseUrl(baseAbhaUrl) + .baseUrl(KeyUtils.baseAbhaUrl()) .client(httpClient) .build() .create(AbhaApiService::class.java) } + private val baseClient = + OkHttpClient.Builder() + .connectTimeout(60, TimeUnit.SECONDS) + .readTimeout(60, TimeUnit.SECONDS) + .writeTimeout(60, TimeUnit.SECONDS) + .addInterceptor(ContentTypeInterceptor()) + .build() + + @Singleton + @Provides + fun provideMoshiInstance(): Moshi { + return Moshi.Builder() + .add(KotlinJsonAdapterFactory()) + .build() + } + + // for dynamic data + @Singleton + @Provides + @Named("gsonAmritApi") + fun provideGsonBasedAmritApiService( + @Named("uatClient") httpClient: OkHttpClient + ): AmritApiService { + return Retrofit.Builder() + .addConverterFactory(GsonConverterFactory.create()) + .baseUrl(KeyUtils.baseTMCUrl()) + .client(httpClient) + .build() + .create(AmritApiService::class.java) + } + @Singleton @Provides fun provideRoomDatabase(@ApplicationContext context: Context) = InAppDb.getInstance(context) + @Provides + @Singleton + fun provideAnalyticsHelper( + @ApplicationContext context: Context + ): AnalyticsHelper { + return AnalyticsHelper(context) + } + + @Provides + @Singleton + fun provideApiAnalyticsInterceptor( + @ApplicationContext context: Context + ): ApiAnalyticsInterceptor { + return ApiAnalyticsInterceptor(context) + } + + @Singleton + @Provides + fun provideTokenInsertTmcInterceptor( + preferenceDao: PreferenceDao + ): TokenInsertTmcInterceptor { + return TokenInsertTmcInterceptor(preferenceDao) + } + @Singleton @Provides fun provideHouseholdDao(database: InAppDb): HouseholdDao = database.householdDao @@ -119,6 +267,10 @@ object AppModule { @Provides fun provideBenDao(database: InAppDb): BenDao = database.benDao + @Singleton + @Provides + fun provideAdolescentHealthDao(database: InAppDb): AdolescentHealthDao = database.adolescentHealthDao + @Singleton @Provides @@ -181,6 +333,26 @@ object AppModule { @Provides fun provideIncentiveDao(database: InAppDb): IncentiveDao = database.incentiveDao + @Singleton + @Provides + fun provideMalariaDao(database: InAppDb): MalariaDao = database.malariaDao + + @Singleton + @Provides + fun provideKalaAzarDao(database: InAppDb): KalaAzarDao = database.kalaAzarDao + + @Singleton + @Provides + fun provideFilariaDao(database: InAppDb): FilariaDao = database.filariaDao + + @Singleton + @Provides + fun provideLeprosyDao(database: InAppDb): LeprosyDao = database.leprosyDao + + @Singleton + @Provides + fun provideAESDao(database: InAppDb): AesDao = database.aesDao + @Singleton @Provides fun provideHBNCDao(database: InAppDb): HbncDao = database.hbncDao @@ -189,5 +361,72 @@ object AppModule { @Provides fun provideHBYCDao(database: InAppDb): HbycDao = database.hbycDao + @Singleton + @Provides + fun provideVlfDao(database: InAppDb): VLFDao = database.vlfDao + + @Singleton + @Provides + fun provideAshaProfileDao(database: InAppDb): ProfileDao = database.profileDao + + @Singleton + @Provides + fun provideABHAGenDao(database: InAppDb): ABHAGenratedDao = database.abhaGenratedDao + + @Singleton + @Provides + fun provideFormResponseJsonDao(database: InAppDb): FormResponseJsonDao = database.formResponseJsonDao() + + @Singleton + @Provides + fun provideFormSaasBahuSamelanDao(database: InAppDb): SaasBahuSammelanDao = database.saasBahuSammelanDao + + @Singleton + @Provides + fun formResponseJsonDaoHBYC(database: InAppDb): FormResponseJsonDaoHBYC = database.formResponseJsonDaoHBYC() + + @Singleton + @Provides + fun formResponseJsonDaoANC(database: InAppDb): FormResponseANCJsonDao = database.formResponseJsonDaoANC() + + @Singleton + @Provides + fun provideUwinDao(database: InAppDb) : UwinDao = database.uwinDao + + @Singleton + @Provides + fun provideGenOPDDao(database: InAppDb): GeneralOpdDao = database.generalOpdDao + + + @Singleton + @Provides + fun provideMaaMeetingDao(database: InAppDb): MaaMeetingDao = database.maaMeetingDao + + @Singleton + @Provides + fun provideCUFYFormResponseJsonDao(database: InAppDb): CUFYFormResponseJsonDao = database.CUFYFormResponseJsonDao() + + @Singleton + @Provides + fun provideEyeSurgeryFormResponseJsonDao(database: InAppDb): EyeSurgeryFormResponseJsonDao = database.formResponseJsonDaoEyeSurgery() + + @Singleton + @Provides + fun provideBenIfaFormResponseJsonDao(database: InAppDb): BenIfaFormResponseJsonDao = database.formResponseJsonDaoBenIfa() + @Singleton + @Provides + fun provideMosquitoNetFormResponseDao(database: InAppDb): MosquitoNetFormResponseDao = database.formResponseMosquitoNetJsonDao() + @Singleton + @Provides + fun provideFilariaMDAFormResponseDao(database: InAppDb): FilariaMDAFormResponseJsonDao = database.formResponseFilariaMDAJsonDao() + + @Singleton + @Provides + fun provideFilariaMDACampaignFormResponseDao(database: InAppDb): FilariaMdaCampaignJsonDao = database.formResponseFilariaMDACampaignJsonDao() + + + @Singleton + @Provides + fun provideNcdReferDao(database: InAppDb): NcdReferalDao = database.referalDao } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/helpers/AadhaarValidationUtils.kt b/app/src/main/java/org/piramalswasthya/sakhi/helpers/AadhaarValidationUtils.kt new file mode 100644 index 000000000..1ebbdf0cd --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/helpers/AadhaarValidationUtils.kt @@ -0,0 +1,44 @@ +package org.piramalswasthya.sakhi.helpers + +object AadhaarValidationUtils { + private val multiplicationTable = arrayOf( + intArrayOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9), + intArrayOf(1, 2, 3, 4, 0, 6, 7, 8, 9, 5), + intArrayOf(2, 3, 4, 0, 1, 7, 8, 9, 5, 6), + intArrayOf(3, 4, 0, 1, 2, 8, 9, 5, 6, 7), + intArrayOf(4, 0, 1, 2, 3, 9, 5, 6, 7, 8), + intArrayOf(5, 9, 8, 7, 6, 0, 4, 3, 2, 1), + intArrayOf(6, 5, 9, 8, 7, 1, 0, 4, 3, 2), + intArrayOf(7, 6, 5, 9, 8, 2, 1, 0, 4, 3), + intArrayOf(8, 7, 6, 5, 9, 3, 2, 1, 0, 4), + intArrayOf(9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + ) + + private val permutationTable = arrayOf( + intArrayOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9), + intArrayOf(1, 5, 7, 6, 2, 8, 3, 0, 9, 4), + intArrayOf(5, 8, 0, 3, 7, 9, 6, 1, 4, 2), + intArrayOf(8, 9, 1, 6, 0, 4, 3, 5, 2, 7), + intArrayOf(9, 4, 5, 3, 1, 2, 6, 8, 7, 0), + intArrayOf(4, 2, 8, 6, 5, 7, 3, 9, 0, 1), + intArrayOf(2, 7, 9, 3, 8, 0, 6, 4, 1, 5), + intArrayOf(7, 0, 4, 6, 9, 1, 3, 2, 5, 8) + ) + + private val inverseTable = intArrayOf(0, 4, 3, 2, 1, 5, 6, 7, 8, 9) + + fun isValidAadhaar(aadhaar: String): Boolean { + if (!aadhaar.matches(Regex("^[2-9]{1}[0-9]{11}$"))) return false + + var checksum = 0 + val reversedDigits = aadhaar.reversed().map { it.toString().toInt() } + + reversedDigits.forEachIndexed { i, digit -> + checksum = multiplicationTable[checksum][permutationTable[i % 8][digit]] + } + + return checksum == 0 + } +} + + diff --git a/app/src/main/java/org/piramalswasthya/sakhi/helpers/AccountDeactivationManager.kt b/app/src/main/java/org/piramalswasthya/sakhi/helpers/AccountDeactivationManager.kt new file mode 100644 index 000000000..e1199b37f --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/helpers/AccountDeactivationManager.kt @@ -0,0 +1,28 @@ +package org.piramalswasthya.sakhi.helpers + +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import java.util.concurrent.atomic.AtomicLong +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class AccountDeactivationManager @Inject constructor() { + + companion object { + private const val DIALOG_COOLDOWN_MS = 5 * 60 * 1000L // 5 minutes + } + + private val _deactivationEvent = MutableSharedFlow(extraBufferCapacity = 1) + val deactivationEvent: SharedFlow = _deactivationEvent + + private val lastDialogTimestamp = AtomicLong(0L) + + fun emitIfCooldownPassed(errorMessage: String) { + val now = System.currentTimeMillis() + val last = lastDialogTimestamp.get() + if (now - last >= DIALOG_COOLDOWN_MS && lastDialogTimestamp.compareAndSet(last, now)) { + _deactivationEvent.tryEmit(errorMessage) + } + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/helpers/AnalyticsHelper.kt b/app/src/main/java/org/piramalswasthya/sakhi/helpers/AnalyticsHelper.kt new file mode 100644 index 000000000..b0d9cdded --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/helpers/AnalyticsHelper.kt @@ -0,0 +1,77 @@ +package org.piramalswasthya.sakhi.helpers + +import android.content.Context +import android.os.Bundle +import com.google.firebase.analytics.ktx.analytics +import com.google.firebase.ktx.Firebase +import dagger.hilt.android.qualifiers.ApplicationContext +import org.json.JSONObject +import javax.inject.Inject + +class AnalyticsHelper @Inject constructor( + @ApplicationContext private val context: Context +) { + val firebaseAnalytics = Firebase.analytics + + fun setUserId(userId: String) { + firebaseAnalytics.setUserId(userId) + } + + fun setUserProperty(key: String, value: String) { + firebaseAnalytics.setUserProperty(key, value) + } + + fun logEvent(name: String, params: Bundle? = null) { + firebaseAnalytics.logEvent(name, params) + } + + fun logEvent(eventName: String, json: JSONObject?) { + val bundle = Bundle() + json?.keys()?.forEach { key -> + when (val value = json.get(key)) { + is String -> bundle.putString(key, value) + is Int -> bundle.putInt(key, value) + is Double -> bundle.putDouble(key, value) + is Long -> bundle.putLong(key, value) + is Boolean -> bundle.putBoolean(key, value) + else -> bundle.putString(key, value.toString()) + } + } + firebaseAnalytics.logEvent(eventName, bundle) + + /* + //Usage + fun sendEvent() { + val json = JSONObject().apply { + put("screen", "HomeScreen") + put("button_clicked", "Start") + put("timestamp", System.currentTimeMillis()) + put("user_type", "premium") + } + analyticsHelper.logEvent("custom_button_click", json) + }*/ + } + + fun logCustomTimestampEvent(eventName: String, timestamp: Long) { + firebaseAnalytics.logEvent(eventName, Bundle().apply { + putLong("${eventName}_time", timestamp) + }) + } + + fun logApiCall( + endpoint: String, + durationMs: Long, + isSuccess: Boolean, + responseCode: Int? = null, + errorMessage: String? = null + ) { + val bundle = Bundle().apply { + putString("api_name", endpoint) + putLong("response_time_ms", durationMs) + putString("status", if (isSuccess) "success" else "failure") + responseCode?.let { putInt("response_code", it) } + errorMessage?.let { putString("error_message", it.take(100)) } // limit to 100 chars + } + firebaseAnalytics.logEvent("api_call_event", bundle) + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/helpers/ApiAnalyticsInterceptor.kt b/app/src/main/java/org/piramalswasthya/sakhi/helpers/ApiAnalyticsInterceptor.kt new file mode 100644 index 000000000..cd326efc8 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/helpers/ApiAnalyticsInterceptor.kt @@ -0,0 +1,60 @@ +package org.piramalswasthya.sakhi.helpers + + +import android.content.Context +import android.os.Bundle +import com.google.firebase.analytics.ktx.analytics +import com.google.firebase.ktx.Firebase +import dagger.hilt.android.qualifiers.ApplicationContext +import okhttp3.Interceptor +import okhttp3.Request +import okhttp3.Response +import javax.inject.Inject + +class ApiAnalyticsInterceptor @Inject constructor( + @ApplicationContext private val context: Context +) : Interceptor { + + private val analytics = Firebase.analytics + + override fun intercept(chain: Interceptor.Chain): Response { + val request = chain.request() + val startTime = System.nanoTime() + val response: Response + + try { + response = chain.proceed(request) + } catch (e: Exception) { + val durationMs = (System.nanoTime() - startTime) / 1_000_000 + logToFirebase(request, durationMs, false, null, e.message) + throw e + } + + val durationMs = (System.nanoTime() - startTime) / 1_000_000 + logToFirebase(request, durationMs, response.isSuccessful, response.code, null) + + return response + } + + private fun logToFirebase( + request: Request, + durationMs: Long, + isSuccess: Boolean, + responseCode: Int?, + errorMessage: String? + ) { + val endpoint = request.url.encodedPath + val body = request.body + + val bundle = Bundle().apply { + putString("api_name", endpoint) + putString("api_request_body", body.toString()) + putLong("response_time_ms", durationMs) + putString("status", if (isSuccess) "success" else "failure") + responseCode?.let { putInt("response_code", it) } + errorMessage?.let { putString("error_message", it.take(100)) } + } + + analytics.logEvent("api_call_event", bundle) + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/helpers/CommonUtils.kt b/app/src/main/java/org/piramalswasthya/sakhi/helpers/CommonUtils.kt index 09ac4c970..15bb5217e 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/helpers/CommonUtils.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/helpers/CommonUtils.kt @@ -1,28 +1,43 @@ package org.piramalswasthya.sakhi.helpers +import android.content.Context +import android.content.Context.CONNECTIVITY_SERVICE +import android.net.ConnectivityManager +import android.net.NetworkCapabilities +import android.os.Build import androidx.core.text.isDigitsOnly -import org.piramalswasthya.sakhi.model.AncStatus import org.piramalswasthya.sakhi.model.BenBasicDomain import org.piramalswasthya.sakhi.model.BenBasicDomainForForm +import org.piramalswasthya.sakhi.model.ChildRegDomain import org.piramalswasthya.sakhi.model.BenPncDomain +import org.piramalswasthya.sakhi.model.BenWithAdolescentDomain import org.piramalswasthya.sakhi.model.BenWithAncListDomain +import org.piramalswasthya.sakhi.database.room.SyncState import org.piramalswasthya.sakhi.model.BenWithEcrDomain import org.piramalswasthya.sakhi.model.BenWithEctListDomain +import org.piramalswasthya.sakhi.model.BenWithCbacReferDomain +import org.piramalswasthya.sakhi.model.BenWithPwrDomain import org.piramalswasthya.sakhi.model.BenWithHRNPADomain import org.piramalswasthya.sakhi.model.BenWithHRNPTListDomain import org.piramalswasthya.sakhi.model.BenWithHRPADomain import org.piramalswasthya.sakhi.model.BenWithHRPTListDomain -import org.piramalswasthya.sakhi.model.BenWithPwrDomain +import org.piramalswasthya.sakhi.model.BenWithMalariaConfirmedDomain import org.piramalswasthya.sakhi.model.BenWithTbScreeningDomain import org.piramalswasthya.sakhi.model.BenWithTbSuspectedDomain +import org.piramalswasthya.sakhi.model.GeneralOPEDBeneficiary +import org.piramalswasthya.sakhi.model.ImmunizationDetailsDomain import org.piramalswasthya.sakhi.model.InfantRegDomain import org.piramalswasthya.sakhi.model.PregnantWomenVisitDomain import java.text.SimpleDateFormat +import java.time.LocalDate +import java.time.Period +import java.time.ZoneId import java.util.Calendar import java.util.Date import java.util.Locale import java.util.concurrent.TimeUnit + fun filterBenList(list: List, text: String): List { if (text == "") return list @@ -34,24 +49,114 @@ fun filterBenList(list: List, text: String): List, text: String): List { + if (text == "") + return list + else { + val filterText = text.lowercase() + return list.filter { + filterOPD(it, filterText) + } + } +} + +fun filterBenList( + list: List, + rchPresent: Boolean +) = + if (rchPresent) { + list.filter { + it.rchId.takeIf { it1 -> it1.toString().isDigitsOnly() }?.contains("") ?: false + } + } else { + list + } + + fun filterBenList( + list: List, + filterType: Int + ): List { + return when (filterType) { + 1 -> list.filter { !it.abhaId.isNullOrEmpty() } + + 2 -> list.filter { it.abhaId.isNullOrEmpty() } + + 3 -> list.filter { ben -> + val age = getAgeFromDob(ben.dob) + age >= 30 && ben.isDeathValue == "false" + } + 4 -> list.filter(::isWARA) + else -> list + } + } + +private fun isWARA(ben: BenBasicDomain): Boolean { + val age = getAgeFromDob(ben.dob) + val alive = ben.isDeathValue.equals("false", ignoreCase = true) + val genderOk = ben.gender?.equals("female", ignoreCase = true) == true + val reproOk = (ben.reproductiveStatusId == 1 || ben.reproductiveStatusId == 2) + return genderOk && alive && age in 20..49 && reproOk +} + +fun getAgeFromDob(dob: Long?): Int { + if (dob == null) return 0 + val currentTimeMillis = System.currentTimeMillis() + val diffMillis = currentTimeMillis - dob + return (diffMillis / (1000L * 60 * 60 * 24 * 365)).toInt() +} + +fun filterAdolescentList(list: List) = list + +fun filterAdolescentList(list: List, text: String): List { + if (text == "") + return list + else { + val filterText = text.lowercase() + return list.filter { + filterAdolesent(it, filterText) + } + } +} + + fun filterForBen( ben: BenBasicDomain, filterText: String ) = ben.hhId.toString().lowercase().contains(filterText) || - ben.benId.toString().lowercase().contains(filterText) || - ben.abhaId.toString().lowercase().contains(filterText) || + ben.benId.toString().lowercase().contains(filterText.replace(" ","")) || + ben.abhaId.toString().replace("-","").lowercase().contains(filterText.replace(" ","")) || ben.regDate.lowercase().contains((filterText)) || - ben.age.lowercase().contains(filterText) || + ben.age.lowercase() == filterText.lowercase() || ben.benFullName.lowercase().contains(filterText) || ben.familyHeadName.lowercase().contains(filterText) || ben.benSurname?.lowercase()?.contains(filterText) ?: false || - ben.rchId.takeIf { it.isDigitsOnly() }?.contains(filterText) ?: false || - ben.mobileNo.lowercase().contains(filterText) || - ben.gender.lowercase().contains(filterText) || + ben.rchId.takeIf { it?.isDigitsOnly() == true }?.contains(filterText.replace(" ","")) ?: false || + ben.mobileNo.lowercase().contains(filterText.replace(" ","")) || + ben.gender.lowercase() == filterText.lowercase() || ben.spouseName?.lowercase()?.contains(filterText) == true || ben.fatherName?.lowercase()?.contains(filterText) ?: false +fun filterAdolesent( + ben: BenWithAdolescentDomain, + filterText: String +) = ben.ben.hhId.toString().lowercase().contains(filterText) || + ben.ben.benId.toString().lowercase().contains(filterText.replace(" ","")) || + ben.ben.abhaId.toString().lowercase().contains(filterText) || + ben.ben.regDate.lowercase().contains((filterText)) || + ben.ben.age.lowercase() == filterText.lowercase() || + ben.ben.benFullName.lowercase().contains(filterText) || + ben.ben.familyHeadName.lowercase().contains(filterText) || + ben.ben.benSurname?.lowercase()?.contains(filterText) ?: false || + ben.ben.rchId.takeIf { it?.isDigitsOnly() == true }?.contains(filterText) ?: false || + ben.ben.mobileNo.lowercase().contains(filterText) || + ben.ben.gender.lowercase() == filterText.lowercase() || + ben.ben.spouseName?.lowercase()?.contains(filterText) == true || + ben.ben.fatherName?.lowercase()?.contains(filterText) ?: false + + + fun filterBenFormList( list: List, filterText: String @@ -65,6 +170,12 @@ fun filterBenFormList( ben.weeksOfPregnancy.toString().lowercase().contains(filterText) } + +fun filterOPD( + ben: GeneralOPEDBeneficiary, + filterText: String +) = ben.benName.toString().lowercase().contains(filterText) + fun filterEcTrackingList( list: List, filterText: String @@ -78,7 +189,92 @@ fun filterEcTrackingList( it.ben.spouseName?.lowercase()?.contains(filterText) ?: false || it.ben.benId.toString().lowercase().contains(filterText) || it.ben.mobileNo.lowercase().contains(filterText) || - it.ben.rchId.takeIf { it1 -> it1.isDigitsOnly() }?.contains(filterText) ?: false + it.ben.rchId.takeIf { it1 -> it1?.isDigitsOnly() == true }?.contains(filterText) ?: false + } + +enum class EcFilterType { + NEWEST_FIRST, OLDEST_FIRST, AGE_WISE, SYNCING_FIRST, UNSYNCED_FIRST +} + +fun sortEcRegistrationList(list: List, sort: EcFilterType): List = + when (sort) { + EcFilterType.NEWEST_FIRST -> list.sortedByDescending { it.ecr?.createdDate ?: 0L } + EcFilterType.OLDEST_FIRST -> list.sortedBy { it.ecr?.createdDate ?: Long.MAX_VALUE } + EcFilterType.AGE_WISE -> list.sortedBy { it.ben.dob } + EcFilterType.SYNCING_FIRST -> list.sortedBy { if (it.ecr?.syncState == SyncState.SYNCING) 0 else 1 } + EcFilterType.UNSYNCED_FIRST -> list.sortedBy { if (it.ecr?.syncState == SyncState.UNSYNCED) 0 else 1 } + } + +fun sortEcTrackingList(list: List, sort: EcFilterType): List = + when (sort) { + EcFilterType.NEWEST_FIRST -> list.sortedByDescending { it.ectDate } + EcFilterType.OLDEST_FIRST -> list.sortedBy { it.ectDate } + EcFilterType.AGE_WISE -> list.sortedBy { it.ben.dob } + EcFilterType.SYNCING_FIRST -> list.sortedBy { if (it.savedECTRecords.any { r -> r.syncState == SyncState.SYNCING }) 0 else 1 } + EcFilterType.UNSYNCED_FIRST -> list.sortedBy { if (it.allSynced == SyncState.UNSYNCED) 0 else 1 } + } + +fun sortPncList(list: List, sort: EcFilterType): List = + when (sort) { + EcFilterType.NEWEST_FIRST -> list.sortedByDescending { it.pncDate } + EcFilterType.OLDEST_FIRST -> list.sortedBy { it.pncDate } + EcFilterType.AGE_WISE -> list.sortedBy { it.ben.dob } + EcFilterType.SYNCING_FIRST -> list.sortedBy { if (it.syncState == SyncState.SYNCING) 0 else 1 } + EcFilterType.UNSYNCED_FIRST -> list.sortedBy { if (it.syncState == SyncState.UNSYNCED) 0 else 1 } + } + +fun sortPwrList(list: List, sort: EcFilterType): List = + when (sort) { + EcFilterType.NEWEST_FIRST -> list.sortedByDescending { it.pwr?.createdDate ?: 0L } + EcFilterType.OLDEST_FIRST -> list.sortedBy { it.pwr?.createdDate ?: Long.MAX_VALUE } + EcFilterType.AGE_WISE -> list.sortedBy { it.ben.dob } + EcFilterType.SYNCING_FIRST -> list.sortedBy { if (it.pwr?.syncState == SyncState.SYNCING) 0 else 1 } + EcFilterType.UNSYNCED_FIRST -> list.sortedBy { if (it.pwr?.syncState == SyncState.UNSYNCED) 0 else 1 } + } + +fun sortAncList(list: List, sort: EcFilterType): List = + when (sort) { + EcFilterType.NEWEST_FIRST -> list.sortedByDescending { it.ancDate } + EcFilterType.OLDEST_FIRST -> list.sortedBy { it.ancDate } + EcFilterType.AGE_WISE -> list.sortedBy { it.ben.dob } + EcFilterType.SYNCING_FIRST -> list.sortedBy { if (it.syncState == SyncState.SYNCING) 0 else 1 } + EcFilterType.UNSYNCED_FIRST -> list.sortedBy { if (it.syncState == SyncState.UNSYNCED) 0 else 1 } + } + +fun sortAbortionList(list: List, sort: EcFilterType): List = + when (sort) { + EcFilterType.NEWEST_FIRST -> list.sortedByDescending { it.abortionDate ?: 0L } + EcFilterType.OLDEST_FIRST -> list.sortedBy { it.abortionDate ?: Long.MAX_VALUE } + EcFilterType.AGE_WISE -> list.sortedBy { it.ben.dob } + EcFilterType.SYNCING_FIRST -> list.sortedBy { if (it.syncState == SyncState.SYNCING) 0 else 1 } + EcFilterType.UNSYNCED_FIRST -> list.sortedBy { if (it.syncState == SyncState.UNSYNCED) 0 else 1 } + } + +fun sortChildRegList(list: List, sort: EcFilterType): List = + when (sort) { + EcFilterType.NEWEST_FIRST -> list.sortedByDescending { it.infant.createdDate } + EcFilterType.OLDEST_FIRST -> list.sortedBy { it.infant.createdDate } + EcFilterType.AGE_WISE -> list.sortedBy { it.motherBen.dob } + EcFilterType.SYNCING_FIRST -> list.sortedBy { if (it.infant.syncState == SyncState.SYNCING) 0 else 1 } + EcFilterType.UNSYNCED_FIRST -> list.sortedBy { if (it.infant.syncState == SyncState.UNSYNCED) 0 else 1 } + } + +fun sortInfantRegList(list: List, sort: EcFilterType): List = + when (sort) { + EcFilterType.NEWEST_FIRST -> list.sortedByDescending { it.savedIr?.createdDate ?: 0L } + EcFilterType.OLDEST_FIRST -> list.sortedBy { it.savedIr?.createdDate ?: Long.MAX_VALUE } + EcFilterType.AGE_WISE -> list.sortedBy { it.motherBen.dob } + EcFilterType.SYNCING_FIRST -> list.sortedBy { if (it.syncState == SyncState.SYNCING) 0 else 1 } + EcFilterType.UNSYNCED_FIRST -> list.sortedBy { if (it.syncState == SyncState.UNSYNCED) 0 else 1 } + } + +fun sortHwcList(list: List, sort: EcFilterType): List = + when (sort) { + EcFilterType.NEWEST_FIRST -> list.sortedByDescending { it.referalCac.revisitDate } + EcFilterType.OLDEST_FIRST -> list.sortedBy { it.referalCac.revisitDate } + EcFilterType.AGE_WISE -> list.sortedBy { it.ben.dob } + EcFilterType.SYNCING_FIRST -> list.sortedBy { if (it.referalCac.syncState == SyncState.SYNCING) 0 else 1 } + EcFilterType.UNSYNCED_FIRST -> list.sortedBy { if (it.referalCac.syncState == SyncState.UNSYNCED) 0 else 1 } } fun filterEcRegistrationList( @@ -93,7 +289,7 @@ fun filterEcRegistrationList( it.ben.spouseName?.lowercase()?.contains(filterText) ?: false || it.ben.benId.toString().lowercase().contains(filterText) || it.ben.mobileNo.lowercase().contains(filterText) || - it.ben.rchId.takeIf { it1 -> it1.isDigitsOnly() }?.contains(filterText) ?: false + it.ben.rchId.takeIf { it1 -> it1?.isDigitsOnly() == true }?.contains(filterText) ?: false // || @@ -112,13 +308,48 @@ fun filterPwrRegistrationList( it.ben.spouseName?.lowercase()?.contains(filterText) ?: false || it.ben.benId.toString().lowercase().contains(filterText) || it.ben.mobileNo.lowercase().contains(filterText) || - it.ben.rchId.takeIf { it1 -> it1.isDigitsOnly() }?.contains(filterText) ?: false + it.ben.rchId.takeIf { it1 -> it1?.isDigitsOnly() == true }?.contains(filterText) ?: false + } + +fun filterPwrRegistrationList( + list: List, + rchPresent: Boolean +) = + if (rchPresent) { + list.filter { +// it.ben.rchId.isNotEmpty() + it.ben.rchId.takeIf { it1 -> it1.toString().isDigitsOnly() }?.contains("") ?: false + } + } else { + list } fun filterPwAncList( list: List, filterText: String +): List { + + val query = filterText.lowercase() + return list + .filter { + it.ben.benId.toString().lowercase().contains(query) || + it.ben.age.lowercase().contains(query) || + it.ben.familyHeadName.lowercase().contains(query) || + it.ben.benFullName.lowercase().contains(query) || + it.ben.spouseName?.lowercase()?.contains(query) ?: false || + it.ben.mobileNo.lowercase().contains(query) || + it.lmpString?.lowercase()?.contains(query) ?: false || + it.eddString?.lowercase()?.contains(query) ?: false || + it.weeksOfPregnancy?.lowercase()?.contains(query) ?: false || + it.ben.rchId.takeIf { id -> id?.isDigitsOnly() == true }?.contains(query) ?: false + } + .sortedByDescending { it.ancDate } +} + +fun filterAbortionList( + list: List, + filterText: String ) = list.filter { it.ben.benId.toString().lowercase().contains(filterText) || @@ -126,15 +357,12 @@ fun filterPwAncList( it.ben.familyHeadName.lowercase().contains(filterText) || it.ben.benFullName.lowercase().contains(filterText) || it.ben.spouseName?.lowercase()?.contains(filterText) ?: false || - it.ben.benId.toString().lowercase().contains(filterText) || it.ben.mobileNo.lowercase().contains(filterText) || - it.lmpString?.contains(filterText) ?: false || - it.eddString?.contains(filterText) ?: false || - it.weeksOfPregnancy?.contains(filterText) ?: false || - it.ben.rchId.takeIf { it1 -> it1.isDigitsOnly() }?.contains(filterText) ?: false - +// it.abortionDate?.toString()?.contains(filterText) ?: false || + it.ben.rchId.takeIf { id -> id?.isDigitsOnly() == true }?.contains(filterText) ?: false } + fun filterPncDomainList( list: List, filterText: String @@ -148,8 +376,8 @@ fun filterPncDomainList( it.ben.benId.toString().lowercase().contains(filterText) || it.ben.mobileNo.lowercase().contains(filterText) || it.deliveryDate.contains(filterText) || - it.ben.rchId.takeIf { it1 -> it1.isDigitsOnly() }?.contains(filterText) ?: false - } + it.ben.rchId.takeIf { it1 -> it1?.isDigitsOnly() == true }?.contains(filterText) ?: false + } .sortedByDescending { it.pncDate } fun filterInfantDomainList( list: List, @@ -164,7 +392,7 @@ fun filterInfantDomainList( it.motherBen.benId.toString().lowercase().contains(filterText) || it.motherBen.mobileNo.lowercase().contains(filterText) || it.babyName.contains(filterText) || - it.motherBen.rchId.takeIf { it1 -> it1.isDigitsOnly() } + it.motherBen.rchId.takeIf { it1 -> it1?.isDigitsOnly() == true } ?.contains(filterText) ?: false } @@ -183,7 +411,7 @@ fun filterTbScreeningList( it.ben.benId.toString().lowercase().contains(filterText) || it.ben.mobileNo.lowercase().contains(filterText) || it.ben.gender.lowercase().contains(filterText) || - it.ben.rchId.takeIf { it1 -> it1.isDigitsOnly() }?.contains(filterText) ?: false + it.ben.rchId.takeIf { it1 -> it1?.isDigitsOnly() == true }?.contains(filterText) ?: false } fun filterTbSuspectedList( @@ -200,7 +428,25 @@ fun filterTbSuspectedList( it.ben.benId.toString().lowercase().contains(filterText) || it.ben.mobileNo.lowercase().contains(filterText) || it.ben.gender.lowercase().contains(filterText) || - it.ben.rchId.takeIf { it1 -> it1.isDigitsOnly() }?.contains(filterText) ?: false + it.ben.rchId.takeIf { it1 -> it1?.isDigitsOnly() == true }?.contains(filterText) ?: false + } + + +fun filterMalariaConfirmedList( + list: List, + filterText: String +) = + list.filter { + it.ben.benId.toString().lowercase().contains(filterText) || + it.ben.age.lowercase().contains(filterText) || + it.ben.familyHeadName.lowercase().contains(filterText) || + it.ben.benFullName.lowercase().contains(filterText) || + it.ben.spouseName?.lowercase()?.contains(filterText) ?: false || + it.ben.fatherName?.lowercase()?.contains(filterText) ?: false || + it.ben.benId.toString().lowercase().contains(filterText) || + it.ben.mobileNo.lowercase().contains(filterText) || + it.ben.gender.lowercase().contains(filterText) || + it.ben.rchId.takeIf { it1 -> it1.toString().isDigitsOnly() }?.contains(filterText) ?: false } @JvmName("filterBenList1") @@ -217,11 +463,12 @@ fun filterBenFormList( it.benId.toString().lowercase().contains(filterText) || it.regDate.lowercase().contains((filterText)) || it.age.lowercase().contains(filterText) || - it.rchId.takeIf { it1 -> it1.isDigitsOnly() }?.contains(filterText) ?: false || + it.rchId.takeIf { it1 -> it1?.isDigitsOnly() == true }?.contains(filterText) ?: false || it.benName.lowercase().contains(filterText) || it.familyHeadName.lowercase().contains(filterText) || it.spouseName?.lowercase()?.contains(filterText) == true || it.benSurname?.lowercase()?.contains(filterText) ?: false || + it.dateOfDeath?.lowercase()?.contains(filterText) ?: false || // it.typeOfList.lowercase().contains(filterText) || it.mobileNo.lowercase().contains(filterText) || it.gender.lowercase().contains(filterText) || @@ -231,6 +478,29 @@ fun filterBenFormList( } +@JvmName("filterChildRegList") +fun filterBenFormList( + list: List, + text: String +): List { + if (text == "") + return list + else { + val filterText = text.lowercase() + return list.filter { + it.motherBen.benId.toString().contains(filterText) || + it.motherBen.benName.lowercase().contains(filterText) || + it.motherBen.benSurname?.lowercase()?.contains(filterText) == true || + it.motherBen.familyHeadName.lowercase().contains(filterText) || + it.motherBen.age.lowercase().contains(filterText) || + it.motherBen.mobileNo.lowercase().contains(filterText) || + it.motherBen.rchId?.contains(filterText) == true || + it.childBen?.benName?.lowercase()?.contains(filterText) == true || + it.childBen?.benId?.toString()?.contains(filterText) == true + } + } +} + fun filterBenHRPFormList( list: List, text: String @@ -244,7 +514,7 @@ fun filterBenHRPFormList( it.ben.benId.toString().lowercase().contains(filterText) || it.ben.regDate.lowercase().contains((filterText)) || it.ben.age.lowercase().contains(filterText) || - it.ben.rchId.takeIf { it1 -> it1.isDigitsOnly() } + it.ben.rchId.takeIf { it1 -> it1?.isDigitsOnly() == true } ?.contains(filterText) ?: false || it.ben.benName.lowercase().contains(filterText) || it.ben.familyHeadName.lowercase().contains(filterText) || @@ -271,7 +541,7 @@ fun filterBenHRNPFormList( it.ben.benId.toString().lowercase().contains(filterText) || it.ben.regDate.lowercase().contains((filterText)) || it.ben.age.lowercase().contains(filterText) || - it.ben.rchId.takeIf { it1 -> it1.isDigitsOnly() } + it.ben.rchId.takeIf { it1 -> it1?.isDigitsOnly() == true } ?.contains(filterText) ?: false || it.ben.benName.lowercase().contains(filterText) || it.ben.familyHeadName.lowercase().contains(filterText) || @@ -298,7 +568,7 @@ fun filterBenHRPTFormList( it.ben.benId.toString().lowercase().contains(filterText) || it.ben.regDate.lowercase().contains((filterText)) || it.ben.age.lowercase().contains(filterText) || - it.ben.rchId.takeIf { it1 -> it1.isDigitsOnly() } + it.ben.rchId.takeIf { it1 -> it1?.isDigitsOnly() == true } ?.contains(filterText) ?: false || it.ben.benName.lowercase().contains(filterText) || it.ben.familyHeadName.lowercase().contains(filterText) || @@ -312,6 +582,87 @@ fun filterBenHRPTFormList( } } + +fun filterImmunList( + list: List, + text: String +): List { + val raw = text.trim() + if (raw.isEmpty()) return list + + var filterText = raw.lowercase() + var alt1 = "" + var alt2 = "" + var alt3 = "" + + when { + filterText.contains("5-6") || filterText.contains("5-6 years") -> { + alt1 = "5 years" + filterText = "6 years" + } + + filterText.contains("16-24") || filterText.contains("16-24 months") -> { + alt1 = "1 year" + filterText = "2 years" + } + + filterText.contains("9-12") || filterText.contains("9-12 months") -> { + alt1 = "9 months" + alt2 = "10 months" + alt3 = "11 months" + filterText = "12 months" + } + + filterText.contains("6 weeks") -> { + alt1 = "1 month" + filterText = "2 months" + } + + filterText.contains("birth dose") -> { + alt1 = "1 day" + filterText = "1 month" + + // special case: also match beneficiaries whose age contains "day" + return list.filter { imm -> + val age = imm.ben.age.lowercase() + age.contains("day") || filterForImm(imm, filterText, alt1, alt2, alt3) + } + } + + filterText.contains("10 weeks") -> filterText = "3 months" + filterText.contains("14 weeks") -> filterText = "4 months" + } + + return list.filter { filterForImm(it, filterText, alt1, alt2, alt3) } +} + +fun filterForImm( + imm: ImmunizationDetailsDomain, + filterText: String, + firstVal: String = "", + secondVal: String = "", + thirdVal: String = "" +): Boolean { + val token = filterText.trim().lowercase() + + val age = imm.ben.age?.lowercase() ?: "" + val name = imm.ben.benFullName?.lowercase() ?: "" + val mother = imm.ben.motherName?.lowercase() ?: "" + val mobile = imm.ben.mobileNo ?: "" + + if (age.contains(token)) return true + if (firstVal.isNotEmpty() && age.contains(firstVal)) return true + if (secondVal.isNotEmpty() && age.contains(secondVal)) return true + if (thirdVal.isNotEmpty() && age.contains(thirdVal)) return true + + if (name.contains(token)) return true + if (mother.contains(token)) return true + + if (mobile.contains(token)) return true + + return false +} + fun filterBenHRNPTFormList( list: List, text: String @@ -325,7 +676,7 @@ fun filterBenHRNPTFormList( it.ben.benId.toString().lowercase().contains(filterText) || it.ben.regDate.lowercase().contains((filterText)) || it.ben.age.lowercase().contains(filterText) || - it.ben.rchId.takeIf { it1 -> it1.isDigitsOnly() } + it.ben.rchId.takeIf { it1 -> it1?.isDigitsOnly() == true } ?.contains(filterText) ?: false || it.ben.benName.lowercase().contains(filterText) || it.ben.familyHeadName.lowercase().contains(filterText) || @@ -339,60 +690,13 @@ fun filterBenHRNPTFormList( } } -fun getWeeksOfPregnancy(regLong: Long, lmpLong: Long) = - (TimeUnit.MILLISECONDS.toDays(regLong - lmpLong) / 7).toInt() - -//private fun getAncStatus( -// list: List, lmpDate: Long, visitNumber: Int, benId: Long, at: Long -//): AncStatus { -// -// val currentAnc = list.firstOrNull { it.visitNumber == visitNumber }?.let { return it } -// val lastAnc = -// if (visitNumber > 1) list.firstOrNull { it.visitNumber == visitNumber - 1 } else null -// val lastAncFilledWeek = lastAnc?.filledWeek ?: 0 -// val weeks = getWeeksOfPregnancy(at, lmpDate) -// val weekRange = when (visitNumber) { -// 1 -> Konstants.minAnc1Week//..Konstants.maxAnc1Week -// 2 -> getMinAncFillDate(Konstants.minAnc2Week, lastAncFilledWeek) //..Konstants.maxAnc2Week -// 3 -> getMinAncFillDate(Konstants.minAnc3Week, lastAncFilledWeek) //..Konstants.maxAnc2Week//..Konstants.maxAnc3Week -// 4 -> getMinAncFillDate(Konstants.minAnc4Week, lastAncFilledWeek) //..Konstants.maxAnc2Week//..Konstants.maxAnc4Week -// else -> throw IllegalStateException("visit number not in [1,4]") -// } -// return if (weeks >= weekRange) AncStatus( -// benId, -// visitNumber, -//// if (visitNumber == 1) AncFormState.ALLOW_FILL else { -//// if (lastAnc == null) AncFormState.NO_FILL else AncFormState.ALLOW_FILL -//// }, -// 0 -// ) -// else AncStatus( -// benId, -// visitNumber, -//// AncFormState.NO_FILL, -// 0 -// ) -//} - -fun getMinAncFillDate(minWeek: Int, lastAncFilledWeek: Int) = - if (minWeek - lastAncFilledWeek <= 4) lastAncFilledWeek + 4 else minWeek - -//fun getAncStatusList( -// list: List, lmpDate: Long, benId: Long, at: Long -//) = -// listOf(1, 2, 3, 4).map { -// getAncStatus(list, lmpDate, it, benId, at) -// } - -fun hasPendingAncVisit( - list: List, lmpDate: Long, benId: Long, at: Long -): Boolean { -// val l = getAncStatusList(list, lmpDate, benId, at).map { it.formState } -// Timber.tag("MaternalHealthRepo").d("Emitted : at CommonUtls : $l") -// return l.contains(AncFormState.ALLOW_FILL) - return true +fun getWeeksOfPregnancy(regLong: Long, lmpLong: Long?): Int { + return lmpLong?.let { + (TimeUnit.MILLISECONDS.toDays(regLong - it) / 7).toInt() + } ?: 0 } + fun getTodayMillis() = Calendar.getInstance().setToStartOfTheDay().timeInMillis fun Calendar.setToStartOfTheDay() = apply { @@ -409,6 +713,60 @@ fun Calendar.setToEndOfTheDay() = apply { set(Calendar.MILLISECOND, 0) } +fun getDateFromLong(time: Long) : Date { + val pattern: String = "dd/MM/yyyy HH:mm:ss" + val date = Date(time) +// val format = SimpleDateFormat(pattern, Locale.getDefault()) + return date +} +fun getPatientTypeByAge(dateOfBirth: Date): String { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + + val birthDate = dateOfBirth.toInstant().atZone(ZoneId.systemDefault()).toLocalDate() + val currentDate = LocalDate.now() + val period = Period.between(birthDate, currentDate) + + val years = period.years + val months = period.months + val days = period.days + + when { + years == 0 && months == 0 && days <= 30 -> "new_born_baby" + years == 0 && (months > 0 || days > 30) -> "infant" + years in 1..12 -> "child" + years in 13..18 -> "adolescence" + else -> "adult" + } + } else { + + val current = Calendar.getInstance() + val birth = Calendar.getInstance().apply { time = dateOfBirth } + + var years = current.get(Calendar.YEAR) - birth.get(Calendar.YEAR) + var months = current.get(Calendar.MONTH) - birth.get(Calendar.MONTH) + var days = current.get(Calendar.DAY_OF_MONTH) - birth.get(Calendar.DAY_OF_MONTH) + + + if (days < 0) { + months -= 1 + days += current.getActualMaximum(Calendar.DAY_OF_MONTH) + } + if (months < 0) { + years -= 1 + months += 12 + } + + when { + years == 0 && months <= 1 -> "new_born_baby" + years == 0 && months <= 12 -> "infant" + years in 1..12 -> "child" + years in 13..18 -> "adolescence" + else -> "adult" + } + } +} + + sealed class NetworkResponse(val data: T? = null, val message: String? = null) { @@ -429,3 +787,20 @@ fun getDateString(dateLong: Long?): String? { } +@Suppress("deprecation") +fun isInternetAvailable(activity: Context): Boolean { + val conMgr = activity.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val network = conMgr.activeNetwork + val networkCapabilities = conMgr.getNetworkCapabilities(network) + return networkCapabilities?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + ?: false + } else { + // below API Level 23 + return (conMgr.activeNetworkInfo != null && conMgr.activeNetworkInfo!!.isAvailable + && conMgr.activeNetworkInfo!!.isConnected) + } +} + + diff --git a/app/src/main/java/org/piramalswasthya/sakhi/helpers/CrashEmailSender.kt b/app/src/main/java/org/piramalswasthya/sakhi/helpers/CrashEmailSender.kt new file mode 100644 index 000000000..be3177d31 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/helpers/CrashEmailSender.kt @@ -0,0 +1,41 @@ +package org.piramalswasthya.sakhi.helpers + +import android.content.Context +import android.content.Intent +import android.net.Uri +import androidx.core.content.FileProvider +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao +import java.io.File + +object CrashEmailSender { + private const val TEST_EMAIL = "android.developer@piramalswasthya.org" + lateinit var pref: PreferenceDao + + fun sendCrashReport(context: Context, crashFile: File) { + if (!crashFile.exists()) { + return + } + + val uri: Uri = FileProvider.getUriForFile( + context, + context.packageName + ".provider", + crashFile + ) + + var currentLocation = pref.getLocationRecord()?.village + + var message= "Hi Team, \n Please check crash file for below user\nUsername:${pref.getRememberedUserName()}\nVillage:$currentLocation" + + + val emailIntent = Intent(Intent.ACTION_SEND).apply { + type = "message/rfc822" + putExtra(Intent.EXTRA_EMAIL, arrayOf(TEST_EMAIL)) + putExtra(Intent.EXTRA_SUBJECT, "Crash Report: ${crashFile.name}") + putExtra(Intent.EXTRA_TEXT, message) + putExtra(Intent.EXTRA_STREAM, uri) + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + } + + context.startActivity(Intent.createChooser(emailIntent, "Send crash via email...")) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/helpers/CrashHandler.kt b/app/src/main/java/org/piramalswasthya/sakhi/helpers/CrashHandler.kt new file mode 100644 index 000000000..6b16c5f94 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/helpers/CrashHandler.kt @@ -0,0 +1,52 @@ +package org.piramalswasthya.sakhi.helpers + +import android.content.Context +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao +import java.io.File +import java.io.PrintWriter +import java.io.StringWriter +import java.text.SimpleDateFormat +import java.util.* + + +class CrashHandler(private val context: Context) : Thread.UncaughtExceptionHandler { + private val defaultHandler = Thread.getDefaultUncaughtExceptionHandler() + private val crashDirName = "crashes" + lateinit var pref: PreferenceDao + + + override fun uncaughtException(t: Thread, e: Throwable) { + try { + val crashDir = File(context.filesDir, crashDirName) + if (!crashDir.exists()) crashDir.mkdirs() + + val time = SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.ENGLISH).format(Date()) + val fileName = "crash_${pref.getRememberedUserName()}$time.txt" + val crashFile = File(crashDir, fileName) + + + val sw = StringWriter() + val pw = PrintWriter(sw) + e.printStackTrace(pw) + pw.flush() + + pref =PreferenceDao(context) + +// You can add more context info here: device info, app version, user id, etc. + val header = StringBuilder() + header.append("Crash time: ").append(time).append('\n') + header.append("Thread: ").append(t.name).append('\n') + header.append("Package: ").append(context.packageName).append('\n') + header.appendLine("Username: ${pref.getRememberedUserName()}") + header.append("\n\n") + + + crashFile.writeText(header.toString() + sw.toString()) + } catch (ex: Exception) { +// If writing the crash file also fails, just swallow it and continue to default handler + } finally { +// Let default handler (system) handle the rest (shows crash dialog / kills process) + defaultHandler?.uncaughtException(t, e) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/helpers/DatabaseKeyManager.kt b/app/src/main/java/org/piramalswasthya/sakhi/helpers/DatabaseKeyManager.kt new file mode 100644 index 000000000..ebc8bbd2c --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/helpers/DatabaseKeyManager.kt @@ -0,0 +1,69 @@ +package org.piramalswasthya.sakhi.helpers + +import android.content.Context +import android.content.SharedPreferences +import androidx.security.crypto.EncryptedSharedPreferences +import androidx.security.crypto.MasterKeys +import timber.log.Timber +import java.security.KeyStore +import java.util.UUID + +object DatabaseKeyManager { + + private const val PREF_NAME = "room_db_encryption_pref" + private const val KEY_DB_PASSWORD = "room_db_password" + private const val MASTER_KEY_ALIAS = "_androidx_security_master_key_" + + fun getDatabasePassphrase(context: Context): CharArray { + + val prefs: SharedPreferences = try { + createEncryptedPreferences(context) + } catch (e: Exception) { + Timber.e(e, "DatabaseKeyManager: EncryptedSharedPreferences failed, recovering") + clearCorruptedPrefs(context) + createEncryptedPreferences(context) + } + + var passphrase = prefs.getString(KEY_DB_PASSWORD, null) + + if (passphrase == null) { + passphrase = generateStrongPassword() + prefs.edit().putString(KEY_DB_PASSWORD, passphrase).apply() + } + + return passphrase.toCharArray() + } + + private fun createEncryptedPreferences(context: Context): SharedPreferences { + val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC) + return EncryptedSharedPreferences.create( + PREF_NAME, + masterKeyAlias, + context, + EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM + ) + } + + private fun clearCorruptedPrefs(context: Context) { + try { + val keyStore = KeyStore.getInstance("AndroidKeyStore") + keyStore.load(null) + if (keyStore.containsAlias(MASTER_KEY_ALIAS)) { + keyStore.deleteEntry(MASTER_KEY_ALIAS) + } + } catch (e: Exception) { + Timber.e(e, "DatabaseKeyManager: Failed to delete master key") + } + try { + val prefsFile = java.io.File(context.filesDir.parent, "shared_prefs/$PREF_NAME.xml") + if (prefsFile.exists()) prefsFile.delete() + } catch (e: Exception) { + Timber.e(e, "DatabaseKeyManager: Failed to clear preferences file") + } + } + + private fun generateStrongPassword(): String { + return UUID.randomUUID().toString() + UUID.randomUUID().toString() + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/helpers/ImageUtils.kt b/app/src/main/java/org/piramalswasthya/sakhi/helpers/ImageUtils.kt index 7c7ab1ef0..c788d6563 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/helpers/ImageUtils.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/helpers/ImageUtils.kt @@ -12,27 +12,51 @@ import timber.log.Timber import java.io.File import java.io.FileInputStream import java.io.FileOutputStream -import java.io.OutputStream object ImageUtils { suspend fun saveBenImageFromCameraToStorage( - context: Context, uriString: String, benId: Long - ): String { + context: Context, + uriString: String, + benId: Long + ): String? { return withContext(Dispatchers.IO) { - val targetFile = File(context.filesDir, "${benId}.jpeg").also { it.createNewFile() } - val os: OutputStream = FileOutputStream(targetFile) - context.contentResolver.openInputStream(Uri.parse(uriString))?.use { - os.write(it.readBytes()) - os.flush() - } - Timber.d("Uncompressed target file :->$targetFile ${targetFile.length()}") - val compressedFile = Compressor.compress(context, targetFile) { - quality(80) + try { + val uri = Uri.parse(uriString) + val inputStream = context.contentResolver.openInputStream(uri) + if (inputStream == null) { + Timber.e("ImageUtils: InputStream is null for uri=$uriString") + return@withContext null + } + val targetFile = File(context.filesDir, "${benId}.jpeg") + inputStream.use { input -> + FileOutputStream(targetFile).use { output -> + input.copyTo(output) + output.flush() + } + } + if (!targetFile.exists() || targetFile.length() == 0L) { + Timber.e("ImageUtils: Invalid image file. exists=${targetFile.exists()}, size=${targetFile.length()}") + return@withContext null + } + Timber.d("Uncompressed image: ${targetFile.absolutePath}, size=${targetFile.length()}") + val compressedFile = Compressor.compress(context, targetFile) { + quality(80) + } + + if (compressedFile.exists() && compressedFile.length() > 0) { + compressedFile.copyTo(targetFile, overwrite = true) + } else { + Timber.e("ImageUtils: Compression produced invalid file") + return@withContext null + } + removeAllTemporaryBenImages(context) + Uri.fromFile(targetFile).toString() + + } catch (e: Exception) { + Timber.e(e, "ImageUtils: Failed to save/compress image") + null } - removeAllTemporaryBenImages(context) - compressedFile.renameTo(targetFile) - Uri.fromFile(targetFile).toString() } } diff --git a/app/src/main/java/org/piramalswasthya/sakhi/helpers/InAppUpdateHelper.kt b/app/src/main/java/org/piramalswasthya/sakhi/helpers/InAppUpdateHelper.kt new file mode 100644 index 000000000..97bf78ba1 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/helpers/InAppUpdateHelper.kt @@ -0,0 +1,174 @@ +package org.piramalswasthya.sakhi.helpers + +import android.app.Activity +import android.content.ActivityNotFoundException +import android.content.Intent +import android.content.IntentSender +import android.net.Uri +import android.util.Log +import com.google.android.material.snackbar.Snackbar +import com.google.android.play.core.appupdate.AppUpdateManager +import com.google.android.play.core.appupdate.AppUpdateManagerFactory +import com.google.android.play.core.install.InstallStateUpdatedListener +import com.google.android.play.core.install.model.AppUpdateType +import com.google.android.play.core.install.model.InstallStatus +import com.google.android.play.core.install.model.UpdateAvailability +import com.google.firebase.remoteconfig.FirebaseRemoteConfig +import com.google.firebase.remoteconfig.ktx.remoteConfigSettings +import org.piramalswasthya.sakhi.BuildConfig +import org.piramalswasthya.sakhi.R + +class InAppUpdateHelper( + private val activity: Activity, + private val requestCode: Int = 123 +) { + private val TAG = "InAppUpdateHelper" + private val appUpdateManager: AppUpdateManager = AppUpdateManagerFactory.create(activity) + private val remoteConfig: FirebaseRemoteConfig = FirebaseRemoteConfig.getInstance() + + private val installStateUpdatedListener = InstallStateUpdatedListener { state -> + if (state.installStatus() == InstallStatus.DOWNLOADED) { + showUpdateDownloadedSnackbar() + } + } + + init { + remoteConfig.setConfigSettingsAsync( + remoteConfigSettings { + minimumFetchIntervalInSeconds = 0 + } + ) + remoteConfig.setDefaultsAsync(R.xml.remote_config_defaults) + } + + fun checkForUpdate() { + remoteConfig.fetchAndActivate() + .addOnCompleteListener { task -> + if (!task.isSuccessful) { + Log.w(TAG, "Remote Config fetch failed, using defaults.") + } + + val currentVersionCode = BuildConfig.VERSION_CODE + val latestVersionCode = try { + remoteConfig.getLong("latest_version_code").toInt() + } catch (e: Exception) { + Log.e(TAG, "Invalid latest_version_code in Remote Config", e) + currentVersionCode + } + + val forceUpdate = try { + remoteConfig.getBoolean("force_update") + } catch (e: Exception) { + Log.e(TAG, "Invalid force_update flag in Remote Config", e) + false + } + + if (currentVersionCode < latestVersionCode) { + val updateType = if (forceUpdate) AppUpdateType.IMMEDIATE else AppUpdateType.FLEXIBLE + startUpdate(updateType) + } else { + Log.d(TAG, "App is up-to-date.") + } + } + } + + private fun startUpdate(updateType: Int) { + appUpdateManager.appUpdateInfo.addOnSuccessListener { appUpdateInfo -> + val isUpdateAvailable = appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE + val isAllowed = appUpdateInfo.isUpdateTypeAllowed(updateType) + + if (isUpdateAvailable && isAllowed) { + try { + if (updateType == AppUpdateType.FLEXIBLE) { + appUpdateManager.registerListener(installStateUpdatedListener) + } + + appUpdateManager.startUpdateFlowForResult( + appUpdateInfo, + updateType, + activity, + requestCode + ) + } catch (e: IntentSender.SendIntentException) { + Log.e(TAG, "Update flow failed to launch", e) + showPlayStoreFallback() + } + } else { + Log.d(TAG, "Update not available or type not allowed.") + } + }.addOnFailureListener { + Log.e(TAG, "Failed to get update info", it) + showPlayStoreFallback() + } + } + + fun onActivityResult(requestCode: Int, resultCode: Int): Boolean { + if (requestCode == this.requestCode) { + if (resultCode != Activity.RESULT_OK) { + Log.w(TAG, "Update flow canceled or failed (code: $resultCode)") + showRetrySnackbar() + } + return true + } + return false + } + + fun resumeUpdateIfNeeded() { + appUpdateManager.appUpdateInfo.addOnSuccessListener { appUpdateInfo -> + when { + appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED -> { + showUpdateDownloadedSnackbar() + } + + appUpdateInfo.updateAvailability() == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS -> { + try { + appUpdateManager.startUpdateFlowForResult( + appUpdateInfo, + AppUpdateType.IMMEDIATE, + activity, + requestCode + ) + } catch (e: IntentSender.SendIntentException) { + Log.e(TAG, "Failed to resume update", e) + showPlayStoreFallback() + } + } + } + } + } + + private fun showUpdateDownloadedSnackbar() { + Snackbar.make( + activity.findViewById(android.R.id.content), + "An update is ready to install.", + Snackbar.LENGTH_INDEFINITE + ).setAction("Restart") { + appUpdateManager.completeUpdate() + }.show() + } + + private fun showRetrySnackbar() { + Snackbar.make( + activity.findViewById(android.R.id.content), + "Update was canceled or failed.", + Snackbar.LENGTH_LONG + ).setAction("Try Again") { + checkForUpdate() + }.show() + } + + private fun showPlayStoreFallback() { + try { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=${activity.packageName}")) + if (intent.resolveActivity(activity.packageManager) != null) { + activity.startActivity(intent) + } + } catch (e: ActivityNotFoundException) { + Log.e(TAG, "Play Store not found", e) + } + } + + fun unregisterListener() { + appUpdateManager.unregisterListener(installStateUpdatedListener) + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/helpers/Konstants.kt b/app/src/main/java/org/piramalswasthya/sakhi/helpers/Konstants.kt index e4cb21b2f..6934d2e3f 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/helpers/Konstants.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/helpers/Konstants.kt @@ -10,6 +10,8 @@ object Konstants { //Dev const val devCode = 112 + val nonFollowUpDuration: Long = TimeUnit.DAYS.toMillis(90) + const val tempBenImagePrefix: String = "tmp_image_file" const val tempBenImageSuffix: String = ".jpeg" @@ -26,11 +28,12 @@ object Konstants { const val minAgeForNcd: Int = 30 const val minAgeForReproductiveAge: Int = 15 const val maxAgeForReproductiveAge: Int = 49 - const val maxAgeForInfant: Int = 1 - const val minAgeForChild: Int = 2 - const val maxAgeForChild: Int = 5 - const val minAgeForAdolescent: Int = 6 + const val maxAgeForInfant: Int = 61 + const val minAgeForChild: Int = 92 + const val maxAgeForChild: Int = 456 + const val minAgeForAdolescent: Int = 10 const val maxAgeForAdolescent: Int = 14 + const val maxAgeForAdolescentlist: Int = 19 const val maxAgeForCdr: Int = 14 const val minAgeForGenBen: Int = 15 const val maxAgeForGenBen: Int = 99 @@ -50,11 +53,13 @@ object Konstants { const val maxAnc3Week = 35 const val minAnc4Week = 36 const val maxAnc4Week = 40 + const val maxAncWeek = 42 + const val english = "ENGLISH" const val minWeekToShowDelivered = 23 - const val babyLowWeight: Double = 2.5 + const val babyLowWeight: Double = 2500.0 //PNC-EC cycle @@ -62,4 +67,29 @@ object Konstants { const val defaultTimeStamp = 1577817001000L + + const val childHealth = "CHILD HEALTH" + + const val immunization = "IMMUNIZATION" + + const val maternalHealth = "MATERNAL HEALTH" + + const val ashaIncetiveJSY = "ASHA Incentive under JSY" + + const val familyPlanning = "FAMILY PLANNING" + + const val adolescentHealth = "ADOLESCENT HEALTH" + + const val ashaMonthlyRActivity = "ASHA Monthly Routine Activities" + + const val umbrellaProgrames = "Umbrella Programmes" + + const val ncd = "NCD" + + const val other_Incentive = "OTHER INCENTIVES" + + const val additionalIncentivetoAshaSGovt = "ADDITIONAL INCENTIVE TO ASHA UNDER STATE GOVT. BUDGET" + + const val antaraProg = "ASHA incentive for accompanying the client for Injectable MPA(Antara Prog)administration" + } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/helpers/Languages.kt b/app/src/main/java/org/piramalswasthya/sakhi/helpers/Languages.kt index c3ba4223b..23eb28737 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/helpers/Languages.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/helpers/Languages.kt @@ -2,6 +2,7 @@ package org.piramalswasthya.sakhi.helpers enum class Languages(val symbol: String) { ENGLISH("en"), + ASSAMESE("as"), HINDI("hi"), - ASSAMESE("as") + } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/helpers/MyContextWrapper.kt b/app/src/main/java/org/piramalswasthya/sakhi/helpers/MyContextWrapper.kt index 2bc777df6..70bb5c065 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/helpers/MyContextWrapper.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/helpers/MyContextWrapper.kt @@ -4,6 +4,7 @@ import android.annotation.TargetApi import android.content.Context import android.content.ContextWrapper import android.content.res.Configuration +import android.content.res.Resources import android.os.Build import java.util.* @@ -12,7 +13,8 @@ import java.util.* class MyContextWrapper(base: Context) : ContextWrapper(base) { companion object { fun wrap(context: Context, applicationContext: Context, language: String): ContextWrapper { - val config: Configuration = context.resources.configuration + updateBaseContextLocale(language,context) + /* val config: Configuration = context.resources.configuration val sysLocale: Locale = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { getSystemLocale(config) } else { @@ -30,7 +32,10 @@ class MyContextWrapper(base: Context) : ContextWrapper(base) { .updateConfiguration(config, context.resources.displayMetrics) applicationContext.resources .updateConfiguration(config, applicationContext.resources.displayMetrics) - } + }*/ + + + return MyContextWrapper(context) } @@ -51,6 +56,35 @@ class MyContextWrapper(base: Context) : ContextWrapper(base) { private fun setSystemLocale(config: Configuration, locale: Locale?) { config.setLocale(locale) } + + + + fun updateBaseContextLocale(selectedLang:String,context: Context): Context? { + val locale = Locale(selectedLang) + Locale.setDefault(locale) + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + updateResourcesLocale(context, locale) + } else updateResourcesLocaleLegacy(context, locale) + } + + @TargetApi(Build.VERSION_CODES.N) + fun updateResourcesLocale(context: Context, locale: Locale): Context? { + val configuration = context.resources.configuration + configuration.setLocale(locale) + return context.createConfigurationContext(configuration) + + } + + fun updateResourcesLocaleLegacy(context: Context, locale: Locale): Context? { + val resources: Resources = context.resources + val configuration: Configuration = resources.configuration + configuration.locale = locale + resources.updateConfiguration(configuration, resources.displayMetrics) + return context + } + + + } } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/helpers/RoomDbEncryptionHelper.kt b/app/src/main/java/org/piramalswasthya/sakhi/helpers/RoomDbEncryptionHelper.kt new file mode 100644 index 000000000..dd4d74682 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/helpers/RoomDbEncryptionHelper.kt @@ -0,0 +1,145 @@ +package org.piramalswasthya.sakhi.helpers + +import android.content.Context +import android.util.Log +import net.zetetic.database.sqlcipher.SQLiteDatabase +import java.io.File +import java.io.FileInputStream + +object RoomDbEncryptionHelper { + + private const val TAG = "RoomDbEncryptionHelper" + private val SQLITE_MAGIC = "SQLite format 3\u0000".toByteArray(Charsets.US_ASCII) + + @Volatile + private var libsLoaded = false + + private fun ensureSqlCipherLoaded() { + if (!libsLoaded) { + synchronized(this) { + if (!libsLoaded) { + System.loadLibrary("sqlcipher") + libsLoaded = true + } + } + } + } + + + private fun isPlainSqlite(dbFile: File): Boolean { + if (!dbFile.exists() || dbFile.length() < SQLITE_MAGIC.size) return false + return try { + val header = ByteArray(SQLITE_MAGIC.size) + FileInputStream(dbFile).use { it.read(header) } + header.contentEquals(SQLITE_MAGIC) + } catch (_: Exception) { + false + } + } + + fun encryptIfNeeded( + context: Context, + dbName: String, + passphrase: CharArray + ) { + ensureSqlCipherLoaded() + + val dbFile = context.getDatabasePath(dbName) + if (!dbFile.exists()) return + + if (isPlainSqlite(dbFile)) { + Log.d(TAG, "Plain DB detected via header check. Encrypting...") + encryptPlainDb(dbFile, passphrase) + return + } + + + if (canOpenWithKey(dbFile, passphrase)) { + Log.d(TAG, "DB already encrypted with current key") + return + } + + + Log.w(TAG, "DB encrypted with unknown key or corrupted. Deleting for fresh start.") + dbFile.delete() + File(dbFile.parent, "$dbName-encrypted").let { if (it.exists()) it.delete() } + } + + private fun encryptPlainDb(dbFile: File, passphrase: CharArray) { + val passphraseStr = String(passphrase) + val tempEncrypted = File(dbFile.parent, "${dbFile.name}-encrypted") + if (tempEncrypted.exists()) tempEncrypted.delete() + + SQLiteDatabase.openDatabase( + tempEncrypted.absolutePath, + passphraseStr, + null, + SQLiteDatabase.OPEN_READWRITE or SQLiteDatabase.CREATE_IF_NECESSARY, + null, + null + ).close() + + val plainDb = SQLiteDatabase.openDatabase( + dbFile.absolutePath, + "", + null, + SQLiteDatabase.OPEN_READWRITE, + null, + null + ) + val dbVersion: Int + try { + plainDb.rawQuery("PRAGMA user_version", null).use { cursor -> + dbVersion = if (cursor.moveToFirst()) cursor.getInt(0) else 0 + } + Log.d(TAG, "Plain DB version: $dbVersion") + + plainDb.execSQL( + "ATTACH DATABASE '${tempEncrypted.absolutePath}' AS encrypted KEY '$passphraseStr'" + ) + plainDb.rawQuery("SELECT sqlcipher_export('encrypted')", null) + .use { it.moveToFirst() } + plainDb.execSQL("DETACH DATABASE encrypted") + } finally { + plainDb.close() + } + + dbFile.delete() + tempEncrypted.renameTo(dbFile) + + val encDb = SQLiteDatabase.openDatabase( + dbFile.absolutePath, + passphraseStr, + null, + SQLiteDatabase.OPEN_READWRITE, + null, + null + ) + try { + encDb.execSQL("PRAGMA user_version = $dbVersion") + encDb.execSQL("DROP VIEW IF EXISTS BEN_BASIC_CACHE") + } finally { + encDb.close() + } + Log.d(TAG, "Encryption complete. DB version set to $dbVersion") + } + + private fun canOpenWithKey(dbFile: File, passphrase: CharArray): Boolean { + var db: SQLiteDatabase? = null + return try { + db = SQLiteDatabase.openDatabase( + dbFile.absolutePath, + String(passphrase), + null, + SQLiteDatabase.OPEN_READONLY, + null, + null + ) + true + } catch (_: Exception) { + false + } finally { + db?.close() + } + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/helpers/SyncLogExporter.kt b/app/src/main/java/org/piramalswasthya/sakhi/helpers/SyncLogExporter.kt new file mode 100644 index 000000000..a1a1bf8ef --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/helpers/SyncLogExporter.kt @@ -0,0 +1,58 @@ +package org.piramalswasthya.sakhi.helpers + +import android.content.Context +import android.content.Intent +import androidx.core.content.FileProvider +import java.io.BufferedInputStream +import java.io.BufferedOutputStream +import java.io.File +import java.io.FileInputStream +import java.io.FileOutputStream +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class SyncLogExporter @Inject constructor( + private val fileWriter: SyncLogFileWriter +) { + + /** + * Creates an [Intent.ACTION_SEND] for sharing sync log files as a zip. + * Must be called from a background thread (performs file I/O). + * Returns `null` if no log files exist. + */ + fun createShareIntent(context: Context): Intent? { + val logDir = fileWriter.getLogDirectory() + val logFiles = logDir.listFiles { f -> + f.name.startsWith("sync-log-") && f.name.endsWith(".log") + } + + if (logFiles.isNullOrEmpty()) return null + + val zipFile = File(context.cacheDir, "sync-logs-${System.currentTimeMillis()}.zip") + ZipOutputStream(BufferedOutputStream(FileOutputStream(zipFile))).use { zos -> + for (file in logFiles.sortedBy { it.name }) { + zos.putNextEntry(ZipEntry(file.name)) + BufferedInputStream(FileInputStream(file)).use { bis -> + bis.copyTo(zos) + } + zos.closeEntry() + } + } + + val uri = FileProvider.getUriForFile( + context, + context.packageName + ".provider", + zipFile + ) + + return Intent(Intent.ACTION_SEND).apply { + type = "application/zip" + putExtra(Intent.EXTRA_STREAM, uri) + putExtra(Intent.EXTRA_SUBJECT, "Sync Logs") + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + } + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/helpers/SyncLogFileWriter.kt b/app/src/main/java/org/piramalswasthya/sakhi/helpers/SyncLogFileWriter.kt new file mode 100644 index 000000000..7f02f8c6f --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/helpers/SyncLogFileWriter.kt @@ -0,0 +1,132 @@ +package org.piramalswasthya.sakhi.helpers + +import android.content.Context +import android.os.Handler +import android.os.HandlerThread +import dagger.hilt.android.qualifiers.ApplicationContext +import org.piramalswasthya.sakhi.model.LogLevel +import timber.log.Timber +import java.io.BufferedWriter +import java.io.File +import java.io.FileOutputStream +import java.io.OutputStreamWriter +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class SyncLogFileWriter @Inject constructor( + @ApplicationContext private val context: Context +) { + + companion object { + private const val LOG_DIR = "sync-logs" + private const val FILE_PREFIX = "sync-log-" + private const val FILE_SUFFIX = ".log" + private const val MAX_AGE_DAYS = 3 + private const val MAX_TOTAL_SIZE_BYTES = 100L * 1024 * 1024 // 100 MB + private const val BUFFER_SIZE = 8 * 1024 // 8 KB + } + + private val logDir = File(context.filesDir, LOG_DIR) + + private val dateFormat = object : ThreadLocal() { + override fun initialValue() = SimpleDateFormat("yyyy-MM-dd", Locale.US) + } + private val timestampFormat = object : ThreadLocal() { + override fun initialValue() = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US) + } + + private val handlerThread = HandlerThread("SyncLogWriter").apply { start() } + private val handler = Handler(handlerThread.looper) + + private var currentWriter: BufferedWriter? = null + private var currentDate: String? = null + + fun getLogDirectory(): File = logDir + + fun writeLog(level: LogLevel, tag: String, message: String) { + val now = System.currentTimeMillis() + val levelStr = level.name.padEnd(5) + val ts = timestampFormat.get()!!.format(Date(now)) + val line = "$ts [$levelStr] [$tag] $message\n" + val today = dateFormat.get()!!.format(Date(now)) + + handler.post { + try { + ensureWriter(today) + currentWriter?.write(line) + currentWriter?.flush() + } catch (e: Exception) { + Timber.e(e, "SyncLogFileWriter: failed to write log") + closeWriter() + } + } + } + + fun runRotation() { + handler.post { + try { + rotate() + } catch (e: Exception) { + Timber.e(e, "SyncLogFileWriter: rotation failed") + } + } + } + + fun shutdown() { + handler.post { closeWriter() } + handlerThread.quitSafely() + } + + private fun ensureWriter(today: String) { + if (currentDate == today && currentWriter != null) return + + closeWriter() + logDir.mkdirs() + val file = File(logDir, "$FILE_PREFIX$today$FILE_SUFFIX") + currentWriter = BufferedWriter( + OutputStreamWriter(FileOutputStream(file, true), Charsets.UTF_8), + BUFFER_SIZE + ) + currentDate = today + } + + private fun closeWriter() { + try { + currentWriter?.close() + } catch (_: Exception) { + } + currentWriter = null + currentDate = null + } + + private fun rotate() { + closeWriter() + + if (!logDir.exists()) return + val files = logDir.listFiles { f -> f.name.startsWith(FILE_PREFIX) && f.name.endsWith(FILE_SUFFIX) } + ?: return + + // Pass 1: delete files older than MAX_AGE_DAYS + val cutoff = System.currentTimeMillis() - MAX_AGE_DAYS * 24 * 60 * 60 * 1000L + val remaining = files.filter { file -> + if (file.lastModified() < cutoff) { + file.delete() + false + } else { + true + } + }.sortedBy { it.lastModified() } + + // Pass 2: if total size exceeds limit, delete oldest until under + var totalSize = remaining.sumOf { it.length() } + for (file in remaining) { + if (totalSize <= MAX_TOTAL_SIZE_BYTES) break + totalSize -= file.length() + file.delete() + } + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/helpers/SyncLogManager.kt b/app/src/main/java/org/piramalswasthya/sakhi/helpers/SyncLogManager.kt new file mode 100644 index 000000000..f004ae8a7 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/helpers/SyncLogManager.kt @@ -0,0 +1,68 @@ +package org.piramalswasthya.sakhi.helpers + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import org.piramalswasthya.sakhi.model.LogLevel +import org.piramalswasthya.sakhi.model.SyncLogEntry +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class SyncLogManager @Inject constructor( + private val fileWriter: SyncLogFileWriter +) { + + companion object { + private const val MAX_BUFFER_SIZE = 500 + private const val EMIT_DELAY_MS = 250L + } + + private val scope = CoroutineScope(Dispatchers.Default) + private val buffer = ArrayDeque(MAX_BUFFER_SIZE) + private var nextId = 0L + private var emitJob: Job? = null + private val _logs = MutableStateFlow>(emptyList()) + val logs: StateFlow> = _logs.asStateFlow() + + fun addLog(level: LogLevel, tag: String, message: String) { + synchronized(buffer) { + if (buffer.size >= MAX_BUFFER_SIZE) { + buffer.removeFirst() + } + buffer.addLast( + SyncLogEntry( + id = nextId++, + timestamp = System.currentTimeMillis(), + level = level, + tag = tag, + message = message + ) + ) + scheduleEmit() + } + fileWriter.writeLog(level, tag, message) + } + + private fun scheduleEmit() { + if (emitJob?.isActive == true) return + emitJob = scope.launch { + delay(EMIT_DELAY_MS) + val snapshot = synchronized(buffer) { buffer.toList() } + _logs.value = snapshot + } + } + + fun clearLogs() { + synchronized(buffer) { + emitJob?.cancel() + buffer.clear() + _logs.value = emptyList() + } + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/helpers/SyncLogTree.kt b/app/src/main/java/org/piramalswasthya/sakhi/helpers/SyncLogTree.kt new file mode 100644 index 000000000..a4c8304f9 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/helpers/SyncLogTree.kt @@ -0,0 +1,88 @@ +package org.piramalswasthya.sakhi.helpers + +import android.util.Log +import org.piramalswasthya.sakhi.model.LogLevel +import timber.log.Timber + +/** + * Intercepts Timber logs and routes sync-related entries to [SyncLogManager] + * for display in the Sync Dashboard logs tab. + * + * Extends [Timber.DebugTree] (not [Timber.Tree]) so that auto-generated tags + * from the calling class name are available. Without this, tags are null for + * most Timber calls and the keyword filter drops everything. + */ +class SyncLogTree( + private val syncLogManager: SyncLogManager +) : Timber.DebugTree() { + + companion object { + private val SYNC_TAG_KEYWORDS = listOf( + "Worker", "Sync", "Push", "Pull", "Amrit", "Repo" + ) + private val SYNC_MESSAGE_KEYWORDS = listOf( + "sync", "push", "pull", "worker", "batch", + "beneficiary", "amrit", "upload", "download" + ) + + // Safety net: promote log level when message content indicates an error + // but the caller used the wrong Timber method (e.g. Timber.d for errors). + private val ERROR_CONTENT_PATTERNS = listOf( + "exception", "error occurred", "something bad happened", + "socket timeout", "constraint" + ) + private val FAILURE_CONTENT_PATTERNS = listOf( + "failed", "worker failed" + ) + private val FAILURE_EXCLUSIONS = listOf( + "failed out of", "succeeded", "synced" + ) + } + + override fun isLoggable(tag: String?, priority: Int): Boolean { + return priority >= Log.DEBUG + } + + override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { + if (!isSyncRelated(tag, message)) return + + // Write sync-related logs to logcat via DebugTree.log(). + // In debug builds this duplicates what DebugTree already outputs, but + // in release builds (where DebugTree is not planted) this is the ONLY + // path to logcat — essential for field debugging with adb. + super.log(priority, tag, message, t) + + val level = when { + priority >= Log.ERROR -> LogLevel.ERROR + priority >= Log.WARN -> LogLevel.WARN + priority >= Log.INFO -> LogLevel.INFO + else -> LogLevel.DEBUG + } + + val fullMessage = if (t != null) "$message: ${t.message}" else message + val effectiveLevel = promoteIfError(level, fullMessage, t) + syncLogManager.addLog(effectiveLevel, tag ?: "Sync", fullMessage) + } + + private fun promoteIfError(level: LogLevel, message: String, t: Throwable?): LogLevel { + if (t != null && level.ordinal < LogLevel.ERROR.ordinal) return LogLevel.ERROR + if (level.ordinal >= LogLevel.WARN.ordinal) return level + + val lower = message.lowercase() + if (ERROR_CONTENT_PATTERNS.any { lower.contains(it) }) return LogLevel.ERROR + if (FAILURE_CONTENT_PATTERNS.any { lower.contains(it) }) { + if (FAILURE_EXCLUSIONS.none { lower.contains(it) }) return LogLevel.WARN + } + return level + } + + private fun isSyncRelated(tag: String?, message: String): Boolean { + if (tag != null && SYNC_TAG_KEYWORDS.any { tag.contains(it, ignoreCase = true) }) { + return true + } + if (SYNC_MESSAGE_KEYWORDS.any { message.contains(it, ignoreCase = true) }) { + return true + } + return false + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/helpers/TapjackingProtectionHelper.kt b/app/src/main/java/org/piramalswasthya/sakhi/helpers/TapjackingProtectionHelper.kt new file mode 100644 index 000000000..7654ccfc1 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/helpers/TapjackingProtectionHelper.kt @@ -0,0 +1,68 @@ +package org.piramalswasthya.sakhi.helpers +import android.app.Activity +import org.piramalswasthya.sakhi.BuildConfig +import android.provider.Settings +import android.view.MotionEvent +import android.view.View +import android.view.WindowManager +import android.widget.Toast + +object TapjackingProtectionHelper { + + /** + * Call this in Activity.onCreate() BEFORE setContentView() + */ + fun applyWindowSecurity(activity: Activity) { + // Skip screenshot prevention in debug builds to allow demos/screen recording + if (BuildConfig.DEBUG) return + + // Prevent screenshot + some overlays + activity.window.setFlags( + WindowManager.LayoutParams.FLAG_SECURE, + WindowManager.LayoutParams.FLAG_SECURE + ) + } + + /** + * Call this in Activity.onCreate() AFTER setContentView() + * to block touch events when UI is obscured. + */ + fun enableTouchFiltering(activity: Activity) { + val rootView = activity.findViewById(android.R.id.content) + rootView.filterTouchesWhenObscured = true + + // OPTIONAL — warn the user if overlays are active + if (Settings.canDrawOverlays(activity)) { + Toast.makeText( + activity, + "Screen overlay detected. Some actions may be restricted.", + Toast.LENGTH_LONG + ).show() + } + } + + /** + * Call this in Activity.onFilterTouchEventForSecurity() override + */ + fun handleFilteredTouch(activity: Activity, event: MotionEvent?): Boolean { + if ((event?.flags ?: 0) and MotionEvent.FLAG_WINDOW_IS_OBSCURED != 0) { + Toast.makeText( + activity, + "Screen overlay detected. Action blocked.", + Toast.LENGTH_SHORT + ).show() + return false + } + return true + } + + fun isTouchAllowed(activity: Activity, event: MotionEvent?): Boolean { + return if ((event?.flags ?: 0) and MotionEvent.FLAG_WINDOW_IS_OBSCURED != 0) { + Toast.makeText(activity, "Screen overlay detected. Action blocked.", Toast.LENGTH_SHORT).show() + false + } else { + true + } + } + +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/helpers/TokenExpiryManager.kt b/app/src/main/java/org/piramalswasthya/sakhi/helpers/TokenExpiryManager.kt new file mode 100644 index 000000000..0ab8b8886 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/helpers/TokenExpiryManager.kt @@ -0,0 +1,35 @@ +package org.piramalswasthya.sakhi.helpers + +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import timber.log.Timber +import java.util.concurrent.atomic.AtomicInteger +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class TokenExpiryManager @Inject constructor() { + + companion object { + private const val MAX_CONSECUTIVE_FAILURES = 3 + } + + private val consecutiveFailures = AtomicInteger(0) + + private val _forceLogoutEvent = MutableSharedFlow(extraBufferCapacity = 1) + val forceLogoutEvent: SharedFlow = _forceLogoutEvent + + fun onRefreshFailed() { + val count = consecutiveFailures.incrementAndGet() + Timber.w("Token refresh failed. Consecutive failures: $count") + if (count >= MAX_CONSECUTIVE_FAILURES) { + Timber.w("Max consecutive refresh failures reached ($count). Forcing logout.") + consecutiveFailures.set(0) + _forceLogoutEvent.tryEmit(Unit) + } + } + + fun onRefreshSuccess() { + consecutiveFailures.set(0) + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/helpers/addharEditText/BlockEditText.kt b/app/src/main/java/org/piramalswasthya/sakhi/helpers/addharEditText/BlockEditText.kt new file mode 100644 index 000000000..96c5006e3 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/helpers/addharEditText/BlockEditText.kt @@ -0,0 +1,762 @@ +package org.piramalswasthya.sakhi.helpers.addharEditText + +import android.content.Context +import android.content.res.ColorStateList +import android.graphics.Typeface +import android.graphics.drawable.Drawable +import android.os.Build +import android.text.Editable +import android.text.InputFilter +import android.text.InputType +import android.text.Spanned +import android.text.TextUtils +import android.text.TextWatcher +import android.util.AttributeSet +import android.util.Log +import android.util.SparseArray +import android.util.SparseIntArray +import android.view.ActionMode +import android.view.Gravity +import android.view.KeyEvent +import android.view.Menu +import android.view.MenuItem +import android.view.MotionEvent +import android.view.View.OnFocusChangeListener +import android.view.View.OnKeyListener +import android.view.ViewGroup +import android.widget.EditText +import android.widget.FrameLayout +import android.widget.LinearLayout +import android.widget.TextView +import androidx.annotation.RequiresApi +import androidx.core.content.ContextCompat +import androidx.core.view.ViewCompat +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.utils.HelperUtil.convertDpToPixel + + +class BlockEditText : FrameLayout { + private var noOfBlock = 1 + private var linearLayout: LinearLayout? = null + private var blockLinearLayout: LinearLayout? = null + private val lengths = SparseIntArray() + private var lengthUsed: SparseIntArray? = null + private var defaultLength = 1 + private var hintTextView: TextView? = null + private var inputType = InputType.TYPE_CLASS_TEXT + private var inputTextColor: ColorStateList? = null + private var typeface: Typeface? = null + private var watcher: TextWatcher? = null + private var callback: ActionMode.Callback? = null + private val editTexts = SparseArray() + private var separator: Char? = null + private var separatorTextAppearance = androidx.appcompat.R.style.Base_TextAppearance_AppCompat_Medium + private var separatorPadding = 16 + private var separatorTextSize = 0f + private var textSize = 0f + private var hintTextSize = 0f + private var textAppearance = androidx.appcompat.R.style.Base_TextAppearance_AppCompat_Medium + private var hintTextAppearance = androidx.appcompat.R.style.Base_TextAppearance_AppCompat_Medium + private var editTextBackground: Drawable? = null + private var hint: String? = null + private var hintColorDefault: ColorStateList? = null + private var hintColorFocus: ColorStateList? = null + private var shiftPosition = true + private var cardIconSize = convertDpToPixel(48f,context).toInt() + private var isEnabled = true + private var isShowCardIcon = false + private var editTextStyle = 0 + + private var isPassword = false + private var showPasswordToggle:Boolean = false + private var isToggleAdded:Boolean = false + private var passwordToggleSize = resources.getDimensionPixelSize(R.dimen.password_toggle_size) + private var passwordToggleColor = resources.getColor(android.R.color.black) + + + constructor(context: Context) : super(context) { + init(context, null) + isSaveEnabled = true + } + + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + isSaveEnabled = true + } + + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { + init(context, attrs) + isSaveEnabled = true + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) { + init(context, attrs) + isSaveEnabled = true + } + + private fun init(context: Context, attrs: AttributeSet?) { + lengthUsed = lengths + linearLayout = LinearLayout(getContext()) + linearLayout!!.orientation = LinearLayout.VERTICAL + linearLayout!!.layoutParams = createWidthMatchParentLayoutParams() + blockLinearLayout = LinearLayout(getContext()) + blockLinearLayout!!.orientation = LinearLayout.HORIZONTAL + blockLinearLayout!!.layoutParams = createWidthMatchParentLayoutParams() + linearLayout!!.addView(blockLinearLayout) + addView(linearLayout) + val a = context.obtainStyledAttributes(attrs, R.styleable.BlockEditText) + editTextBackground = a.getDrawable(R.styleable.BlockEditText_editTextBackground) + hint = a.getString(R.styleable.BlockEditText_hint) + setHint(hint) + + var tempStr = a.getString(R.styleable.BlockEditText_separatorCharacter) + if (!TextUtils.isEmpty(tempStr)) { + separator = tempStr!![0] + } + noOfBlock = a.getInt( + R.styleable.BlockEditText_numberOfBlock, + noOfBlock + ) + defaultLength = a.getInt( + R.styleable.BlockEditText_defaultLength, + defaultLength + ) + textAppearance = a.getResourceId( + R.styleable.BlockEditText_hintTextAppearance, + textAppearance + ) + hintTextAppearance = a.getResourceId( + R.styleable.BlockEditText_hintTextAppearance, + hintTextAppearance + ) + separatorTextAppearance = a.getResourceId( + R.styleable.BlockEditText_separatorTextAppearance, + separatorTextAppearance + ) + textSize = a.getDimension( + R.styleable.BlockEditText_textSize, + textSize + ) + hintTextSize = a.getDimension( + R.styleable.BlockEditText_hintTextSize, + hintTextSize + ) + separatorTextSize = a.getDimension( + R.styleable.BlockEditText_separatorTextSize, + separatorTextSize + ) + cardIconSize = a.getDimensionPixelOffset( + R.styleable.BlockEditText_cardIconSize, + cardIconSize + ) + separatorPadding = a.getDimensionPixelOffset( + R.styleable.BlockEditText_hintTextSize, + separatorPadding + ) + inputType = a.getInt( + R.styleable.BlockEditText_inputType, + inputType + ) + shiftPosition = a.getBoolean( + R.styleable.BlockEditText_showCardIcon, + true + ) + isShowCardIcon = a.getBoolean( + R.styleable.BlockEditText_showCardIcon, + true + ) + editTextStyle = a.getResourceId( + R.styleable.BlockEditText_style, -1 + ) + + setHintTextAppearance(hintTextAppearance) + shiftPosition = a.getBoolean( + R.styleable.BlockEditText_shiftPosition, + true + ) + initLayout() + tempStr = a.getString(R.styleable.BlockEditText_text) + if (tempStr != null) text = tempStr + + + val filename = a.getString(R.styleable.BlockEditText_typefaceFromAsset); + Log.e("Filename", filename ?: "") + if (filename != null) + setTypefaceFromAsset(filename) + + val color = a.getColorStateList( + R.styleable.BlockEditText_inputTextColor + ) + if (color != null) + setInputTextColor(color) + val hintTextColor = a.getColorStateList( + R.styleable.BlockEditText_hintTextColor + ) + if (hintTextColor != null) + setHintTextColor(hintTextColor.defaultColor) + a.recycle() + } + + private fun setPasswordType(editText: EditText) { + if (isPassword) { + editText.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_PASSWORD + } else { + editText.inputType = InputType.TYPE_CLASS_NUMBER + } + } + + private fun containsFlag(flagSet: Int, flag: Int): Boolean { + return flagSet or flag == flagSet + } + + private fun createWidthMatchParentLayoutParams(): ViewGroup.LayoutParams { + return LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT + ) + } + + private fun initLayout() { + blockLinearLayout!!.removeAllViews() + var i = 0 + var text: String = text.toString() + while (i < noOfBlock) { + val length = getLength(i) + val editText: EditText? + if (editTexts[i] == null) { + editText = if (editTextStyle == -1) { + EditText(context) + } else { + EditText(context, null, editTextStyle) + } + editText.addTextChangedListener(createTextChangeListener(editText, i)) + editTexts.put(i, editText) + editText.onFocusChangeListener = OnFocusChangeListener { _, hasFocus -> + if (hintTextView != null) { + val hintColorFocus: ColorStateList = ColorStateList.valueOf( + editText.textColors.getColorForState( + intArrayOf(android.R.attr.state_focused), + ContextCompat.getColor(context, R.color.seed) + )) + updateHintTextColor(hasFocus); + } + } + editText.setTextAppearance(textAppearance) + setTextSize(editText, textSize) + editText.setOnKeyListener(createKeyListener(editText, i)) + } else { + editText = editTexts[i] + } + editText!!.inputType = inputType + editText.typeface = typeface + if (inputTextColor != null) + editText.setTextColor(inputTextColor); + val filters = arrayOfNulls(1) + filters[0] = LengthFilter(editText, i) + editText.filters = filters + val params = LinearLayout.LayoutParams( + 0, LinearLayout.LayoutParams.WRAP_CONTENT, length.toFloat() + ) + setPasswordType(editText) + editText.layoutParams = params + editText.gravity = Gravity.CENTER + blockLinearLayout!!.addView(editText) + if (editTextBackground != null) setEdiTextBackground(editText, editTextBackground!!) + if (i + 1 < noOfBlock && separator != null) { + val textView = TextView(context) + textView.text = separator.toString() + textView.typeface = typeface + val textViewParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT + ) + textViewParams.gravity = Gravity.CENTER + textView.layoutParams = textViewParams + textView.setPadding(separatorPadding, 0, separatorPadding, 0) + textView.setTextAppearance(separatorTextAppearance) + if (separatorTextSize > 0) textView.textSize = separatorTextSize + blockLinearLayout!!.addView(textView) + } + editText.text = null + setEditTextEnable(editText, i) + i++ + } + while (i < editTexts.size()) { + editTexts.remove(i) + } + this.text = text + } + + private fun updateHintTextColor(hasFocus: Boolean) { + hintTextView!!.setHintTextColor( + if (hasFocus) hintColorFocus else hintColorDefault + ) + } + + private fun setEditTextEnable(editText: EditText?, i: Int) { + if (shiftPosition && i > 0) editText!!.isEnabled = false else editText!!.isEnabled = isEnabled + } + + fun getText(editText: EditText): Editable { + return editText.text + } + + private fun createKeyListener(editText: EditText, i: Int): OnKeyListener { + return OnKeyListener { v, keyCode, event -> + if (keyCode == KeyEvent.KEYCODE_DEL) { + if (editText.selectionStart == 0 && editText.selectionEnd == 0) { + val prevEditText = editTexts[i - 1] + if (prevEditText != null) { + prevEditText.requestFocus() + if (editText.length() > 0) { + prevEditText.editableText.delete(getText(prevEditText).length - 1, prevEditText.text!!.length) + prevEditText.setSelection(prevEditText.text!!.length - 1) + } + return@OnKeyListener true + } + } + } + false + } + } + + private fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { + if (watcher != null) watcher!!.beforeTextChanged(s, start, count, after) + } + + private fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { + if (watcher != null) watcher!!.onTextChanged(s, start, before, count) + } + + private fun afterTextChanged(s: Editable) { + if (watcher != null) watcher!!.afterTextChanged(s) + } + + fun setTextChangedListener(watcher: TextWatcher?) { + this.watcher = watcher + } + + fun setCustomInsertionActionModeCallback(callback: ActionMode.Callback?) { + this.callback = callback + for (i in 0 until editTexts.size()) { + val editText = editTexts[i] + setCustomInsertionActionModeCallback(editText, callback) + } + } + + private fun setCustomInsertionActionModeCallback(editText: EditText?, callback: ActionMode.Callback?) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + editText!!.customInsertionActionModeCallback = callback + } else editText!!.customSelectionActionModeCallback = callback + } + + fun setSeparatorCharacter(separator: Char?) { + this.separator = separator + initLayout() + } + + var text: CharSequence? + get() { + val builder = StringBuilder() + for (i in 0 until editTexts.size()) { + val editText = editTexts[i] + builder.append(editText!!.text) + } + return builder.toString() + } + set(sequence) { + var i = 0 + while (i < editTexts.size()) { + val editText = editTexts[i] + editText?.text = null + i++ + } + if (sequence != null) { + val text = sequence.toString() + val editText = editTexts[0] + editText!!.editableText.insert(0, text) + } + } + val maxLength: Int + get() { + var length = 0 + for (i in 0 until editTexts.size()) { + length += getLength(i) + } + return length + } + + private fun getLength(i: Int): Int { + return lengthUsed!![i, defaultLength] + } + + fun setTextSize(textSize: Float) { + this.textSize = textSize + for (i in 0 until editTexts.size()) { + val editText = editTexts[i] + setTextSize(editText, textSize) + } + } + + private fun setTextSize(editText: EditText?, textSize: Float) { + if (textSize > 0) editText!!.textSize = textSize + } + + fun setTextAppearance(textAppearance: Int) { + this.textAppearance = textAppearance + for (i in 0 until editTexts.size()) { + val editText = editTexts[i] + editText!!.setTextAppearance(textAppearance) + } + } + + fun setInputType(type: Int) { + inputType = type + for (i in 0 until editTexts.size()) { + val editText = editTexts[i] + editText!!.inputType = type + } + } + + + fun setInputTextColor(color: ColorStateList) { + inputTextColor = color + + hintColorFocus = ColorStateList.valueOf(color.getColorForState( + intArrayOf(android.R.attr.state_focused), + ContextCompat.getColor(context, R.color.seed))) + for (i in 0 until editTexts.size()) { + val editText = editTexts[i] + editText!!.setTextColor(inputTextColor); + + } + } + + + fun setInputTextColor(color: Int) { + + + for (i in 0 until editTexts.size()) { + val editText = editTexts[i] + editText!!.setTextColor(color); + inputTextColor = editText.textColors + } + } + + fun setTypefaceFromAsset(filename: String) { + setTypeface(Typeface.createFromAsset(context.resources.assets, filename)); + } + + fun setTypeface(typeface: Typeface) { + Log.e("Typeface", typeface.toString()) + this.typeface = typeface + if (hintTextView != null) + hintTextView!!.typeface = typeface + + for (i in 0 until editTexts.size()) { + val editText = editTexts[i] + editText!!.typeface = typeface + } + } + + fun setNumberOfBlock(block: Int) { + noOfBlock = block + initLayout() + } + + fun setDefaultLength(defaultLength: Int) { + this.defaultLength = defaultLength + initLayout() + } + + fun setEdiTextBackground(drawable: Drawable?) { + for (i in 0 until editTexts.size()) { + val editText = editTexts[i] + setEdiTextBackground(editText, drawable) + } + } + + private fun setEdiTextBackground(editText: EditText?, drawable: Drawable?) { + ViewCompat.setBackground(editText!!, drawable?.constantState?.newDrawable()) + } + + fun setLengthAt(index: Int, length: Int) { + lengths.put(index, length) + initLayout() + } + + fun setHint(hint: String?) { + if (hintTextView == null) { + hintTextView = TextView(context) + hintTextView!!.typeface = typeface + hintTextView!!.setPadding(16, 0, 16, 0) + setHintTextAppearance(hintTextAppearance) + setHintTextSize(hintTextAppearance.toFloat()) + linearLayout!!.addView(hintTextView, 0) + hintColorDefault = hintTextView!!.hintTextColors + } + hintTextView!!.visibility = if (hint == null) GONE else VISIBLE + hintTextView!!.hint = hint + } + + fun setHintTextSize(textSize: Float) { + hintTextSize = textSize + if (hintTextView != null && textSize > 0) { + hintTextView!!.textSize = textSize + } + } + + fun setHintTextAppearance(textAppearance: Int) { + hintTextAppearance = textAppearance + if (hintTextView != null) { + hintTextView!!.setTextAppearance(textAppearance) + } + } + + fun setHintTextColor(color: Int) { + hintColorDefault = ColorStateList.valueOf(color) + if (hintTextView != null) { + var focus = false; + for (i in 0 until editTexts.size()) { + val editText = editTexts[i] + if (editText?.isFocused == true) { + focus = true + break; + } + } + updateHintTextColor(focus) + } + } + + fun setSeparatorTextAppearance(textAppearance: Int) { + separatorTextAppearance = textAppearance + initLayout() + } + + fun setSeparatorPadding(padding: Int) { + separatorPadding = padding + initLayout() + } + + fun setSeparatorTextSize(textSize: Float) { + separatorTextSize = textSize + initLayout() + } + + fun setSelection(selection: Int) { + var selection = selection + for (i in 0 until editTexts.size()) { + val length = getLength(i) + if (selection < length) { + val editText = editTexts[i] + editText!!.requestFocus() + editText.setSelection(selection) + break + } + selection -= length + } + } + + override fun setEnabled(isEnabled: Boolean) { + this.isEnabled = isEnabled + for (i in 0 until editTexts.size()) { + val editText = editTexts[i] + editText!!.isEnabled = isEnabled + } + } + + fun setShiftPosition(b: Boolean) { + shiftPosition = b + initLayout() + } + + override fun isEnabled(): Boolean { + return isEnabled + } + + fun isShiftPosition(): Boolean { + return shiftPosition + } + + + private fun updateEditTextLength() { + for (i in 0 until editTexts.size()) { + val editText = editTexts[i] + val params = editText!!.layoutParams as LinearLayout.LayoutParams + params.weight = getLength(i).toFloat() + editText.requestLayout() + } + } + + private fun createActionModeCallback(editText: EditText): ActionMode.Callback { + return object : ActionMode.Callback { + override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { + return true + } + + override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean { + return false + } + + override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { + return false + } + + override fun onDestroyActionMode(mode: ActionMode) {} + } + } + + private fun createTextChangeListener(editText: EditText, index: Int): TextWatcher { + return object : TextWatcher { + var sequence: CharSequence = "" + var beforeSequence: CharSequence = "" + var prevLength = 0 + var selection = 0 + var start = 0 + var before = 0 + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { + var start = start + prevLength = s.length + selection = editText.selectionStart + beforeSequence = sequence + for (i in 0 until index) { + start += getLength(i) + } + this.start = start + before = beforeSequence.length + this@BlockEditText.beforeTextChanged(beforeSequence, this.start, before, before + (after - count)) + } + + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { + val nextView: EditText? = editTexts[index + 1] + val prevView: EditText? = editTexts[index - 1] + if (s.length > prevLength && editText.isFocused && editText.selectionStart == getLength(index)) + if (s.length == getLength(index) && nextView != null && nextView.text.isEmpty()) nextView.requestFocus() + else if (s.isEmpty() && prevView != null) prevView.requestFocus() + if (shiftPosition && s.length < getLength(index)) { + if (editText.selectionStart == 0 && editText.isFocused && prevView != null) { + prevView.requestFocus() + prevView.setSelection(prevView.text.length) + } + if (nextView != null && !nextView.text.toString().isEmpty()) { + var length = getLength(index) - s.length + length = if (length > nextView.text.length) nextView.text.length else length + var editable = nextView.text + val temp = editable.toString().substring(0, length) + editable = editable.delete(0, length) + editText.append(temp) + + val safeSelection = selection.coerceIn(0, editText.text.length) + try { + editText.setSelection(safeSelection) + } catch (e: IndexOutOfBoundsException) { + // fallback to end of text as last resort + editText.setSelection(editText.text.length) + } + + nextView.text = editable + } + } + sequence = text!! + this@BlockEditText.onTextChanged(sequence, this.start, this.before, sequence.length) + } + + override fun afterTextChanged(s: Editable) { + val nextView: EditText? = editTexts[index + 1] + if (shiftPosition && nextView != null) nextView.isEnabled = isEnabled && s.length >= getLength(index) + this@BlockEditText.afterTextChanged(s) + } + } + } + + inner class LengthFilter(private val editText: EditText, private val index: Int) : InputFilter { + override fun filter(source: CharSequence, start: Int, end: Int, dest: Spanned, + dstart: Int, dend: Int): CharSequence? { + var source = source + source = if (editText.inputType == InputType.TYPE_CLASS_NUMBER) { + source.toString().replace("[\\D]".toRegex(), "") + } else { + source.toString().replace("[\\W]".toRegex(), "") + } + val mMax = getLength(index) + var keep = mMax - (dest.length - (dend - dstart)) + val nextView: EditText? = editTexts[index + 1] + if (source.isEmpty()) return null + return if (keep <= 0) { + if (nextView != null && text!!.length < maxLength) { + var temp = editText.text.toString() + val selection = editText.selectionStart + temp = temp.substring(0, selection) + source + temp.substring(selection) + editText.setText(temp.substring(0, mMax)) + temp = temp.substring(mMax) + if (selection + source.length <= mMax) editText.setSelection(selection + source.length) else { + nextView.requestFocus() + nextView.setSelection(0) + } + if (temp.isNotEmpty()) { + nextView.editableText.insert(0, temp) + val nextLength = getLength(index + 1) + nextView.setSelection(if (temp.length < nextLength) temp.length else nextLength) + } else nextView.setSelection(0) + } + "" + } else if (keep >= end - start) { + null // keep original + } else { + if (source.length > keep) if (nextView != null && text!!.length < maxLength) { + var temp = editText.text.toString() + val selection = editText.selectionStart + temp = temp.substring(0, selection) + source + temp.substring(selection) + editText.setText(temp.substring(0, mMax)) + temp = temp.substring(mMax) + if (selection + source.length <= mMax) editText.setSelection(selection + source.length) else { + nextView.requestFocus() + nextView.setSelection(0) + } + if (temp.isNotEmpty()) { + nextView.editableText.insert(0, temp) + val nextLength = getLength(index + 1) + nextView.setSelection(if (temp.length < nextLength) temp.length else nextLength) + } else nextView.setSelection(0) + return "" + } + keep += start + if (Character.isHighSurrogate(source[keep - 1])) { + --keep + if (keep == start) { + return "" + } + } + source.subSequence(start, keep) + } + } + } + + + fun disableCopyPaste() { + for (i in 0 until editTexts.size()) { + val et = editTexts[i] ?: continue + + et.isLongClickable = false + et.setLongClickable(false) + et.setTextIsSelectable(false) + + // Block ActionMode (copy, cut, paste) + et.customSelectionActionModeCallback = object : ActionMode.Callback { + override fun onCreateActionMode(mode: ActionMode?, menu: Menu?) = false + override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?) = false + override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?) = false + override fun onDestroyActionMode(mode: ActionMode?) {} + } + + // Block long-press entirely + et.setOnLongClickListener { true } + + // Block paste from keyboard + et.setOnTouchListener { v, event -> + if (event.action == MotionEvent.ACTION_DOWN) v.cancelLongPress() + false + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/helpers/dynamicMapper/FormSubmitRequestMapper.kt b/app/src/main/java/org/piramalswasthya/sakhi/helpers/dynamicMapper/FormSubmitRequestMapper.kt new file mode 100644 index 000000000..93be68ec4 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/helpers/dynamicMapper/FormSubmitRequestMapper.kt @@ -0,0 +1,88 @@ +package org.piramalswasthya.sakhi.helpers.dynamicMapper + +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import org.json.JSONObject +import org.piramalswasthya.sakhi.utils.StringMappingUtil +import org.piramalswasthya.sakhi.model.dynamicEntity.CUFYFormResponseJsonEntity +import org.piramalswasthya.sakhi.model.dynamicEntity.FilariaMDA.FilariaMDAFormResponseJsonEntity +import org.piramalswasthya.sakhi.model.dynamicEntity.FormResponseJsonEntity +import org.piramalswasthya.sakhi.model.dynamicEntity.FormSubmitRequest +import org.piramalswasthya.sakhi.model.dynamicEntity.NCDReferalFormResponseJsonEntity +import org.piramalswasthya.sakhi.model.dynamicEntity.anc.ANCFormResponseJsonEntity +import org.piramalswasthya.sakhi.model.dynamicEntity.ben_ifa.BenIfaFormResponseJsonEntity +import org.piramalswasthya.sakhi.model.dynamicEntity.eye_surgery.EyeSurgeryFormResponseJsonEntity +import org.piramalswasthya.sakhi.model.dynamicEntity.hbyc.FormResponseJsonEntityHBYC +import org.piramalswasthya.sakhi.model.dynamicEntity.mosquitonetEntity.MosquitoNetFormResponseJsonEntity +import java.text.SimpleDateFormat +import java.util.* + +object FormSubmitRequestMapper { + + fun fromEntity(entity: FormResponseJsonEntity, userName: String): FormSubmitRequest? { + return mapCommon(entity.formDataJson, userName) + } + fun fromEntity(entity: MosquitoNetFormResponseJsonEntity, userName: String): FormSubmitRequest? { + return mapCommon(entity.formDataJson, userName) + } + fun fromEntity(entity: BenIfaFormResponseJsonEntity, userName: String): FormSubmitRequest? { + return mapCommon(entity.formDataJson, userName) + } + + fun fromEntity(entity: FormResponseJsonEntityHBYC, userName: String): FormSubmitRequest? { + return mapCommon(entity.formDataJson, userName) + } + fun fromEntity(entity: EyeSurgeryFormResponseJsonEntity, userName: String): FormSubmitRequest? { + return mapCommon(entity.formDataJson, userName) + } + fun fromEntity(entity: CUFYFormResponseJsonEntity, userName: String): FormSubmitRequest? { + return mapCommon(entity.formDataJson, userName) + } + fun fromEntity(entity: NCDReferalFormResponseJsonEntity, userName: String): FormSubmitRequest? { + return mapCommon(entity.formDataJson, userName) + } + + + fun fromEntity(entity: FilariaMDAFormResponseJsonEntity, userName: String): FormSubmitRequest? { + return mapCommon(entity.formDataJson, userName) + } + fun formEntity(entity: ANCFormResponseJsonEntity, userName: String): FormSubmitRequest? { + return mapCommon(entity.formDataJson,userName) + } + + private fun mapCommon(formDataJson: String, userName: String): FormSubmitRequest? { + return try { + val jsonObj = JSONObject(formDataJson) + val fieldsObj = jsonObj.optJSONObject("fields") + + val type = object : TypeToken>() {}.type + val fieldsMap: Map = Gson().fromJson(fieldsObj.toString(), type) + val englishFieldsMap = fieldsMap.mapValues { (_, v) -> + if (v is String) StringMappingUtil.convertDigits(v) else v + } + + FormSubmitRequest( + userName = userName, + formId = jsonObj.optString("formId"), + beneficiaryId = jsonObj.optLong("beneficiaryId"), + houseHoldId = jsonObj.optLong("houseHoldId"), + visitDate = StringMappingUtil.convertDigits(jsonObj.optString("visitDate")), + fields = englishFieldsMap + ) + } catch (e: Exception) { + e.printStackTrace() + null + } + } + + private fun convertDateToIso(input: String): String { + return try { + val inputFormat = SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH) + val outputFormat = SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) + val date = inputFormat.parse(input) + outputFormat.format(date!!) + } catch (e: Exception) { + input + } + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/helpers/otpview/DefaultMovementMethod.kt b/app/src/main/java/org/piramalswasthya/sakhi/helpers/otpview/DefaultMovementMethod.kt new file mode 100644 index 000000000..f03a042e2 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/helpers/otpview/DefaultMovementMethod.kt @@ -0,0 +1,68 @@ +package org.piramalswasthya.sakhi.helpers.otpview + +import android.text.Selection +import android.text.Spannable +import android.text.method.MovementMethod +import android.view.KeyEvent +import android.view.MotionEvent +import android.widget.TextView + +class DefaultMovementMethod private constructor() : MovementMethod { + override fun initialize(widget: TextView, text: Spannable) { + Selection.setSelection(text, 0) + } + + override fun onKeyDown( + widget: TextView, + text: Spannable, + keyCode: Int, + event: KeyEvent + ): Boolean { + return false + } + + override fun onKeyUp( + widget: TextView, + text: Spannable, + keyCode: Int, + event: KeyEvent + ): Boolean { + return false + } + + override fun onKeyOther(view: TextView, text: Spannable, event: KeyEvent): Boolean { + return false + } + + override fun onTakeFocus(widget: TextView, text: Spannable, direction: Int) {} + override fun onTrackballEvent(widget: TextView, text: Spannable, event: MotionEvent): Boolean { + return false + } + + override fun onTouchEvent(widget: TextView, text: Spannable, event: MotionEvent): Boolean { + return false + } + + override fun onGenericMotionEvent( + widget: TextView, + text: Spannable, + event: MotionEvent + ): Boolean { + return false + } + + override fun canSelectArbitrarily(): Boolean { + return false + } + + companion object { + private var sInstance: DefaultMovementMethod? = null + val instance: MovementMethod? + get() { + if (sInstance == null) { + sInstance = DefaultMovementMethod() + } + return sInstance + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/helpers/otpview/PinView.kt b/app/src/main/java/org/piramalswasthya/sakhi/helpers/otpview/PinView.kt new file mode 100644 index 000000000..1586c40fc --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/helpers/otpview/PinView.kt @@ -0,0 +1,1106 @@ +/* + * Copyright 2017 Chaos Leong + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.piramalswasthya.sakhi.helpers.otpview + +import android.animation.ValueAnimator +import android.animation.ValueAnimator.AnimatorUpdateListener +import android.content.Context +import android.content.res.ColorStateList +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.graphics.Path +import android.graphics.PointF +import android.graphics.Rect +import android.graphics.RectF +import android.graphics.Typeface +import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.Drawable +import android.os.Build +import android.text.InputFilter +import android.text.InputFilter.LengthFilter +import android.text.TextPaint +import android.text.TextUtils +import android.text.method.MovementMethod +import android.util.AttributeSet +import android.view.ActionMode +import android.view.Menu +import android.view.MenuItem +import android.view.animation.DecelerateInterpolator +import android.view.inputmethod.EditorInfo +import androidx.annotation.ColorInt +import androidx.annotation.DrawableRes +import androidx.annotation.Px +import androidx.appcompat.widget.AppCompatEditText +import androidx.core.content.res.ResourcesCompat +import androidx.core.view.ViewCompat +import org.piramalswasthya.sakhi.R +import kotlin.math.abs + +/** + * Provides a widget for enter PIN/OTP/password etc. + * + * @author Chaos Leong + * 01/04/2017 + */ +class PinView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = R.attr.pinViewStyle +) : AppCompatEditText(context, attrs, defStyleAttr) { + private val mViewType: Int + + private var mPinItemCount: Int + + private var mPinItemWidth: Int + private var mPinItemHeight: Int + private var mPinItemRadius: Int + private var mPinItemSpacing: Int + + private val mPaint: Paint + private val mAnimatorTextPaint: TextPaint? = TextPaint() + + /** + * Gets the line colors for the different states (normal, selected, focused) of the PinView. + * + * @attr ref R.styleable#PinView_lineColor + * @see .setLineColor + * @see .setLineColor + */ + var lineColors: ColorStateList? + private set + + /** + * + * Return the current color selected for normal line. + * + * @return Returns the current item's line color. + */ + @get:ColorInt + var currentLineColor: Int = Color.BLACK + private set + private var mLineWidth: Int + + private val mTextRect = Rect() + private val mItemBorderRect = RectF() + private val mItemLineRect = RectF() + private val mPath = Path() + private val mItemCenterPoint = PointF() + + private var mDefaultAddAnimator: ValueAnimator? = null + private var isAnimationEnable = false + private var isPasswordHidden: Boolean + + private var mBlink: Blink? = null + private var isCursorVisible: Boolean + private var drawCursor = false + private var mCursorHeight = 0f + private var mCursorWidth: Int + private var mCursorColor: Int + + private var mItemBackgroundResource = 0 + private var mItemBackground: Drawable? + + private var mHideLineWhenFilled: Boolean + + private var mTransformed: String? = null + + init { + val res = resources + + mPaint = Paint(Paint.ANTI_ALIAS_FLAG) + mPaint.style = Paint.Style.STROKE + + mAnimatorTextPaint!!.set(paint) + + val theme = context.theme + + val a = theme.obtainStyledAttributes(attrs, R.styleable.PinView, defStyleAttr, 0) + + mViewType = a.getInt(R.styleable.PinView_viewType, VIEW_TYPE_RECTANGLE) + mPinItemCount = a.getInt(R.styleable.PinView_itemCount, DEFAULT_COUNT) + mPinItemHeight = a.getDimension( + R.styleable.PinView_itemHeight, + res.getDimensionPixelSize(R.dimen.pv_pin_view_item_size).toFloat() + ).toInt() + mPinItemWidth = a.getDimension( + R.styleable.PinView_itemWidth, + res.getDimensionPixelSize(R.dimen.pv_pin_view_item_size).toFloat() + ).toInt() + mPinItemSpacing = a.getDimensionPixelSize( + R.styleable.PinView_itemSpacing, + res.getDimensionPixelSize(R.dimen.pv_pin_view_item_spacing) + ) + mPinItemRadius = a.getDimension(R.styleable.PinView_itemRadius, 0f).toInt() + mLineWidth = a.getDimension( + R.styleable.PinView_lineWidth, + res.getDimensionPixelSize(R.dimen.pv_pin_view_item_line_width).toFloat() + ).toInt() + lineColors = a.getColorStateList(R.styleable.PinView_lineColor) + isCursorVisible = a.getBoolean(R.styleable.PinView_android_cursorVisible, true) + mCursorColor = a.getColor(R.styleable.PinView_cursorColor, currentTextColor) + mCursorWidth = a.getDimensionPixelSize( + R.styleable.PinView_cursorWidth, + res.getDimensionPixelSize(R.dimen.pv_pin_view_cursor_width) + ) + + mItemBackground = a.getDrawable(R.styleable.PinView_android_itemBackground) + mHideLineWhenFilled = a.getBoolean(R.styleable.PinView_hideLineWhenFilled, false) + + a.recycle() + + if (lineColors != null) { + currentLineColor = lineColors!!.defaultColor + } + updateCursorHeight() + + checkItemRadius() + + setMaxLength(mPinItemCount) + mPaint.strokeWidth = mLineWidth.toFloat() + setupAnimator() + + transformationMethod = null + disableSelectionMenu() + + // preserve the legacy behavior: isPasswordHidden controlled by inputType + isPasswordHidden = isPasswordInputType(inputType) + } + + // preserve the legacy behavior: isPasswordHidden controlled by inputType + override fun setInputType(type: Int) { + super.setInputType(type) + isPasswordHidden = isPasswordInputType(inputType) + } + + /** + * new behavior: reveal or hide the pins programmatically + * + * @param hidden True to hide, false to reveal the text + */ + fun setPasswordHidden(hidden: Boolean) { + isPasswordHidden = hidden + requestLayout() + } + + /** + * new behavior: reveal or hide the pins programmatically + * + * @returns True if the pins are currently hidden + */ + fun isPasswordHidden(): Boolean { + return isPasswordHidden + } + + override fun setTypeface(tf: Typeface?, style: Int) { + super.setTypeface(tf, style) + } + + override fun setTypeface(tf: Typeface?) { + super.setTypeface(tf) + mAnimatorTextPaint?.set(paint) + } + + private fun setMaxLength(maxLength: Int) { + filters = if (maxLength >= 0) { + arrayOf(LengthFilter(maxLength)) + } else { + NO_FILTERS + } + } + + private fun setupAnimator() { + mDefaultAddAnimator = ValueAnimator.ofFloat(0.5f, 1f) + mDefaultAddAnimator?.setDuration(150) + mDefaultAddAnimator?.setInterpolator(DecelerateInterpolator()) + mDefaultAddAnimator?.addUpdateListener(AnimatorUpdateListener { animation -> + val scale = animation.animatedValue as Float + val alpha = (255 * scale).toInt() + mAnimatorTextPaint!!.textSize = textSize * scale + mAnimatorTextPaint.alpha = alpha + postInvalidate() + }) + } + + private fun checkItemRadius() { + if (mViewType == VIEW_TYPE_LINE) { + val halfOfLineWidth = (mLineWidth.toFloat()) / 2 + require(!(mPinItemRadius > halfOfLineWidth)) { "The itemRadius can not be greater than lineWidth when viewType is line" } + } else if (mViewType == VIEW_TYPE_RECTANGLE) { + val halfOfItemWidth = (mPinItemWidth.toFloat()) / 2 + require(!(mPinItemRadius > halfOfItemWidth)) { "The itemRadius can not be greater than itemWidth" } + } + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + val widthMode = MeasureSpec.getMode(widthMeasureSpec) + val heightMode = MeasureSpec.getMode(heightMeasureSpec) + val widthSize = MeasureSpec.getSize(widthMeasureSpec) + val heightSize = MeasureSpec.getSize(heightMeasureSpec) + + var width: Int + val height: Int + + val boxHeight = mPinItemHeight + + if (widthMode == MeasureSpec.EXACTLY) { + // Parent has told us how big to be. So be it. + width = widthSize + } else { + val boxesWidth = (mPinItemCount - 1) * mPinItemSpacing + mPinItemCount * mPinItemWidth + width = boxesWidth + ViewCompat.getPaddingEnd(this) + ViewCompat.getPaddingStart(this) + if (mPinItemSpacing == 0) { + width -= (mPinItemCount - 1) * mLineWidth + } + } + + height = if (heightMode == MeasureSpec.EXACTLY) { + // Parent has told us how big to be. So be it. + heightSize + } else { + boxHeight + paddingTop + paddingBottom + } + + setMeasuredDimension(width, height) + } + + override fun onTextChanged( + text: CharSequence, + start: Int, + lengthBefore: Int, + lengthAfter: Int + ) { + if (start != text.length) { + moveSelectionToEnd() + } + + makeBlink() + + if (isAnimationEnable) { + val isAdd = lengthAfter - lengthBefore > 0 + if (isAdd) { + if (mDefaultAddAnimator != null) { + mDefaultAddAnimator!!.end() + mDefaultAddAnimator!!.start() + } + } + } + + val transformation = transformationMethod + mTransformed = transformation?.getTransformation(getText(), this)?.toString() + ?: getText().toString() + } + + override fun onFocusChanged(focused: Boolean, direction: Int, previouslyFocusedRect: Rect?) { + super.onFocusChanged(focused, direction, previouslyFocusedRect) + + if (focused) { + moveSelectionToEnd() + makeBlink() + } + } + + override fun onSelectionChanged(selStart: Int, selEnd: Int) { + super.onSelectionChanged(selStart, selEnd) + + if (selEnd != text!!.length) { + moveSelectionToEnd() + } + } + + private fun moveSelectionToEnd() { + setSelection(text!!.length) + } + + override fun drawableStateChanged() { + super.drawableStateChanged() + + if (lineColors == null || lineColors!!.isStateful) { + updateColors() + } + } + + override fun onDraw(canvas: Canvas) { + canvas.save() + + updatePaints() + drawPinView(canvas) + + canvas.restore() + } + + private fun updatePaints() { + mPaint.color = currentLineColor + mPaint.style = Paint.Style.STROKE + mPaint.strokeWidth = mLineWidth.toFloat() + paint.color = currentTextColor + } + + private fun drawPinView(canvas: Canvas) { + val highlightIdx = text!!.length + for (i in 0 until mPinItemCount) { + val highlight = isFocused && highlightIdx == i + mPaint.color = + if (highlight) getLineColorForState(*HIGHLIGHT_STATES) else currentLineColor + + updateItemRectF(i) + updateCenterPoint() + + canvas.save() + if (mViewType == VIEW_TYPE_RECTANGLE) { + updatePinBoxPath(i) + canvas.clipPath(mPath) + } + drawItemBackground(canvas, highlight) + canvas.restore() + + if (highlight) { + drawCursor(canvas) + } + + if (mViewType == VIEW_TYPE_RECTANGLE) { + drawPinBox(canvas, i) + } else if (mViewType == VIEW_TYPE_LINE) { + drawPinLine(canvas, i) + } + + if (DBG) { + drawAnchorLine(canvas) + } + + if (mTransformed!!.length > i) { + if (transformationMethod == null && isPasswordHidden) { + drawCircle(canvas, i) + } else { + drawText(canvas, i) + } + } else if (!TextUtils.isEmpty(hint) && hint.length == mPinItemCount) { + drawHint(canvas, i) + } + } + + // highlight the next item + if (isFocused && text!!.length != mPinItemCount && mViewType == VIEW_TYPE_RECTANGLE) { + val index = text!!.length + updateItemRectF(index) + updateCenterPoint() + updatePinBoxPath(index) + mPaint.color = getLineColorForState(*HIGHLIGHT_STATES) + drawPinBox(canvas, index) + } + } + + private fun getLineColorForState(vararg states: Int): Int { + return if (lineColors != null) lineColors!!.getColorForState( + states, + currentLineColor + ) else currentLineColor + } + + private fun drawItemBackground(canvas: Canvas, highlight: Boolean) { + if (mItemBackground == null) { + return + } + val delta = mLineWidth.toFloat() / 2 + val left = Math.round(mItemBorderRect.left - delta) + val top = Math.round(mItemBorderRect.top - delta) + val right = Math.round(mItemBorderRect.right + delta) + val bottom = Math.round(mItemBorderRect.bottom + delta) + + mItemBackground!!.setBounds(left, top, right, bottom) + mItemBackground!!.setState(if (highlight) HIGHLIGHT_STATES else drawableState) + mItemBackground!!.draw(canvas) + } + + private fun updatePinBoxPath(i: Int) { + var drawRightCorner = false + var drawLeftCorner = false + if (mPinItemSpacing != 0) { + drawRightCorner = true + drawLeftCorner = drawRightCorner + } else { + if (i == 0 && i != mPinItemCount - 1) { + drawLeftCorner = true + } + if (i == mPinItemCount - 1 && i != 0) { + drawRightCorner = true + } + } + updateRoundRectPath( + mItemBorderRect, + mPinItemRadius.toFloat(), + mPinItemRadius.toFloat(), + drawLeftCorner, + drawRightCorner + ) + } + + private fun drawPinBox(canvas: Canvas, i: Int) { + if (mHideLineWhenFilled && i < text!!.length) { + return + } + canvas.drawPath(mPath, mPaint) + } + + private fun drawPinLine(canvas: Canvas, i: Int) { + if (mHideLineWhenFilled && i < text!!.length) { + return + } + var l: Boolean + var r: Boolean + r = true + l = r + if (mPinItemSpacing == 0 && mPinItemCount > 1) { + if (i == 0) { + // draw only left round + r = false + } else if (i == mPinItemCount - 1) { + // draw only right round + l = false + } else { + // draw rect + r = false + l = r + } + } + mPaint.style = Paint.Style.FILL + mPaint.strokeWidth = mLineWidth.toFloat() / 10 + val halfLineWidth = (mLineWidth.toFloat()) / 2 + mItemLineRect[mItemBorderRect.left - halfLineWidth, mItemBorderRect.bottom - halfLineWidth, mItemBorderRect.right + halfLineWidth] = + mItemBorderRect.bottom + halfLineWidth + + updateRoundRectPath(mItemLineRect, mPinItemRadius.toFloat(), mPinItemRadius.toFloat(), l, r) + canvas.drawPath(mPath, mPaint) + } + + private fun drawCursor(canvas: Canvas) { + if (drawCursor) { + val cx = mItemCenterPoint.x + val cy = mItemCenterPoint.y + val x = cx + val y = cy - mCursorHeight / 2 + + val color = mPaint.color + val width = mPaint.strokeWidth + mPaint.color = mCursorColor + mPaint.strokeWidth = mCursorWidth.toFloat() + + canvas.drawLine(x, y, x, y + mCursorHeight, mPaint) + + mPaint.color = color + mPaint.strokeWidth = width + } + } + + private fun updateRoundRectPath(rectF: RectF, rx: Float, ry: Float, l: Boolean, r: Boolean) { + updateRoundRectPath(rectF, rx, ry, l, r, r, l) + } + + private fun updateRoundRectPath( + rectF: RectF, rx: Float, ry: Float, + tl: Boolean, tr: Boolean, br: Boolean, bl: Boolean + ) { + mPath.reset() + + val l = rectF.left + val t = rectF.top + val r = rectF.right + val b = rectF.bottom + + val w = r - l + val h = b - t + + val lw = w - 2 * rx // line width + val lh = h - 2 * ry // line height + + mPath.moveTo(l, t + ry) + + if (tl) { + mPath.rQuadTo(0f, -ry, rx, -ry) // top-left corner + } else { + mPath.rLineTo(0f, -ry) + mPath.rLineTo(rx, 0f) + } + + mPath.rLineTo(lw, 0f) + + if (tr) { + mPath.rQuadTo(rx, 0f, rx, ry) // top-right corner + } else { + mPath.rLineTo(rx, 0f) + mPath.rLineTo(0f, ry) + } + + mPath.rLineTo(0f, lh) + + if (br) { + mPath.rQuadTo(0f, ry, -rx, ry) // bottom-right corner + } else { + mPath.rLineTo(0f, ry) + mPath.rLineTo(-rx, 0f) + } + + mPath.rLineTo(-lw, 0f) + + if (bl) { + mPath.rQuadTo(-rx, 0f, -rx, -ry) // bottom-left corner + } else { + mPath.rLineTo(-rx, 0f) + mPath.rLineTo(0f, -ry) + } + + mPath.rLineTo(0f, -lh) + + mPath.close() + } + + private fun updateItemRectF(i: Int) { + val halfLineWidth = (mLineWidth.toFloat()) / 2 + var left = + scrollX + ViewCompat.getPaddingStart(this) + i * (mPinItemSpacing + mPinItemWidth) + halfLineWidth + if (mPinItemSpacing == 0 && i > 0) { + left = left - (mLineWidth) * i + } + val right = left + mPinItemWidth - mLineWidth + val top = scrollY + paddingTop + halfLineWidth + val bottom = top + mPinItemHeight - mLineWidth + + mItemBorderRect[left, top, right] = bottom + } + + private fun drawText(canvas: Canvas, i: Int) { + val paint = getPaintByIndex(i) + drawTextAtBox(canvas, paint, mTransformed, i) + } + + private fun drawHint(canvas: Canvas, i: Int) { + val paint = getPaintByIndex(i) + paint!!.color = currentHintTextColor + drawTextAtBox(canvas, paint, hint, i) + } + + private fun drawTextAtBox(canvas: Canvas, paint: Paint?, text: CharSequence?, charAt: Int) { + paint!!.getTextBounds(text.toString(), charAt, charAt + 1, mTextRect) + val cx = mItemCenterPoint.x + val cy = mItemCenterPoint.y + val x = (cx - abs(mTextRect.width().toFloat().toDouble()) / 2 - mTextRect.left).toFloat() + val y = (cy + abs( + mTextRect.height().toFloat().toDouble() + ) / 2 - mTextRect.bottom).toFloat() // always center vertical + canvas.drawText(text!!, charAt, charAt + 1, x, y, paint) + } + + private fun drawCircle(canvas: Canvas, i: Int) { + val paint = getPaintByIndex(i) + val cx = mItemCenterPoint.x + val cy = mItemCenterPoint.y + canvas.drawCircle(cx, cy, paint!!.textSize / 2, paint) + } + + private fun getPaintByIndex(i: Int): Paint? { + if (isAnimationEnable && i == text!!.length - 1) { + mAnimatorTextPaint!!.color = paint.color + return mAnimatorTextPaint + } else { + return paint + } + } + + /** + * For seeing the font position + */ + private fun drawAnchorLine(canvas: Canvas) { + var cx = mItemCenterPoint.x + var cy = mItemCenterPoint.y + mPaint.strokeWidth = 1f + cx -= mPaint.strokeWidth / 2 + cy -= mPaint.strokeWidth / 2 + + mPath.reset() + mPath.moveTo(cx, mItemBorderRect.top) + mPath.lineTo(cx, (mItemBorderRect.top + abs(mItemBorderRect.height().toDouble())).toFloat()) + canvas.drawPath(mPath, mPaint) + + mPath.reset() + mPath.moveTo(mItemBorderRect.left, cy) + mPath.lineTo((mItemBorderRect.left + abs(mItemBorderRect.width().toDouble())).toFloat(), cy) + canvas.drawPath(mPath, mPaint) + + mPath.reset() + + mPaint.strokeWidth = mLineWidth.toFloat() + } + + private fun updateColors() { + var inval = false + val color = if (lineColors != null) { + lineColors!!.getColorForState(drawableState, 0) + } else { + currentTextColor + } + + if (color != currentLineColor) { + currentLineColor = color + inval = true + } + + if (inval) { + invalidate() + } + } + + private fun updateCenterPoint() { + val cx = (mItemBorderRect.left + abs(mItemBorderRect.width().toDouble()) / 2).toFloat() + val cy = (mItemBorderRect.top + abs(mItemBorderRect.height().toDouble()) / 2).toFloat() + mItemCenterPoint[cx] = cy + } + + override fun getDefaultMovementMethod(): MovementMethod { + // we don't need arrow key. + return DefaultMovementMethod.instance!! + } + + /** + * Sets the line color for all the states (normal, selected, + * focused) to be this color. + * + * @param color A color value in the form 0xAARRGGBB. + * Do not pass a resource ID. To get a color value from a resource ID, call + * [getColor][androidx.core.content.ContextCompat.getColor]. + * @attr ref R.styleable#PinView_lineColor + * @see .setLineColor + * @see .getLineColors + */ + fun setLineColor(@ColorInt color: Int) { + lineColors = ColorStateList.valueOf(color) + updateColors() + } + + /** + * Sets the line color. + * + * @attr ref R.styleable#PinView_lineColor + * @see .setLineColor + * @see .getLineColors + */ + fun setLineColor(colors: ColorStateList?) { + if (colors == null) { + throw NullPointerException() + } + + lineColors = colors + updateColors() + } + + var lineWidth: Int + /** + * @return Returns the width of the item's line. + * @see .setLineWidth + */ + get() = mLineWidth + /** + * Sets the line width. + * + * @attr ref R.styleable#PinView_lineWidth + * @see .getLineWidth + */ + set(borderWidth) { + mLineWidth = borderWidth + checkItemRadius() + requestLayout() + } + + var itemCount: Int + /** + * @return Returns the count of items. + * @see .setItemCount + */ + get() = mPinItemCount + /** + * Sets the count of items. + * + * @attr ref R.styleable#PinView_itemCount + * @see .getItemCount + */ + set(count) { + mPinItemCount = count + setMaxLength(count) + requestLayout() + } + + var itemRadius: Int + /** + * @return Returns the radius of square. + * @see .setItemRadius + */ + get() = mPinItemRadius + /** + * Sets the radius of square. + * + * @attr ref R.styleable#PinView_itemRadius + * @see .getItemRadius + */ + set(itemRadius) { + mPinItemRadius = itemRadius + checkItemRadius() + requestLayout() + } + + @get:Px + var itemSpacing: Int + /** + * @return Returns the spacing between two items. + * @see .setItemSpacing + */ + get() = mPinItemSpacing + /** + * Specifies extra space between two items. + * + * @attr ref R.styleable#PinView_itemSpacing + * @see .getItemSpacing + */ + set(itemSpacing) { + mPinItemSpacing = itemSpacing + requestLayout() + } + + var itemHeight: Int + /** + * @return Returns the height of item. + * @see .setItemHeight + */ + get() = mPinItemHeight + /** + * Sets the height of item. + * + * @attr ref R.styleable#PinView_itemHeight + * @see .getItemHeight + */ + set(itemHeight) { + mPinItemHeight = itemHeight + updateCursorHeight() + requestLayout() + } + + var itemWidth: Int + /** + * @return Returns the width of item. + * @see .setItemWidth + */ + get() = mPinItemWidth + /** + * Sets the width of item. + * + * @attr ref R.styleable#PinView_itemWidth + * @see .getItemWidth + */ + set(itemWidth) { + mPinItemWidth = itemWidth + checkItemRadius() + requestLayout() + } + + /** + * Specifies whether the text animation should be enabled or disabled. + * By the default, the animation is disabled. + * + * @param enable True to start animation when adding text, false to transition immediately + */ + fun setAnimationEnable(enable: Boolean) { + isAnimationEnable = enable + } + + /** + * Specifies whether the line (border) should be hidden or visible when text entered. + * By the default, this flag is false and the line is always drawn. + * + * @param hideLineWhenFilled true to hide line on a position where text entered, + * false to always show line + * @attr ref R.styleable#PinView_hideLineWhenFilled + */ + fun setHideLineWhenFilled(hideLineWhenFilled: Boolean) { + this.mHideLineWhenFilled = hideLineWhenFilled + } + + override fun setTextSize(size: Float) { + super.setTextSize(size) + updateCursorHeight() + } + + override fun setTextSize(unit: Int, size: Float) { + super.setTextSize(unit, size) + updateCursorHeight() + } + + //region ItemBackground + /** + * Set the item background to a given resource. The resource should refer to + * a Drawable object or 0 to remove the item background. + * + * @param resId The identifier of the resource. + * @attr ref R.styleable#PinView_android_itemBackground + */ + fun setItemBackgroundResources(@DrawableRes resId: Int) { + if (resId != 0 && mItemBackgroundResource != resId) { + return + } + mItemBackground = ResourcesCompat.getDrawable(resources, resId, context.theme) + setItemBackground(mItemBackground) + mItemBackgroundResource = resId + } + + /** + * Sets the item background color for this view. + * + * @param color the color of the item background + */ + fun setItemBackgroundColor(@ColorInt color: Int) { + if (mItemBackground is ColorDrawable) { + ((mItemBackground as ColorDrawable).mutate() as ColorDrawable).color = color + mItemBackgroundResource = 0 + } else { + setItemBackground(ColorDrawable(color)) + } + } + + /** + * Set the item background to a given Drawable, or remove the background. + * + * @param background The Drawable to use as the item background, or null to remove the + * item background + */ + fun setItemBackground(background: Drawable?) { + mItemBackgroundResource = 0 + mItemBackground = background + invalidate() + } + + //endregion + //region Cursor + + var cursorWidth: Int + /** + * @return Returns the width (in pixels) of cursor. + * @see .setCursorWidth + */ + get() = mCursorWidth + /** + * Sets the width (in pixels) of cursor. + * + * @attr ref R.styleable#PinView_cursorWidth + * @see .getCursorWidth + */ + set(width) { + mCursorWidth = width + if (isCursorVisible()) { + invalidateCursor(true) + } + } + + var cursorColor: Int + /** + * Gets the cursor color. + * + * @return Return current cursor color. + * @see .setCursorColor + */ + get() = mCursorColor + /** + * Sets the cursor color. + * + * @param color A color value in the form 0xAARRGGBB. + * Do not pass a resource ID. To get a color value from a resource ID, call + * [getColor][androidx.core.content.ContextCompat.getColor]. + * @attr ref R.styleable#PinView_cursorColor + * @see .getCursorColor + */ + set(color) { + mCursorColor = color + if (isCursorVisible()) { + invalidateCursor(true) + } + } + + override fun setCursorVisible(visible: Boolean) { + if (isCursorVisible != visible) { + isCursorVisible = visible + invalidateCursor(isCursorVisible) + makeBlink() + } + } + + override fun isCursorVisible(): Boolean { + return isCursorVisible + } + + override fun onScreenStateChanged(screenState: Int) { + super.onScreenStateChanged(screenState) + when (screenState) { + SCREEN_STATE_ON -> resumeBlink() + SCREEN_STATE_OFF -> suspendBlink() + } + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + resumeBlink() + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + suspendBlink() + } + + private fun shouldBlink(): Boolean { + return isCursorVisible() && isFocused + } + + private fun makeBlink() { + if (shouldBlink()) { + if (mBlink == null) { + mBlink = Blink() + } + removeCallbacks(mBlink) + drawCursor = false + postDelayed(mBlink, BLINK.toLong()) + } else { + if (mBlink != null) { + removeCallbacks(mBlink) + } + } + } + + private fun suspendBlink() { + if (mBlink != null) { + mBlink!!.cancel() + invalidateCursor(false) + } + } + + private fun resumeBlink() { + if (mBlink != null) { + mBlink!!.uncancel() + makeBlink() + } + } + + private fun invalidateCursor(showCursor: Boolean) { + if (drawCursor != showCursor) { + drawCursor = showCursor + invalidate() + } + } + + private fun updateCursorHeight() { + val delta = 2 * dpToPx(2f) + mCursorHeight = if (mPinItemHeight - textSize > delta) textSize + delta else textSize + } + + private inner class Blink : Runnable { + private var mCancelled = false + + override fun run() { + if (mCancelled) { + return + } + + removeCallbacks(this) + + if (shouldBlink()) { + invalidateCursor(!drawCursor) + postDelayed(this, BLINK.toLong()) + } + } + + fun cancel() { + if (!mCancelled) { + removeCallbacks(this) + mCancelled = true + } + } + + fun uncancel() { + mCancelled = false + } + } + + //endregion + //region Selection Menu + private fun disableSelectionMenu() { + customSelectionActionModeCallback = DefaultActionModeCallback() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + customInsertionActionModeCallback = object : DefaultActionModeCallback() { + override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { + menu.removeItem(android.R.id.autofill) + return true + } + } + } + } + + override fun isSuggestionsEnabled(): Boolean { + return false + } + + //endregion + private fun dpToPx(dp: Float): Int { + return (dp * resources.displayMetrics.density + 0.5f).toInt() + } + + private open class DefaultActionModeCallback : ActionMode.Callback { + override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { + return false + } + + override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean { + return false + } + + override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { + return false + } + + override fun onDestroyActionMode(mode: ActionMode) { + } + } + + companion object { + private const val TAG = "PinView" + + private const val DBG = false + + private const val BLINK = 500 + + private const val DEFAULT_COUNT = 4 + + private val NO_FILTERS = arrayOfNulls(0) + + private val HIGHLIGHT_STATES = intArrayOf(android.R.attr.state_selected) + + private const val VIEW_TYPE_RECTANGLE = 0 + private const val VIEW_TYPE_LINE = 1 + private const val VIEW_TYPE_NONE = 2 + + private fun isPasswordInputType(inputType: Int): Boolean { + val variation = + inputType and (EditorInfo.TYPE_MASK_CLASS or EditorInfo.TYPE_MASK_VARIATION) + return (variation + == (EditorInfo.TYPE_CLASS_TEXT or EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) || (variation + == (EditorInfo.TYPE_CLASS_TEXT or EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)) || (variation + == (EditorInfo.TYPE_CLASS_NUMBER or EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD)) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/ABHAModel.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/ABHAModel.kt new file mode 100644 index 000000000..683f9aac4 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/ABHAModel.kt @@ -0,0 +1,98 @@ +package org.piramalswasthya.sakhi.model + +import androidx.room.Embedded +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.Index +import androidx.room.PrimaryKey +import androidx.room.Relation +import org.piramalswasthya.sakhi.configuration.FormDataModel +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.network.ABHAGeneratedDTO +import org.piramalswasthya.sakhi.network.ABHAProfile +import org.piramalswasthya.sakhi.network.MapHIDtoBeneficiary + +@Entity( + tableName = "ABHA_GENERATED", + foreignKeys = [ForeignKey( + entity = BenRegCache::class, + parentColumns = arrayOf("beneficiaryId"/* "householdId"*/), + childColumns = arrayOf("beneficiaryID" /*"hhId"*/), + onUpdate = ForeignKey.CASCADE, + onDelete = ForeignKey.CASCADE + )], + indices = [Index(value = ["beneficiaryID"], unique = true)] +) +data class ABHAModel( + @PrimaryKey(autoGenerate = true) + val id: Int = 0, + val beneficiaryID: Long, + val beneficiaryRegID: Long, + val benName: String, + val createdBy: String, + val message: String, + val txnId: String, + val benSurname: String? = null, + var healthId: String = "", + var healthIdNumber: String = "", + var abhaProfileJson : String = "", + var isNewAbha: Boolean= false, + val providerServiceMapId: Int, + var syncState: SyncState = SyncState.UNSYNCED, +) : FormDataModel { + fun toDTO(): ABHAGeneratedDTO { + return ABHAGeneratedDTO( + id = 0, + beneficiaryID = beneficiaryID, + beneficiaryRegID = beneficiaryRegID, + benName = benName, + benSurname = benSurname, + healthId = healthId, + healthIdNumber = healthIdNumber, + providerServiceMapId = providerServiceMapId, + txnId = txnId, + message = message, + createdBy = createdBy, + isNewAbha = isNewAbha, + + ) + } +} + +data class BenWithABHAGeneratedCache( + @Embedded + val ben: BenBasicCache, + @Relation( + parentColumn = "benId", entityColumn = "beneficiaryID" + ) + val abha: ABHAModel?, + + ) { + fun asBenWithABHAGeneratedDomainModel(): BenWithABHAGeneratedDomain { + return BenWithABHAGeneratedDomain( + ben = ben.asBasicDomainModel(), + abha = abha + ) + } +} + +data class BenWithABHAGeneratedDomain( + + val ben: BenBasicDomain, + val abha: ABHAModel? +) + +fun ABHAModel.toMapHIDtoBeneficiaryRequest(sharedAbhaProfile: ABHAProfile): MapHIDtoBeneficiary { + return MapHIDtoBeneficiary( + beneficiaryRegID = this.beneficiaryRegID, + beneficiaryID = this.beneficiaryID, + healthId = this.healthId, + healthIdNumber = this.healthIdNumber, + providerServiceMapId = this.providerServiceMapId, + createdBy = this.createdBy, + message = this.message, + txnId = this.txnId, + ABHAProfile = sharedAbhaProfile, + isNew = this.isNewAbha + ) +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/AESScreening.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/AESScreening.kt new file mode 100644 index 000000000..f1546c923 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/AESScreening.kt @@ -0,0 +1,94 @@ +package org.piramalswasthya.sakhi.model + +import androidx.room.Embedded +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.Index +import androidx.room.PrimaryKey +import androidx.room.Relation +import org.piramalswasthya.sakhi.configuration.FormDataModel +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.network.AESScreeningDTO + +@Entity( + tableName = "AES_SCREENING", + foreignKeys = [ForeignKey( + entity = BenRegCache::class, + parentColumns = arrayOf("beneficiaryId"/* "householdId"*/), + childColumns = arrayOf("benId" /*"hhId"*/), + onUpdate = ForeignKey.CASCADE, + onDelete = ForeignKey.CASCADE + )], + indices = [Index(name = "ind_aessn", value = ["benId"/* "hhId"*/])] +) +data class AESScreeningCache( + @PrimaryKey(autoGenerate = true) + val id: Int = 0, + val benId: Long, + var visitDate: Long = System.currentTimeMillis(), + val houseHoldDetailsId: Long, + var beneficiaryStatus: String ? = null, + var beneficiaryStatusId: Int = 0, + var dateOfDeath: Long = System.currentTimeMillis(), + var placeOfDeath: String ? = null, + var otherPlaceOfDeath: String ? = null, + var reasonForDeath: String ? = null, + var otherReasonForDeath: String ? = null, + var aesJeCaseStatus: String ? = "", + var referredTo: Int ? = 0, + var referToName: String ? = null, + var otherReferredFacility: String ? = null, + var diseaseTypeID: Int ? = 0, + var createdDate: Long = System.currentTimeMillis(), + var createdBy: String ? = null, + var followUpPoint: Int ? = 0, + var syncState: SyncState = SyncState.UNSYNCED, +): FormDataModel { + fun toDTO(): AESScreeningDTO { + return AESScreeningDTO( + id = 0, + benId = benId, + visitDate = getDateTimeStringFromLong(visitDate).toString(), + aesJeCaseStatus = aesJeCaseStatus, + houseHoldDetailsId = houseHoldDetailsId, + referredTo = referredTo, + referToName = referToName.toString(), + otherReferredFacility = otherReferredFacility, + createdDate = getDateTimeStringFromLong(createdDate).toString(), + syncState = SyncState.SYNCED, + diseaseTypeID = diseaseTypeID, + beneficiaryStatusId = beneficiaryStatusId, + beneficiaryStatus = beneficiaryStatus, + createdBy = createdBy, + dateOfDeath = getDateTimeStringFromLong(dateOfDeath).toString(), + reasonForDeath = reasonForDeath, + otherReasonForDeath = otherReasonForDeath, + otherPlaceOfDeath = otherPlaceOfDeath, + placeOfDeath = placeOfDeath, + followUpPoint = followUpPoint + + ) + } +} + +data class BenWithAESScreeningCache( + @Embedded + val ben: BenBasicCache, + @Relation( + parentColumn = "benId", entityColumn = "benId" + ) + val aesScreening: AESScreeningCache?, + + ) { + fun asAESScreeningDomainModel(): BenWithAESScreeningDomain { + return BenWithAESScreeningDomain( + ben = ben.asBasicDomainModel(), + aes = aesScreening + ) + } +} + +data class BenWithAESScreeningDomain( + val ben: BenBasicDomain, + val aes: AESScreeningCache? +) \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/AHDCache.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/AHDCache.kt new file mode 100644 index 000000000..dd19fd5e7 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/AHDCache.kt @@ -0,0 +1,42 @@ +package org.piramalswasthya.sakhi.model + +import androidx.room.Entity +import androidx.room.PrimaryKey +import org.piramalswasthya.sakhi.configuration.FormDataModel +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.network.AHDDTO + +@Entity(tableName = "AHDMeeting") +data class AHDCache( + @PrimaryKey(autoGenerate = true) + var id: Int = 0, + var mobilizedForAHD: String? = null, + var ahdPlace: String? = null, + var ahdDate: String? = null, + var image1: String? = null, + var image2: String? = null, + var syncState: SyncState = SyncState.UNSYNCED +) : FormDataModel { + + fun toDTO(): AHDDTO { + return AHDDTO( + id = id, + mobilizedForAHD = mobilizedForAHD, + ahdPlace = ahdPlace, + ahdDate = ahdDate, + image1 = image1, + image2 = image2, + ) + } + + fun toAHDCache(): AHDCache { + return AHDCache( + id = id, + mobilizedForAHD = mobilizedForAHD, + ahdPlace = ahdPlace, + ahdDate = ahdDate, + image1 = image1, + image2 = image2, + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/AadhaarConsentModel.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/AadhaarConsentModel.kt new file mode 100644 index 000000000..3c3325ae4 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/AadhaarConsentModel.kt @@ -0,0 +1,6 @@ +package org.piramalswasthya.sakhi.model + +data class AadhaarConsentModel ( + val title: CharSequence, + var checked: Boolean = false +) \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/AdolescentHealthCache.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/AdolescentHealthCache.kt new file mode 100644 index 000000000..a8f5acd2a --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/AdolescentHealthCache.kt @@ -0,0 +1,88 @@ +package org.piramalswasthya.sakhi.model + +import androidx.room.Embedded +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.Index +import androidx.room.PrimaryKey +import androidx.room.Relation +import org.piramalswasthya.sakhi.configuration.FormDataModel +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.network.AdolscentHealthDTO + +@Entity( + tableName = "Adolescent_Health_Form_Data", + foreignKeys = [ForeignKey( + entity = BenRegCache::class, + parentColumns = arrayOf("beneficiaryId"/* "householdId"*/), + childColumns = arrayOf("benId" /*"hhId"*/), + onUpdate = ForeignKey.CASCADE, + onDelete = ForeignKey.CASCADE + )], + indices = [Index(name = "ind_adolescentsn", value = ["benId"/* "hhId"*/])] +) +data class AdolescentHealthCache( + @PrimaryKey(autoGenerate = true) + var id :Int? = null, + var userID :Int? =null, + var benId:Long?=null, + var visitDate: Long = System.currentTimeMillis(), + var healthStatus: String? = null, + var ifaTabletDistributed: Boolean? = null, + var quantityOfIfaTablets: Int? = null, + var menstrualHygieneAwarenessGiven: Boolean? = null, + var sanitaryNapkinDistributed: Boolean? = null, + var noOfPacketsDistributed: Int? = null, + var place: String? = null, + var distributionDate: Long = System.currentTimeMillis(), + var referredToHealthFacility: String? = null, + var counselingProvided: Boolean? = null, + var counselingType: String? = null, + var followUpDate: Long = System.currentTimeMillis(), + var referralStatus: String? = null, + var syncState: SyncState = SyncState.UNSYNCED, +) : FormDataModel{ + fun toDTO(): AdolscentHealthDTO { + return AdolscentHealthDTO( + id = 0, + benId = benId!!, + visitDate = getDateTimeStringFromLong(visitDate).toString(), + healthStatus = healthStatus, + ifaTabletDistributed = ifaTabletDistributed, + quantityOfIfaTablets = quantityOfIfaTablets, + menstrualHygieneAwarenessGiven = menstrualHygieneAwarenessGiven, + sanitaryNapkinDistributed = sanitaryNapkinDistributed, + noOfPacketsDistributed = noOfPacketsDistributed, + place = place, + distributionDate = getDateTimeStringFromLong(distributionDate), + referredToHealthFacility = referredToHealthFacility, + counselingProvided = counselingProvided, + counselingType = counselingType, + followUpDate = getDateTimeStringFromLong(followUpDate).toString(), + referralStatus = referralStatus + + + ) + } +} +data class BenWithAdolescentCache( + @Embedded + val ben: BenBasicCache, + @Relation( + parentColumn = "benId", entityColumn = "benId" + ) + val tb: AdolescentHealthCache?, + + ) { + fun asAdolescentDomainModel(): BenWithAdolescentDomain { + return BenWithAdolescentDomain( + ben = ben.asBasicDomainModel(), + adolescent = tb + ) + } +} + +data class BenWithAdolescentDomain( + val ben: BenBasicDomain, + val adolescent: AdolescentHealthCache? +) \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/Asha.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/Asha.kt new file mode 100644 index 000000000..52b671a6a --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/Asha.kt @@ -0,0 +1,100 @@ +import androidx.room.Entity +import androidx.room.PrimaryKey +import com.squareup.moshi.JsonClass + +@Entity(tableName = "ASHA") +data class AshaCache( + @PrimaryKey(autoGenerate = true) + val id: Int = 0, + var userId: Int, + var usrMappingId: Int, + var name: String, + var userName: String, + var serviceId: Int, + var serviceName: String, + var stateId: Int, + var stateName: String, + var workingDistrictId: Int?, + var workingDistrictName: String?, + var workingLocationId: Int?, + var serviceProviderId: Int, + var locationName: String?, + var workingLocationAddress: String?, + var roleId: Int, + var roleName: String, + var providerServiceMapId: Int, + var blockid: Int, + var blockname: String, + var villageid: String, + var villagename: String +) + +@JsonClass(generateAdapter = true) +data class AshaNetwork( + var userId: Int = 0, + var usrMappingId: Int = 0, + var name: String = "", + var userName: String = "", + var serviceId: Int = 0, + var serviceName: String = "", + var stateId: Int = 0, + var stateName: String = "", + var workingDistrictId: Int? = 0, + var workingDistrictName: String? = "", + var workingLocationId: Int? = 0, + var serviceProviderId: Int = 0, + var locationName: String? = "", + var workingLocationAddress: String? = "", + var roleId: Int = 0, + var roleName: String = "", + var providerServiceMapId: Int = 0, + var agentId: Any?, + var psmStatusId: Int = 0, + var psmStatus: String = "", + var userServciceRoleDeleted: Boolean, + var userDeleted: Boolean, + var serviceProviderDeleted: Boolean, + var roleDeleted: Boolean, + var providerServiceMappingDeleted: Boolean, + var blockid: Int = 0, + var blockname: String = "", + var villageid: String = "", + var villagename: String = "", + var national: Boolean, + var inbound: Any?, + var outbound: Any?, +) { + fun asCacheModel(): AshaCache { + return AshaCache( + userId = userId, + usrMappingId = usrMappingId, + name = name, + userName = userName, + serviceId = serviceId, + serviceName = serviceName, + stateId = stateId, + stateName = stateName, + workingDistrictId = workingDistrictId, + workingDistrictName = workingDistrictName, + workingLocationId = workingLocationId, + serviceProviderId = serviceProviderId, + locationName = locationName, + workingLocationAddress = workingLocationAddress, + roleId = roleId, + roleName = roleName, + providerServiceMapId = providerServiceMapId, + blockid = blockid, + blockname = blockname, + villageid = villageid, + villagename = villagename, + ) + } +} + + +@JsonClass(generateAdapter = true) +data class AshaListResponse( + val data: AshaNetwork, + val statusCode: Int, + val status: String +) \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/AshaProfile.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/AshaProfile.kt new file mode 100644 index 000000000..62bc65419 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/AshaProfile.kt @@ -0,0 +1,169 @@ +package org.piramalswasthya.sakhi.model + +import androidx.room.Embedded +import androidx.room.Entity +import androidx.room.PrimaryKey +import com.squareup.moshi.JsonClass + + +@Entity(tableName = "PROFILE_ACTIVITY") +data class ProfileActivityCache( + @PrimaryKey + val id: Long, + var name: String?="", + var profileImage: String = "", + var village:String = "", + val employeeId:Int = 0, + var dob:String = "", + var age:Int = 0, + var mobileNumber:String = "", + var alternateMobileNumber:String = "", + var fatherOrSpouseName:String = "", + var dateOfJoining: String = "", + var bankAccount: String = "", + var ifsc: String = "", + var populationCovered: Int = 0, + var choName: String = "", + var choMobile: String = "", + var awwName: String = "", + var awwMobile: String = "", + var anm1Name: String = "", + var anm1Mobile: String = "", + var anm2Name: String = "", + var anm2Mobile: String = "", + var abhaNumber: String = "", + var ashaHouseholdRegistration: String = "", + var ashaFamilyMember: String = "", + var providerServiceMapID: String = " ", + var isFatherOrSpouse: Boolean = false, + var supervisorName: String = "", + var supervisorMobile: String = "", + ) + +@JsonClass(generateAdapter = true) +data class ProfileActivityNetwork( + val id: Long=0L, + val name: String?=null, + var profileImage: String? = null, + val village:String?=null, + val employeeId:Int?=0, + val dob:String?=null, + val age:Int?=0, + val mobileNumber:String?=null, + val alternateMobileNumber:String?=null, + val fatherOrSpouseName:String?=null, + val dateOfJoining: String?=null, + val bankAccount: String?=null, + val ifsc: String?=null, + val populationCovered: Int?=0, + val choName: String?=null, + val choMobile: String?=null, + val awwName: String?=null, + val awwMobile: String?=null, + val anm1Name: String?=null, + val anm1Mobile: String?=null, + val anm2Name: String?=null, + val anm2Mobile: String?=null, + val abhaNumber: String?=null, + val ashaHouseholdRegistration: String?=null, + val ashaFamilyMember: String?=null, + val providerServiceMapID: String?=null, + val isFatherOrSpouse: Boolean?=null, + var supervisorName: String?="", + var supervisorMobile: String?="", +) { + fun asCacheModel(): ProfileActivityCache { + return ProfileActivityCache( + id = id, + name = name, + profileImage = profileImage.toString(), + village = village.toString(), + employeeId = employeeId!!, + dob = dob.toString(), + age = age!!, + mobileNumber = mobileNumber.toString(), + alternateMobileNumber = alternateMobileNumber.toString(), + fatherOrSpouseName = fatherOrSpouseName.toString(), + dateOfJoining = dateOfJoining.toString(), + bankAccount = bankAccount.toString(), + ifsc = ifsc.toString(), + populationCovered = populationCovered!!, + choName = choName.toString(), + choMobile = choMobile.toString(), + awwName = awwName.toString(), + awwMobile = awwMobile.toString(), + anm1Name = anm1Name.toString(), + anm1Mobile = anm1Mobile.toString(), + anm2Name = anm2Name.toString(), + anm2Mobile = anm2Mobile.toString(), + abhaNumber = abhaNumber.toString(), + ashaHouseholdRegistration = ashaHouseholdRegistration.toString(), + ashaFamilyMember = ashaFamilyMember.toString(), + providerServiceMapID = providerServiceMapID.toString(), + isFatherOrSpouse = isFatherOrSpouse == false, + supervisorName = supervisorName.toString(), + supervisorMobile = supervisorMobile.toString(), + + ) + } +} + + +@JsonClass(generateAdapter = true) +data class ProfileActivityListResponse( + val data: ProfileActivityNetwork, + val statusCode: Int, + val status: String +) + + + +data class ProfileCache( + @Embedded + val activity: ProfileActivityCache, +) { + fun asDomainModel(): ProfileDomain { + return ProfileDomain( + activity, + ) + } +} + +data class ProfileDomain( + val activity: ProfileActivityCache, +) + +data class ProfileDomainDTO( + val id: Long, + val name: String, + var profileImage: String? = null, + val village:String, + val employeeId:Int, + val dob:String, + val age:Int, + val mobileNumber:String, + val alternateMobileNumber:String, + val fatherOrSpouseName:String, + val dateOfJoining: String, + val bankAccount: String, + val ifsc: String, + val populationCovered: Int, + val choName: String, + val choMobile: String, + val awwName: String, + val awwMobile: String, + val anm1Name: String, + val anm1Mobile: String, + val anm2Name: String, + val anm2Mobile: String, + val abhaNumber: String, + val ashaHouseholdRegistration: String, + val ashaFamilyMember: String, + val providerServiceMapID: String, + val isFatherOrSpouse: Boolean, + val supervisorName: String, + val supervisorMobile: String +) + + + diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/Ashas.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/Ashas.kt new file mode 100644 index 000000000..e94c9694f --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/Ashas.kt @@ -0,0 +1,44 @@ +import androidx.room.Entity +import androidx.room.PrimaryKey +import com.squareup.moshi.JsonClass + +data class Ashas( + var success: Boolean, + var message: Any?, + var `data`: List +) { + data class Data( + var userId: Int, + var usrMappingId: Int, + var name: String, + var userName: String, + var serviceId: Int, + var serviceName: String, + var stateId: Int, + var stateName: String, + var workingDistrictId: Int?, + var workingDistrictName: String?, + var workingLocationId: Int?, + var serviceProviderId: Int, + var locationName: String?, + var workingLocationAddress: String?, + var roleId: Int, + var roleName: String, + var providerServiceMapId: Int, + var agentId: Any?, + var psmStatusId: Int, + var psmStatus: String, + var userServciceRoleDeleted: Boolean, + var userDeleted: Boolean, + var serviceProviderDeleted: Boolean, + var roleDeleted: Boolean, + var providerServiceMappingDeleted: Boolean, + var blockid: Int, + var blockname: String, + var villageid: String, + var villagename: String, + var national: Boolean, + var inbound: Any?, + var outbound: Any? + ) +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/Ben.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/Ben.kt index 30996f16b..db7c55e93 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/model/Ben.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/Ben.kt @@ -1,6 +1,7 @@ package org.piramalswasthya.sakhi.model import android.content.Context +import android.os.Parcelable import androidx.room.ColumnInfo import androidx.room.DatabaseView import androidx.room.Embedded @@ -9,6 +10,7 @@ import androidx.room.ForeignKey import androidx.room.Index import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import kotlinx.parcelize.Parcelize import org.piramalswasthya.sakhi.configuration.FormDataModel import org.piramalswasthya.sakhi.database.room.SyncState import org.piramalswasthya.sakhi.helpers.ImageUtils @@ -48,69 +50,90 @@ enum class Gender { TRANSGENDER } +enum class BenStatus { + Alive, + Death, +} + +// In your BenBasicCache.kt file, REPLACE the old @DatabaseView with this one. @DatabaseView( viewName = "BEN_BASIC_CACHE", - value = "SELECT b.beneficiaryId as benId, b.householdId as hhId, b.regDate, b.firstName as benName, b.lastName as benSurname, b.gender, b.dob as dob, b.familyHeadRelationPosition as relToHeadId" + - ", b.contactNumber as mobileNo, b.fatherName, h.fam_familyHeadName as familyHeadName, b.gen_spouseName as spouseName,b.rchId, b.gen_lastMenstrualPeriod as lastMenstrualPeriod" + - ", b.isHrpStatus as hrpStatus, b.syncState, b.gen_reproductiveStatusId as reproductiveStatusId, b.isKid, b.immunizationStatus, b.gen_spouseName as spouseName," + - " b.loc_village_id as villageId, b.abha_healthIdNumber as abhaId," + - " cbac.benId is not null as cbacFilled, cbac.syncState as cbacSyncState," + - " cdr.benId is not null as cdrFilled, cdr.syncState as cdrSyncState, " + - " mdsr.benId is not null as mdsrFilled, mdsr.syncState as mdsrSyncState," + - " pmsma.benId is not null as pmsmaFilled, pmsma.syncState as pmsmaSyncState, " + - " hbnc.benId is not null as hbncFilled, " + - " hbyc.benId is not null as hbycFilled, " + - " pwr.benId is not null as pwrFilled, pwr.syncState as pwrSyncState," + - " pwa.pregnantWomanDelivered as isDelivered, pwa.hrpConfirmed as pwHrp," + - " ecr.benId is not null as ecrFilled, " + - " ect.benId is not null as ectFilled, (pwa.maternalDeath or do.complication = 'DEATH' or pnc.motherDeath) as isMdsr," + - " ect.benId is not null as ectFilled, " + - " tbsn.benId is not null as tbsnFilled, tbsn.syncState as tbsnSyncState," + - " tbsp.benId is not null as tbspFilled, tbsp.syncState as tbspSyncState, " + - " ir.motherBenId is not null as irFilled, ir.syncState as irSyncState, " + - " cr.motherBenId is not null as crFilled, cr.syncState as crSyncState, " + - " do.benId is not null as doFilled, do.syncState as doSyncState, " + - " (hrppa.benId is not null and hrppa.noOfDeliveries is not null and hrppa.timeLessThan18m is not null and hrppa.heightShort is not null and hrppa.age is not null and hrppa.rhNegative is not null and hrppa.homeDelivery is not null and hrppa.badObstetric is not null and hrppa.multiplePregnancy is not null) as hrppaFilled, hrppa.syncState as hrppaSyncState," + - " (hrpnpa.benId is not null and hrpnpa.noOfDeliveries is not null and hrpnpa.timeLessThan18m is not null and hrpnpa.heightShort is not null and hrpnpa.age is not null and hrpnpa.misCarriage is not null and hrpnpa.homeDelivery is not null and hrpnpa.medicalIssues is not null and hrpnpa.pastCSection is not null )as hrpnpaFilled, hrpnpa.syncState as hrpnpaSyncState," + - " hrpmbp.benId is not null as hrpmbpFilled, hrpmbp.syncState as hrpmbpSyncState," + - " hrpt.benId is not null as hrptFilled, ((count(distinct hrpt.id) > 3) or (((JulianDay('now')) - JulianDay(date(max(hrpt.visitDate)/1000,'unixepoch','localtime'))) < 1 ))as hrptrackingDone, hrpt.syncState as hrptSyncState," + - " hrnpt.benId is not null as hrnptFilled,((JulianDay('now') - JulianDay(date(max(hrnpt.visitDate)/1000,'unixepoch','localtime'))) < 1 ) as hrnptrackingDone, hrnpt.syncState as hrnptSyncState " + - "from BENEFICIARY b " + + value = "SELECT b.beneficiaryId as benId,b.isMarried,b.noOfAliveChildren, b.noOfChildren, b.doYouHavechildren ,b.isConsent as isConsent, b.motherName as motherName, b.householdId as hhId, b.regDate, b.firstName as benName, b.lastName as benSurname, b.gender, b.dob as dob,b.isDeactivate, b.isDeath,b.isDeathValue,b.dateOfDeath,b.timeOfDeath,b.reasonOfDeath,b.reasonOfDeathId,b.placeOfDeath,b.placeOfDeathId,b.otherPlaceOfDeath,b.isSpouseAdded,b.isChildrenAdded, b.familyHeadRelationPosition as relToHeadId" + + ", b.contactNumber as mobileNo, b.fatherName,h.fam_familyHeadName as familyHeadName, b.gen_spouseName as spouseName, b.rchId, b.gen_lastMenstrualPeriod as lastMenstrualPeriod" + + ", b.isHrpStatus as hrpStatus, b.syncState, b.gen_reproductiveStatusId as reproductiveStatusId, b.isKid, b.immunizationStatus" + + ", b.loc_village_id as villageId, b.abha_healthIdNumber as abhaId" + + ", b.isNewAbha" + // FIX: Using only one, correct source for isNewAbha. + ", IFNULL(cbac.benId IS NOT NULL, 0) as cbacFilled, cbac.syncState as cbacSyncState" + + ", IFNULL(cdr.benId IS NOT NULL, 0) as cdrFilled, cdr.syncState as cdrSyncState" + + ", IFNULL(mdsr.benId IS NOT NULL, 0) as mdsrFilled, mdsr.syncState as mdsrSyncState" + + ", IFNULL(pmsma.benId IS NOT NULL, 0) as pmsmaFilled, pmsma.syncState as pmsmaSyncState" + + ", IFNULL(hbnc.benId IS NOT NULL, 0) as hbncFilled" + + ", IFNULL(hbyc.benId IS NOT NULL, 0) as hbycFilled" + + ", IFNULL(pwr.benId IS NOT NULL, 0) as pwrFilled, pwr.syncState as pwrSyncState" + + ", IFNULL(pwa.pregnantWomanDelivered, 0) as isDelivered, IFNULL(pwa.hrpConfirmed, 0) as pwHrp" + + ", IFNULL(ecr.benId IS NOT NULL, 0) as ecrFilled" + + ", IFNULL(ect.benId IS NOT NULL, 0) as ectFilled" + // FIX: Removed duplicate ectFilled and used a safe version. + ", IFNULL((pwa.maternalDeath OR do.complication = 'DEATH' OR pnc.motherDeath), 0) as isMdsr" + + ", IFNULL(tbsn.benId IS NOT NULL, 0) as tbsnFilled, tbsn.syncState as tbsnSyncState" + + ", IFNULL(tbsp.benId IS NOT NULL, 0) as tbspFilled, tbsp.syncState as tbspSyncState" + + ", IFNULL(ir.motherBenId IS NOT NULL, 0) as irFilled, ir.syncState as irSyncState" + + ", IFNULL(cr.motherBenId IS NOT NULL, 0) as crFilled, cr.syncState as crSyncState" + + ", IFNULL(do.benId IS NOT NULL, 0) as doFilled, do.syncState as doSyncState" + + ", IFNULL((hrppa.benId IS NOT NULL AND hrppa.noOfDeliveries IS NOT NULL AND hrppa.timeLessThan18m IS NOT NULL AND hrppa.heightShort IS NOT NULL AND hrppa.age IS NOT NULL AND hrppa.rhNegative IS NOT NULL AND hrppa.homeDelivery IS NOT NULL AND hrppa.badObstetric IS NOT NULL AND hrppa.multiplePregnancy IS NOT NULL), 0) as hrppaFilled, hrppa.syncState as hrppaSyncState" + + ", IFNULL((hrpnpa.benId IS NOT NULL AND hrpnpa.noOfDeliveries IS NOT NULL AND hrpnpa.timeLessThan18m IS NOT NULL AND hrpnpa.heightShort IS NOT NULL AND hrpnpa.age IS NOT NULL AND hrpnpa.misCarriage IS NOT NULL AND hrpnpa.homeDelivery IS NOT NULL AND hrpnpa.medicalIssues IS NOT NULL AND hrpnpa.pastCSection IS NOT NULL), 0) as hrpnpaFilled, hrpnpa.syncState as hrpnpaSyncState" + + ", IFNULL(hrpmbp.benId IS NOT NULL, 0) as hrpmbpFilled, hrpmbp.syncState as hrpmbpSyncState" + + ", IFNULL(hrpt.benId IS NOT NULL, 0) as hrptFilled, IFNULL(((count(distinct hrpt.id) > 3) OR (((JulianDay('now')) - JulianDay(date(max(hrpt.visitDate)/1000,'unixepoch','localtime'))) < 1)), 0) as hrptrackingDone, hrpt.syncState as hrptSyncState" + + ", IFNULL(hrnpt.benId IS NOT NULL, 0) as hrnptFilled, IFNULL(((JulianDay('now') - JulianDay(date(max(hrnpt.visitDate)/1000,'unixepoch','localtime'))) < 1), 0) as hrnptrackingDone, hrnpt.syncState as hrnptSyncState " + + "FROM BENEFICIARY b " + "JOIN HOUSEHOLD h ON b.householdId = h.householdId " + - "LEFT OUTER JOIN CBAC cbac on b.beneficiaryId = cbac.benId " + - "LEFT OUTER JOIN CDR cdr on b.beneficiaryId = cdr.benId " + - "LEFT OUTER JOIN MDSR mdsr on b.beneficiaryId = mdsr.benId " + - "LEFT OUTER JOIN PMSMA pmsma on b.beneficiaryId = pmsma.benId " + - "LEFT OUTER JOIN HBNC hbnc on b.beneficiaryId = hbnc.benId " + - "LEFT OUTER JOIN HBYC hbyc on b.beneficiaryId = hbyc.benId " + - "LEFT OUTER JOIN PREGNANCY_REGISTER pwr on b.beneficiaryId = pwr.benId " + - "LEFT OUTER JOIN PREGNANCY_ANC pwa on b.beneficiaryId = pwa.benId " + - "LEFT OUTER JOIN pnc_visit pnc on b.beneficiaryId = pnc.benId " + - "LEFT OUTER JOIN ELIGIBLE_COUPLE_REG ecr on b.beneficiaryId = ecr.benId " + - "LEFT OUTER JOIN ELIGIBLE_COUPLE_TRACKING ect on (b.beneficiaryId = ect.benId and CAST((strftime('%s','now') - ect.visitDate/1000)/60/60/24 AS INTEGER) < 30 )" + - "LEFT OUTER JOIN TB_SCREENING tbsn on b.beneficiaryId = tbsn.benId " + - "LEFT OUTER JOIN TB_SUSPECTED tbsp on b.beneficiaryId = tbsp.benId " + - "LEFT OUTER JOIN HRP_PREGNANT_ASSESS hrppa on b.beneficiaryId = hrppa.benId " + - "LEFT OUTER JOIN HRP_NON_PREGNANT_ASSESS hrpnpa on b.beneficiaryId = hrpnpa.benId " + - "LEFT OUTER JOIN HRP_MICRO_BIRTH_PLAN hrpmbp on b.beneficiaryId = hrpmbp.benId " + - "LEFT OUTER JOIN HRP_NON_PREGNANT_TRACK hrnpt on b.beneficiaryId = hrnpt.benId " + - "LEFT OUTER JOIN HRP_PREGNANT_TRACK hrpt on b.beneficiaryId = hrpt.benId " + - "LEFT OUTER JOIN DELIVERY_OUTCOME do on b.beneficiaryId = do.benId " + - "LEFT OUTER JOIN INFANT_REG ir on b.beneficiaryId = ir.motherBenId " + - "LEFT OUTER JOIN CHILD_REG cr on b.beneficiaryId = cr.motherBenId " + - "where b.isDraft = 0 GROUP BY b.beneficiaryId ORDER BY b.updatedDate DESC" + "LEFT OUTER JOIN CBAC cbac ON b.beneficiaryId = cbac.benId " + + "LEFT OUTER JOIN CDR cdr ON b.beneficiaryId = cdr.benId " + + "LEFT OUTER JOIN MDSR mdsr ON b.beneficiaryId = mdsr.benId " + + "LEFT OUTER JOIN PMSMA pmsma ON b.beneficiaryId = pmsma.benId " + + "LEFT OUTER JOIN HBNC hbnc ON b.beneficiaryId = hbnc.benId " + + "LEFT OUTER JOIN HBYC hbyc ON b.beneficiaryId = hbyc.benId " + + "LEFT OUTER JOIN PREGNANCY_REGISTER pwr ON b.beneficiaryId = pwr.benId " + + "LEFT OUTER JOIN PREGNANCY_ANC pwa ON b.beneficiaryId = pwa.benId " + + "LEFT OUTER JOIN pnc_visit pnc ON b.beneficiaryId = pnc.benId " + + "LEFT OUTER JOIN ELIGIBLE_COUPLE_REG ecr ON b.beneficiaryId = ecr.benId " + + "LEFT OUTER JOIN ELIGIBLE_COUPLE_TRACKING ect ON (b.beneficiaryId = ect.benId AND CAST((strftime('%s','now') - ect.visitDate/1000)/60/60/24 AS INTEGER) < 30) " + + "LEFT OUTER JOIN TB_SCREENING tbsn ON b.beneficiaryId = tbsn.benId " + + "LEFT OUTER JOIN TB_SUSPECTED tbsp ON b.beneficiaryId = tbsp.benId " + + "LEFT OUTER JOIN MALARIA_SCREENING masp on b.beneficiaryId = masp.benId " + + "LEFT OUTER JOIN MALARIA_CONFIRMED macp on b.beneficiaryId = macp.benId " + + "LEFT OUTER JOIN HRP_PREGNANT_ASSESS hrppa ON b.beneficiaryId = hrppa.benId " + + "LEFT OUTER JOIN HRP_NON_PREGNANT_ASSESS hrpnpa ON b.beneficiaryId = hrpnpa.benId " + + "LEFT OUTER JOIN HRP_MICRO_BIRTH_PLAN hrpmbp ON b.beneficiaryId = hrpmbp.benId " + + "LEFT OUTER JOIN HRP_NON_PREGNANT_TRACK hrnpt ON b.beneficiaryId = hrnpt.benId " + + "LEFT OUTER JOIN HRP_PREGNANT_TRACK hrpt ON b.beneficiaryId = hrpt.benId " + + "LEFT OUTER JOIN DELIVERY_OUTCOME do ON b.beneficiaryId = do.benId " + + "LEFT OUTER JOIN INFANT_REG ir ON b.beneficiaryId = ir.motherBenId " + + "LEFT OUTER JOIN CHILD_REG cr ON b.beneficiaryId = cr.motherBenId " + + "WHERE b.isDraft = 0 GROUP BY b.beneficiaryId ORDER BY b.updatedDate DESC" ) +@Parcelize data class BenBasicCache( + @ColumnInfo val benId: Long, val hhId: Long, val regDate: Long, - val benName: String, + var isDeath: Boolean = false, + var isDeathValue: String? = null, + var dateOfDeath: String? = null, + var timeOfDeath: String? = null, + var reasonOfDeath: String? = null, + var reasonOfDeathId: Int? = null, + var placeOfDeath: String? = null, + var placeOfDeathId: Int? = null, + var otherPlaceOfDeath: String? = null, + val benName: String?, val benSurname: String? = null, val gender: Gender, val dob: Long, val relToHeadId: Int, val mobileNo: Long, val fatherName: String? = null, + val motherName: String?= null, val familyHeadName: String? = null, // val typeOfList: TypeOfList, val spouseName: String? = null, @@ -123,6 +146,7 @@ data class BenBasicCache( val immunizationStatus: Boolean, val villageId: Int, val abhaId: String?, + val isNewAbha: Boolean, val cbacFilled: Boolean, val cbacSyncState: SyncState?, val cdrFilled: Boolean, @@ -162,7 +186,15 @@ data class BenBasicCache( val isMdsr: Boolean, val crFilled: Boolean, val doFilled: Boolean, -) { + val isConsent: Boolean, + var isSpouseAdded: Boolean, + var isChildrenAdded: Boolean, + var isMarried: Boolean, + var doYouHavechildren: Boolean = false, + var noOfChildren: Int = 0, + var noOfAliveChildren: Int = 0, + var isDeactivate: Boolean =false +) : Parcelable { companion object { val dateFormat = SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH) fun getAgeFromDob(dob: Long): Int { @@ -186,6 +218,22 @@ data class BenBasicCache( } + fun getYearsFromDate(dateString: String): Int { + val sdf = SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH) + val inputDate = sdf.parse(dateString) ?: return -1 + + val dobCalendar = Calendar.getInstance() + dobCalendar.time = inputDate + val today = Calendar.getInstance() + var years = today.get(Calendar.YEAR) - dobCalendar.get(Calendar.YEAR) + if (today.get(Calendar.DAY_OF_YEAR) < dobCalendar.get(Calendar.DAY_OF_YEAR)) { + years-- + } + + return years + } + + fun getAgeUnitFromDob(dob: Long): AgeUnit { val calDob = Calendar.getInstance().apply { timeInMillis = dob @@ -202,6 +250,7 @@ data class BenBasicCache( } + private fun getDiffYears(a: Calendar, b: Calendar): Int { var diff = b.get(Calendar.YEAR) - a.get(Calendar.YEAR) if (a.get(Calendar.MONTH) > b.get(Calendar.MONTH) || a.get(Calendar.MONTH) == b.get( @@ -225,21 +274,45 @@ data class BenBasicCache( return BenBasicDomain( benId = benId, hhId = hhId, + isDeath = isDeath, + isDeathValue = isDeathValue, + dateOfDeath = dateOfDeath, + reproductiveStatusId = reproductiveStatusId, + timeOfDeath = timeOfDeath, + reasonOfDeath = reasonOfDeath, + reasonOfDeathId = reasonOfDeathId, + placeOfDeath = placeOfDeath, + placeOfDeathId = placeOfDeathId, + otherPlaceOfDeath = otherPlaceOfDeath, regDate = dateFormat.format(Date(regDate)), - benName = benName, + benName = benName.orEmpty(), benSurname = benSurname ?: "", gender = gender.name, dob = dob, abhaId = abhaId, + isNewAbha = isNewAbha, relToHeadId = relToHeadId, mobileNo = mobileNo.toString(), + motherName = motherName?.takeIf { it.isNotEmpty() } ?: "Not Available", fatherName = fatherName?.takeIf { it.isNotEmpty() } ?: "Not Available", + /* motherName = motherName,*/ familyHeadName = familyHeadName ?: "Not Available", // typeOfList = typeOfList.name, spouseName = spouseName?.takeIf { it.isNotEmpty() } ?: "Not Available", - rchId = rchId?.takeIf { it.isNotEmpty() } ?: "Not Available", + rchId = rchId?.takeIf { it.isNotEmpty() }, hrpStatus = hrpStatus, - syncState = syncState + syncState = syncState, + isConsent = isConsent, + isChildrenAdded = isChildrenAdded, + isSpouseAdded = isSpouseAdded, + isMarried = isMarried, + noOfAliveChildren = noOfAliveChildren, + noOfChildren = noOfChildren, + doYouHavechildren = doYouHavechildren, + isDeactivate = isDeactivate + + + ) } @@ -248,7 +321,7 @@ data class BenBasicCache( benId = benId, hhId = hhId, regDate = dateFormat.format(Date(regDate)), - benName = benName, + benName = benName.orEmpty(), benSurname = benSurname ?: "", spouseName = spouseName ?: "Not Available", gender = gender.name, @@ -257,10 +330,18 @@ data class BenBasicCache( mobileNo = mobileNo.toString(), fatherName = fatherName, familyHeadName = familyHeadName ?: "", - rchId = rchId ?: "Not Available", + rchId = rchId, hrpStatus = hrpStatus, relToHeadId = 0, - syncState = syncState + syncState = syncState, + isConsent = isConsent, + isChildrenAdded = isChildrenAdded, + isSpouseAdded = isSpouseAdded, + isMarried = isMarried, + noOfAliveChildren = noOfAliveChildren, + noOfChildren = noOfChildren, + doYouHavechildren = doYouHavechildren, + reproductiveStatusId = reproductiveStatusId ) } @@ -270,7 +351,7 @@ data class BenBasicCache( benId = benId, hhId = hhId, regDate = dateFormat.format(Date(regDate)), - benName = benName, + benName = benName.orEmpty(), benSurname = benSurname ?: "Not Available", gender = gender.name, dob = dob, @@ -278,11 +359,12 @@ data class BenBasicCache( fatherName = fatherName, familyHeadName = familyHeadName ?: "Not Available", // typeOfList = typeOfList.name, - rchId = rchId ?: "Not Available", + rchId = rchId, hrpStatus = hrpStatus, form1Filled = tbsnFilled, syncState = tbsnSyncState - ?: throw IllegalStateException("Sync state for tbsn is null!!") + ?: throw IllegalStateException("Sync state for tbsn is null!!"), + isConsent = isConsent ) } @@ -291,7 +373,7 @@ data class BenBasicCache( benId = benId, hhId = hhId, regDate = dateFormat.format(Date(regDate)), - benName = benName, + benName = benName.orEmpty(), benSurname = benSurname ?: "Not Available", gender = gender.name, dob = dob, @@ -299,11 +381,12 @@ data class BenBasicCache( fatherName = fatherName, familyHeadName = familyHeadName ?: "Not Available", // typeOfList = typeOfList.name, - rchId = rchId ?: "Not Available", + rchId = rchId, hrpStatus = hrpStatus, form1Filled = tbspFilled, syncState = tbspSyncState - ?: throw IllegalStateException("Sync state for tbsp is null!!") + ?: throw IllegalStateException("Sync state for tbsp is null!!"), + isConsent = isConsent ) } @@ -311,8 +394,17 @@ data class BenBasicCache( return BenBasicDomainForForm( benId = benId, hhId = hhId, + isDeath = isDeath, + isDeathValue = isDeathValue, + dateOfDeath = dateOfDeath, + timeOfDeath = timeOfDeath, + reasonOfDeath = reasonOfDeath, + reasonOfDeathId = reasonOfDeathId, + placeOfDeath = placeOfDeath, + placeOfDeathId = placeOfDeathId, + otherPlaceOfDeath = otherPlaceOfDeath, regDate = dateFormat.format(Date(regDate)), - benName = benName, + benName = benName.orEmpty(), benSurname = benSurname ?: "Not Available", gender = gender.name, dob = dob, @@ -320,11 +412,12 @@ data class BenBasicCache( fatherName = fatherName, familyHeadName = familyHeadName ?: "Not Available", // typeOfList = typeOfList.name, - rchId = rchId ?: "Not Available", + rchId = rchId, hrpStatus = hrpStatus, form1Filled = cdrFilled, syncState = cdrSyncState - ?: throw IllegalStateException("Sync state for cbac is null!!") + ?: throw IllegalStateException("Sync state for cbac is null!!"), + isConsent = isConsent ) } @@ -332,8 +425,17 @@ data class BenBasicCache( return BenBasicDomainForForm( benId = benId, hhId = hhId, + isDeath = isDeath, + isDeathValue = isDeathValue, + dateOfDeath = dateOfDeath, + timeOfDeath = timeOfDeath, + reasonOfDeath = reasonOfDeath, + reasonOfDeathId = reasonOfDeathId, + placeOfDeath = placeOfDeath, + placeOfDeathId = placeOfDeathId, + otherPlaceOfDeath = otherPlaceOfDeath, regDate = dateFormat.format(Date(regDate)), - benName = benName, + benName = benName.orEmpty(), benSurname = benSurname ?: "Not Available", gender = gender.name, dob = dob, @@ -341,11 +443,12 @@ data class BenBasicCache( fatherName = fatherName, familyHeadName = familyHeadName ?: "Not Available", // typeOfList = typeOfList.name, - rchId = rchId ?: "Not Available", + rchId = rchId, hrpStatus = hrpStatus, form1Filled = mdsrFilled, syncState = mdsrSyncState - ?: throw IllegalStateException("Sync state for mdsr is null!!") + ?: throw IllegalStateException("Sync state for mdsr is null!!"), + isConsent = isConsent ) } @@ -354,7 +457,7 @@ data class BenBasicCache( benId = benId, hhId = hhId, regDate = dateFormat.format(Date(regDate)), - benName = benName, + benName = benName.orEmpty(), benSurname = benSurname ?: "Not Available", gender = gender.name, dob = dob, @@ -362,10 +465,11 @@ data class BenBasicCache( fatherName = fatherName, familyHeadName = familyHeadName ?: "Not Available", // typeOfList = typeOfList.name, - rchId = rchId ?: "Not Available", + rchId = rchId, hrpStatus = hrpStatus, form1Filled = pmsmaFilled, - syncState = syncState + syncState = syncState, + isConsent = isConsent ) } @@ -374,7 +478,7 @@ data class BenBasicCache( benId = benId, hhId = hhId, regDate = dateFormat.format(Date(regDate)), - benName = benName, + benName = benName.orEmpty(), benSurname = benSurname ?: "Not Available", gender = gender.name, dob = dob, @@ -382,10 +486,11 @@ data class BenBasicCache( fatherName = fatherName, familyHeadName = familyHeadName ?: "Not Available", // typeOfList = typeOfList.name, - rchId = rchId ?: "Not Available", + rchId = rchId, hrpStatus = hrpStatus, form1Filled = ectFilled, - syncState = syncState + syncState = syncState, + isConsent = isConsent ) } @@ -394,7 +499,7 @@ data class BenBasicCache( benId = benId, hhId = hhId, regDate = dateFormat.format(Date(regDate)), - benName = benName, + benName = benName.orEmpty(), benSurname = benSurname ?: "Not Available", gender = gender.name, dob = dob, @@ -402,10 +507,11 @@ data class BenBasicCache( fatherName = fatherName, familyHeadName = familyHeadName ?: "Not Available", // typeOfList = typeOfList.name, - rchId = rchId ?: "Not Available", + rchId = rchId, hrpStatus = hrpStatus, form1Filled = false, - syncState = syncState + syncState = syncState, + isConsent = isConsent ) } @@ -414,7 +520,7 @@ data class BenBasicCache( benId = benId, hhId = hhId, regDate = dateFormat.format(Date(regDate)), - benName = benName, + benName = benName.orEmpty(), benSurname = benSurname ?: "Not Available", gender = gender.name, dob = dob, @@ -422,13 +528,14 @@ data class BenBasicCache( fatherName = fatherName, familyHeadName = familyHeadName ?: "Not Available", // typeOfList = typeOfList.name, - rchId = rchId ?: "Not Available", + rchId = rchId, hrpStatus = hrpStatus, form1Filled = false, form1Enabled = hbncFilled || dob > (System.currentTimeMillis() - TimeUnit.DAYS.toMillis( 42 )), - syncState = syncState + syncState = syncState, + isConsent = false ) } @@ -437,7 +544,7 @@ data class BenBasicCache( benId = benId, hhId = hhId, regDate = dateFormat.format(Date(regDate)), - benName = benName, + benName = benName.orEmpty(), benSurname = benSurname ?: "Not Available", gender = gender.name, dob = dob, @@ -445,13 +552,14 @@ data class BenBasicCache( fatherName = fatherName, familyHeadName = familyHeadName ?: "Not Available", // typeOfList = typeOfList.name, - rchId = rchId ?: "Not Available", + rchId = rchId, hrpStatus = hrpStatus, syncState = syncState, form1Filled = false, form1Enabled = hbycFilled || dob > (System.currentTimeMillis() - TimeUnit.DAYS.toMillis( 490 )), + isConsent = false ) } @@ -460,7 +568,7 @@ data class BenBasicCache( benId = benId, hhId = hhId, regDate = dateFormat.format(Date(regDate)), - benName = benName, + benName = benName.orEmpty(), benSurname = benSurname ?: "Not Available", gender = gender.name, dob = dob, @@ -468,10 +576,11 @@ data class BenBasicCache( fatherName = fatherName, familyHeadName = familyHeadName ?: "Not Available", // typeOfList = typeOfList.name, - rchId = rchId ?: "Not Available", + rchId = rchId, hrpStatus = hrpStatus, form1Filled = pwrFilled, - syncState = pwrSyncState + syncState = pwrSyncState, + isConsent = false ) } @@ -481,7 +590,7 @@ data class BenBasicCache( benId = benId, hhId = hhId, regDate = dateFormat.format(Date(regDate)), - benName = benName, + benName = benName.orEmpty(), benSurname = benSurname ?: "", gender = gender.name, dob = dob, @@ -492,12 +601,13 @@ data class BenBasicCache( lastMenstrualPeriod = getDateStringFromLong(lastMenstrualPeriod), edd = getEddFromLmp(lastMenstrualPeriod), // typeOfList = typeOfList.name, - rchId = rchId ?: "Not Available", + rchId = rchId, hrpStatus = hrpStatus, form1Filled = hrppaFilled, syncState = hrppaSyncState, form2Enabled = true, - form2Filled = hrpmbpFilled + form2Filled = hrpmbpFilled, + isConsent = false ) } @@ -506,7 +616,7 @@ data class BenBasicCache( benId = benId, hhId = hhId, regDate = dateFormat.format(Date(regDate)), - benName = benName, + benName = benName.orEmpty(), benSurname = benSurname ?: "", spouseName = spouseName ?: "", gender = gender.name, @@ -515,10 +625,11 @@ data class BenBasicCache( fatherName = fatherName, familyHeadName = familyHeadName ?: "Not Available", // typeOfList = typeOfList.name, - rchId = rchId ?: "Not Available", + rchId = rchId, hrpStatus = hrpStatus, form1Filled = hrpnpaFilled, - syncState = hrpnpaSyncState + syncState = hrpnpaSyncState, + isConsent = false ) } @@ -527,7 +638,7 @@ data class BenBasicCache( benId = benId, hhId = hhId, regDate = dateFormat.format(Date(regDate)), - benName = benName, + benName = benName.orEmpty(), benSurname = benSurname ?: "", spouseName = spouseName ?: "", gender = gender.name, @@ -536,13 +647,14 @@ data class BenBasicCache( fatherName = fatherName, familyHeadName = familyHeadName ?: "Not Available", // typeOfList = typeOfList.name, - rchId = rchId ?: "Not Available", + rchId = rchId, hrpStatus = hrpStatus, form1Filled = hrnptrackingDone, form1Enabled = !hrnptrackingDone, form2Filled = hrnptFilled, form2Enabled = hrnptFilled, - syncState = hrnptSyncState + syncState = hrnptSyncState, + isConsent = false ) } @@ -551,7 +663,7 @@ data class BenBasicCache( benId = benId, hhId = hhId, regDate = dateFormat.format(Date(regDate)), - benName = benName, + benName = benName.orEmpty(), benSurname = benSurname ?: "", spouseName = spouseName ?: "", gender = gender.name, @@ -562,13 +674,14 @@ data class BenBasicCache( fatherName = fatherName, familyHeadName = familyHeadName ?: "Not Available", // typeOfList = typeOfList.name, - rchId = rchId ?: "Not Available", + rchId = rchId, hrpStatus = hrpStatus, form1Filled = hrptrackingDone, form1Enabled = !hrptrackingDone, form2Filled = hrptFilled, form2Enabled = hrptFilled, - syncState = hrptSyncState + syncState = hrptSyncState, + isConsent = false ) } @@ -577,7 +690,7 @@ data class BenBasicCache( benId = benId, hhId = hhId, regDate = dateFormat.format(Date(regDate)), - benName = benName, + benName = benName.orEmpty(), benSurname = benSurname ?: "Not Available", gender = gender.name, dob = dob, @@ -585,10 +698,11 @@ data class BenBasicCache( fatherName = fatherName, familyHeadName = familyHeadName ?: "Not Available", // typeOfList = typeOfList.name, - rchId = rchId ?: "Not Available", + rchId = rchId, hrpStatus = hrpStatus, form1Filled = irFilled, - syncState = irSyncState + syncState = irSyncState, + isConsent = false ) } @@ -597,7 +711,7 @@ data class BenBasicCache( benId = benId, hhId = hhId, regDate = dateFormat.format(Date(regDate)), - benName = benName, + benName = benName.orEmpty(), benSurname = benSurname ?: "Not Available", gender = gender.name, dob = dob, @@ -605,10 +719,11 @@ data class BenBasicCache( fatherName = fatherName, familyHeadName = familyHeadName ?: "Not Available", // typeOfList = typeOfList.name, - rchId = rchId ?: "Not Available", + rchId = rchId, hrpStatus = hrpStatus, form1Filled = irFilled, - syncState = crSyncState + syncState = crSyncState, + isConsent = false ) } @@ -617,7 +732,7 @@ data class BenBasicCache( benId = benId, hhId = hhId, regDate = dateFormat.format(Date(regDate)), - benName = benName, + benName = benName.orEmpty(), benSurname = benSurname ?: "Not Available", gender = gender.name, dob = dob, @@ -625,10 +740,11 @@ data class BenBasicCache( fatherName = fatherName, familyHeadName = familyHeadName ?: "Not Available", // typeOfList = typeOfList.name, - rchId = rchId ?: "Not Available", + rchId = rchId, hrpStatus = hrpStatus, form1Filled = doFilled, - syncState = doSyncState + syncState = doSyncState, + isConsent = isConsent ) } @@ -637,7 +753,7 @@ data class BenBasicCache( benId = benId, hhId = hhId, regDate = dateFormat.format(Date(regDate)), - benName = benName, + benName = benName.orEmpty(), benSurname = benSurname ?: "Not Available", gender = gender.name, dob = dob, @@ -645,10 +761,11 @@ data class BenBasicCache( fatherName = fatherName, familyHeadName = familyHeadName ?: "Not Available", // typeOfList = typeOfList.name, - rchId = rchId ?: "Not Available", + rchId = rchId, hrpStatus = hrpStatus, form1Filled = ecrFilled, - syncState = syncState + syncState = syncState, + isConsent = false ) } @@ -657,7 +774,7 @@ data class BenBasicCache( benId = benId, hhId = hhId, regDate = dateFormat.format(Date(regDate)), - benName = benName, + benName = benName.orEmpty(), benSurname = benSurname ?: "Not Available", gender = gender.name, dob = dob, @@ -665,19 +782,86 @@ data class BenBasicCache( fatherName = fatherName, familyHeadName = familyHeadName ?: "Not Available", // typeOfList = typeOfList.name, - rchId = rchId ?: "Not Available", + rchId = rchId, hrpStatus = hrpStatus, form1Filled = false, - syncState = syncState + syncState = syncState, + isConsent = isConsent ) } } +fun getAgeDisplayString(dob: Long): String { + val calDob = Calendar.getInstance().apply { timeInMillis = dob } + val calNow = Calendar.getInstance() + + val diffDays = + TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() - dob).toInt() + + if (diffDays < 31) { + return "$diffDays Day${if (diffDays != 1) "s" else ""}" + } + + var years = calNow.get(Calendar.YEAR) - calDob.get(Calendar.YEAR) + var months = calNow.get(Calendar.MONTH) - calDob.get(Calendar.MONTH) + var days = calNow.get(Calendar.DAY_OF_MONTH) - calDob.get(Calendar.DAY_OF_MONTH) + + if (days < 0) { + months -= 1 + val tempCal = calNow.clone() as Calendar + tempCal.add(Calendar.MONTH, -1) + days += tempCal.getActualMaximum(Calendar.DAY_OF_MONTH) + } + + if (months < 0) { + years -= 1 + months += 12 + } + + return when { + years >= 1 -> { + buildString { + append("$years Year${if (years != 1) "s" else ""}") + if (months >= 1) { + append(" $months Month${if (months != 1) "s" else ""}") + } + if (days >= 1) { + append(" $days Day${if (days != 1) "s" else ""}") + } + } + } + else -> { + buildString { + if (months >= 1) { + append("$months Month${if (months != 1) "s" else ""}") + } + if (days >= 1) { + append(" $days Day${if (days != 1) "s" else ""}") + } + } + } + } +} + + +@Parcelize data class BenBasicDomain( val benId: Long, val hhId: Long, + + var isDeath: Boolean = false, + var isDeathValue: String? = null, + var dateOfDeath: String? = null, + var timeOfDeath: String? = null, + var reasonOfDeath: String? = null, + var reasonOfDeathId: Int? = null, + var placeOfDeath: String? = null, + var placeOfDeathId: Int? = null, + var otherPlaceOfDeath: String? = null, + var reproductiveStatusId: Int, + val regDate: String, val benName: String, val benSurname: String? = null, @@ -686,22 +870,53 @@ data class BenBasicDomain( val dob: Long, val ageInt: Int = getAgeFromDob(dob), val ageUnit: AgeUnit = getAgeUnitFromDob(dob), - val age: String = "$ageInt ${ageUnit.name}", +// val age: String = "$ageInt ${ageUnit.name}", + val age: String = "${getAgeDisplayString(dob)}", val relToHeadId: Int, val mobileNo: String, val abhaId: String? = null, + val isNewAbha: Boolean = false, val fatherName: String? = null, + val motherName: String? = null, val familyHeadName: String, val spouseName: String? = null, // val typeOfList: String, - val rchId: String, + val rchId: String? = null, val hrpStatus: Boolean = false, - var syncState: SyncState? + var syncState: SyncState?, + val isConsent: Boolean, + var isSpouseAdded: Boolean, + var isChildrenAdded: Boolean, + var isMarried : Boolean, + var doYouHavechildren: Boolean = false, + var noOfChildren: Int = 0, + var noOfAliveChildren: Int = 0, + var isDeactivate: Boolean =false + +) : Parcelable{ + val dobString: String + get() = java.text.SimpleDateFormat("dd-MM-yyyy", java.util.Locale.ENGLISH) + .format(java.util.Date(dob)) +} + + +data class BenChildCount( + val benId: Long, + val childCount: Int ) data class BenBasicDomainForForm( val benId: Long, val hhId: Long, + var isDeath: Boolean = false, + var isDeathValue: String? = null, + var dateOfDeath: String? = null, + var timeOfDeath: String? = null, + var reasonOfDeath: String? = null, + var reasonOfDeathId: Int? = null, + var placeOfDeath: String? = null, + var placeOfDeathId: Int? = null, + var otherPlaceOfDeath: String? = null, val regDate: String, val benName: String, val benSurname: String? = null, @@ -717,7 +932,7 @@ data class BenBasicDomainForForm( val lastMenstrualPeriod: String? = null, val edd: String? = null, // val typeOfList: String, - val rchId: String, + val rchId: String? = null, val hrpStatus: Boolean = false, val form1Filled: Boolean = false, val form2Filled: Boolean = false, @@ -726,7 +941,9 @@ data class BenBasicDomainForForm( val form2Enabled: Boolean = true, val form3Enabled: Boolean = true, val formsFilled: Int = 0, - var syncState: SyncState? + var syncState: SyncState?, + val isConsent: Boolean, + ) { companion object } @@ -790,6 +1007,10 @@ data class BenRegKid( var birthBCG: Boolean = false, var birthHepB: Boolean = false, var birthOPV: Boolean = false, + val isConsent: Boolean = false, + var birthCertificateFileFrontView: String? = null, + var birthCertificateFileBackView: String? = null + ) @JsonClass(generateAdapter = true) @@ -868,12 +1089,14 @@ data class BenRegKidNetwork( val birthBCG: Boolean? = null, val birthHepB: Boolean? = null, val birthOPV: Boolean? = null, + val isConsent: Boolean - ) +) data class BenHealthIdDetails( - var healthId: String? = null, - var healthIdNumber: String? = null + var healthId: String = "", + var healthIdNumber: String = "", + var isNewAbha: Boolean = false ) data class BenRegGen( @@ -939,6 +1162,17 @@ data class BenRegCache( var beneficiaryId: Long, + var isDeath: Boolean, + var isDeathValue: String? = null, + var dateOfDeath: String? = null, + var timeOfDeath: String? = null, + var reasonOfDeath: String? = null, + var reasonOfDeathId: Int, + var placeOfDeath: String? = null, + var placeOfDeathId: Int, + var otherPlaceOfDeath: String? = null, + + var benRegId: Long = 0, @ColumnInfo(index = true) @@ -986,6 +1220,8 @@ data class BenRegCache( var mobileNoOfRelationId: Int = 0, + var tempMobileNoOfRelationId: Int = 0, + var mobileOthers: String? = null, var contactNumber: Long = 0, @@ -1104,7 +1340,19 @@ data class BenRegCache( var isDraft: Boolean, - ) : FormDataModel { + @ColumnInfo(name = "isConsent", defaultValue = "0") + var isConsent: Boolean = false , + + var isNewAbha: Boolean = false, + var isSpouseAdded: Boolean = false, + var isChildrenAdded: Boolean = false, + var isMarried: Boolean = false, + var doYouHavechildren: Boolean = false, + var noOfChildren: Int = 0, + var noOfAliveChildren: Int = 0, + var isDeactivate: Boolean =false + + ) : FormDataModel { fun asNetworkPostModel(context: Context, user: User): BenPost { return BenPost( @@ -1173,21 +1421,25 @@ data class BenRegCache( 0 -> 0 1 -> 1 2 -> 2 - 3 -> 4 - 4 -> 5 + 3 -> 3 + 4 -> 4 + 5-> 5 else -> 6 } } ?: 0, - reproductiveStatus = genDetails?.reproductiveStatusId?.let { - when (it) { - 0 -> "" - 1 -> "Eligible Couple" - 2 -> "Antenatal Mother" - 3 -> "Postnatal Mother-Lactating Mother" - 4 -> "Menopause Stage" - else -> "Teenager" - } - } ?: "", + + reproductiveStatus = genDetails?.reproductiveStatus.toString(), +// reproductiveStatus = genDetails?.reproductiveStatusId?.let { +// when (it) { +// 0 -> "" +// 1 -> "Eligible Couple" +// 2 -> "Antenatal Mother" +// 3 -> "Postnatal Mother-Lactating Mother" +// 4 -> "Menopause Stage" +// 5 -> "Permanently Sterilised" +// else -> "Teenager" +// } +// } ?: "", // noOfDaysForDelivery = if (genDetails?.reproductiveStatusId ==2) getNumDaysForDeliveryFromLastMenstrualPeriod( // genDetails?.lastMenstrualPeriod // ) else null, @@ -1277,8 +1529,26 @@ data class BenRegCache( dob = getDateStringFromLong(dob) ?: "", gender = gender.toString(), genderId = genderId, - maritalStatusID = if (isKid) null else genDetails?.maritalStatusId?.toString() ?: "", - maritalStatusName = if (isKid) null else genDetails?.maritalStatus ?: "", + maritalStatusID = if (isKid) null else genDetails?.maritalStatusId?.toString() ?: null, + maritalStatusName = if (isKid) null else genDetails?.maritalStatus ?: null, + isDeath = isDeath, + isDeathValue = isDeathValue ?: "", + dateOfDeath = dateOfDeath ?:"", + placeOfDeath = placeOfDeath ?: "", + timeOfDeath = timeOfDeath ?: "", + reasonOfDeath = reasonOfDeath ?: "", + reasonOfDeathId = reasonOfDeathId ?: -1, + placeOfDeathId = placeOfDeathId ?: -1, + otherPlaceOfDeath = otherPlaceOfDeath?: "", + noofAlivechildren = noOfAliveChildren, + noOfchildren = noOfChildren, + isMarried = isMarried, + isChildrenAdded = isChildrenAdded, + doYouHavechildren = doYouHavechildren, + isSpouseAdded = isSpouseAdded, + isDeactivate = isDeactivate + + ) } @@ -1292,7 +1562,7 @@ data class BenRegCache( return TimeUnit.MILLISECONDS.toDays(cal.timeInMillis - millisCurrent).toInt() } - fun asKidNetworkModel(): BenRegKidNetwork { + fun asKidNetworkModel(user: User): BenRegKidNetwork { return BenRegKidNetwork( benficieryid = beneficiaryId, childName = firstName, @@ -1342,13 +1612,14 @@ data class BenRegCache( Processed = processed, serverUpdatedStatus = serverUpdatedStatus, VanID = 4, -// ProviderServiceMapID = user.serviceMapId, + ProviderServiceMapID = user.serviceMapId, Countyid = locationRecord.country.id, stateid = locationRecord.state.id, districtid = locationRecord.district.id, villageid = locationRecord.village.id, + isConsent = isConsent, - ) + ) } } @@ -1363,8 +1634,8 @@ fun getEddFromLmp(dateLong: Long?): String? { } fun getDateTimeStringFromLong(dateLong: Long?): String? { - val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) - val timeFormat = SimpleDateFormat("HH:mm:ss", Locale.getDefault()) + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) + val timeFormat = SimpleDateFormat("HH:mm:ss", Locale.ENGLISH) dateLong?.let { val dateString = dateFormat.format(dateLong) val timeString = timeFormat.format(dateLong) diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/BenPost.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/BenPost.kt index e2ba32f87..424a88fff 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/model/BenPost.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/BenPost.kt @@ -11,6 +11,33 @@ data class BenPost( val countyid: Int = 0, @Json(name = "Processed") val processed: String? = null, + @Json(name = "isDeath") + val isDeath: Boolean, + + @Json(name = "isDeathValue") + val isDeathValue: String, + + @Json(name = "dateOfDeath") + val dateOfDeath: String, + + @Json(name = "timeOfDeath") + val timeOfDeath: String, + + @Json(name = "reasonOfDeath") + val reasonOfDeath: String, + + @Json(name = "reasonOfDeathId") + val reasonOfDeathId: Int, + + @Json(name = "placeOfDeath") + val placeOfDeath: String, + + @Json(name = "placeOfDeathId") + val placeOfDeathId: Int, + + @Json(name = "otherPlaceOfDeath") + val otherPlaceOfDeath: String, + @Json(name = "ProviderServiceMapID") val providerServiceMapID: Int = 0, @Json(name = "VanID") @@ -241,4 +268,24 @@ data class BenPost( @Json(name = "i_bendemographics") val benDemographics: BenDemographics, + @Json(name = "isSpouseAdded") + val isSpouseAdded: Boolean = false, + + @Json(name = "isChildrenAdded") + val isChildrenAdded: Boolean = false, + + @Json(name = "isMarried") + val isMarried: Boolean = false, + + @Json(name = "doYouHavechildren") + val doYouHavechildren: Boolean = false, + + @Json(name = "noOfchildren") + val noOfchildren: Int = 0, + + @Json(name = "noofAlivechildren") + val noofAlivechildren: Int = 0, + @Json(name = "isDeactivate") + val isDeactivate: Boolean = false, + ) diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/BenSending.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/BenSending.kt index be2ed816e..c157622a4 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/model/BenSending.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/BenSending.kt @@ -17,6 +17,34 @@ data class BeneficiaryDataSending( @Json(name = "benImage") val benImage: String, + @Json(name = "isDeath") + val isDeath: Boolean, + + @Json(name = "isDeathValue") + val isDeathValue: String, + + @Json(name = "dateOfDeath") + val dateOfDeath: String, + + @Json(name = "timeOfDeath") + val timeOfDeath: String, + + @Json(name = "reasonOfDeath") + val reasonOfDeath: String, + + @Json(name = "reasonOfDeathId") + val reasonOfDeathId: Int, + + @Json(name = "placeOfDeath") + val placeOfDeath: String, + + @Json(name = "placeOfDeathId") + val placeOfDeathId: Int, + + @Json(name = "otherPlaceOfDeath") + val otherPlaceOfDeath: String, + + @Json(name = "benPhoneMaps") val benPhoneMaps: Array, @@ -108,6 +136,10 @@ data class BeneficiaryDataSending( @Json(name = "maritalStatusName") val maritalStatusName: String? = null, + + @Json(name = "beneficiaryConsent") + val beneficiaryConsent: Boolean = false, + ) data class BenDemographics( @@ -227,6 +259,17 @@ fun BenRegCache.asNetworkSendingModel( val isKid = (ageUnit != null && (ageUnit != AgeUnit.YEARS || age < 15)) return BeneficiaryDataSending( + + isDeath=isDeath, + isDeathValue=isDeathValue?: "", + dateOfDeath=dateOfDeath?: "", + timeOfDeath=timeOfDeath?: "", + reasonOfDeath=reasonOfDeath?: "", + reasonOfDeathId=reasonOfDeathId?: 0, + placeOfDeath=placeOfDeath?: "", + placeOfDeathId=placeOfDeathId?: 0, + otherPlaceOfDeath=otherPlaceOfDeath?: "", + benImage = ImageUtils.getEncodedStringForBenImage(context, beneficiaryId) ?: "", //Base64.encodeToString(userImageBlob, Base64.DEFAULT), firstName = firstName!!, @@ -301,6 +344,7 @@ fun BenRegCache.asNetworkSendingModel( // vanID = user.vanId, // parkingPlaceID = user.parkingPlaceId, createdBy = user.userName, + beneficiaryConsent = isConsent ) diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/BottleItem.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/BottleItem.kt new file mode 100644 index 000000000..271a493cb --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/BottleItem.kt @@ -0,0 +1,7 @@ +package org.piramalswasthya.sakhi.model + +data class BottleItem( + val srNo: Int, + val bottleNumber: String, + val dateOfProvision: String +) diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/CBAC.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/CBAC.kt index fadc89bde..0ee88a194 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/model/CBAC.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/CBAC.kt @@ -14,6 +14,7 @@ import org.piramalswasthya.sakhi.R import org.piramalswasthya.sakhi.database.room.SyncState import java.text.SimpleDateFormat import java.util.Locale +import java.util.TimeZone @Entity( tableName = "CBAC", @@ -123,343 +124,135 @@ data class CbacCache( var suspected_ncd_diseases: String? = null, var confirmed_ncd_diseases: String? = null, var diagnosis_status: String? = null, - + var isReffered: Boolean? = false, var syncState: SyncState ) { - fun asPostModel(hhId: Long, benGender: Gender, resources: Resources): CbacPost { - return CbacPost( - houseoldId = hhId, - beneficiaryId = benId, - ashaid = ashaId, - filledDate = getDateTimeStringFromLong(fillDate) ?: "", - cbac_age = resources.getStringArray(R.array.cbac_age)[cbac_age_posi - 1], - cbac_age_posi = cbac_age_posi, - cbac_smoke = resources.getStringArray(R.array.cbac_smoke)[cbac_smoke_posi - 1], - cbac_smoke_posi = cbac_smoke_posi, - cbac_alcohol = resources.getStringArray(R.array.cbac_alcohol)[cbac_alcohol_posi - 1], - cbac_alcohol_posi = cbac_alcohol_posi, - cbac_waist = if (benGender == Gender.MALE) + + fun asPostModel( + hhId: Long, + benGender: Gender, + resources: Resources, + ): CbacPostNew { + return CbacPostNew( + cbacAge = resources.getStringArray(R.array.cbac_age)[cbac_age_posi - 1], + cbacAgeScore = cbac_age_posi, + cbacConsumeGutka = resources.getStringArray(R.array.cbac_smoke)[cbac_smoke_posi - 1], + cbacConsumeGutkaScore = cbac_smoke_posi, + cbacAlcohol = resources.getStringArray(R.array.cbac_alcohol)[cbac_alcohol_posi - 1], + cbacAlcoholScore = cbac_alcohol_posi, + + cbacWaistMale = if (benGender == Gender.MALE) resources.getStringArray(R.array.cbac_waist_mes_male)[cbac_waist_posi - 1] - else - resources.getStringArray(R.array.cbac_waist_mes_female)[cbac_waist_posi - 1], - cbac_waist_posi = cbac_waist_posi, - cbac_pa = resources.getStringArray(R.array.cbac_pa)[cbac_pa_posi - 1], - cbac_pa_posi = cbac_pa_posi, - cbac_familyhistory = resources.getStringArray(R.array.cbac_fh)[cbac_familyhistory_posi - 1], - cbac_familyhistory_posi = cbac_familyhistory_posi, - total_score = total_score, - cbac_sufferingtb = when (cbac_sufferingtb_pos) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_fh_tb) - }, - cbac_sufferingtb_pos = cbac_sufferingtb_pos, - cbac_antitbdrugs = when (cbac_antitbdrugs_pos) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_taking_tb_drug) - }, - cbac_antitbdrugs_pos = cbac_antitbdrugs_pos, - cbac_tbhistory = when (cbac_tbhistory_pos) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_histb) - }, - cbac_tbhistory_pos = cbac_tbhistory_pos, - cbac_sortnesofbirth = when (cbac_sortnesofbirth_pos) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_breath) - }, - cbac_sortnesofbirth_pos = cbac_sortnesofbirth_pos, - cbac_coughing = when (cbac_coughing_pos) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_coughing) - }, - cbac_coughing_pos = cbac_coughing_pos, - cbac_bloodsputum = when (cbac_bloodsputum_pos) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_blsputum) - }, - cbac_bloodsputum_pos = cbac_bloodsputum_pos, - cbac_fivermore = when (cbac_fivermore_pos) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_feverwks) - }, - cbac_fivermore_pos = cbac_fivermore_pos, - cbac_loseofweight = when (cbac_loseofweight_pos) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_lsweight) - }, - cbac_loseofweight_pos = cbac_loseofweight_pos, - cbac_nightsweats = when (cbac_nightsweats_pos) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_ntswets) - }, - cbac_nightsweats_pos = cbac_nightsweats_pos, - cbac_historyoffits = when (cbac_historyoffits_pos) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_hifits) - }, - cbac_historyoffits_pos = cbac_historyoffits_pos, - cbac_difficultyinmouth = when (cbac_difficultyinmouth_pos) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_difmouth) - }, - cbac_difficultyinmouth_pos = cbac_difficultyinmouth_pos, - cbac_uicers = when (cbac_uicers_pos) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_recurrent_ulceration) - }, - cbac_uicers_pos = cbac_uicers_pos, - cbac_toneofvoice = when (cbac_toneofvoice_pos) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_voice) - }, - cbac_toneofvoice_pos = cbac_toneofvoice_pos, - cbac_lumpinbreast = when (cbac_lumpinbreast_pos) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_lumpbrest) - }, - cbac_lumpinbreast_pos = cbac_lumpinbreast_pos, - cbac_blooddischage = when (cbac_blooddischage_pos) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_nipple) - }, - cbac_blooddischage_pos = cbac_blooddischage_pos, - cbac_changeinbreast = when (cbac_changeinbreast_pos) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_breast) - }, - cbac_changeinbreast_pos = cbac_changeinbreast_pos, - cbac_bleedingbtwnperiods = when (cbac_bleedingbtwnperiods_pos) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_blperiods) - }, - cbac_bleedingbtwnperiods_pos = cbac_bleedingbtwnperiods_pos, - cbac_bleedingaftermenopause = when (cbac_bleedingaftermenopause_pos) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_blmenopause) - }, - cbac_bleedingaftermenopause_pos = cbac_bleedingaftermenopause_pos, - cbac_bleedingafterintercourse = when (cbac_bleedingafterintercourse_pos) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_blintercorse) - }, - cbac_bleedingafterintercourse_pos = cbac_bleedingafterintercourse_pos, - cbac_foulveginaldischarge = when (cbac_foulveginaldischarge_pos) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_fouldis) - }, - cbac_foulveginaldischarge_pos = cbac_foulveginaldischarge_pos, - cbac_referpatient_mo = cbac_referpatient_mo ?: "0", - cbac_tracing_all_fm = cbac_tracing_all_fm ?: "0", - cbac_sputemcollection = cbac_sputemcollection ?: "0", - serverUpdatedStatus = serverUpdatedStatus, - createdBy = createdBy!!, - createdDate = getDateTimeStringFromLong(createdDate)!!, - ProviderServiceMapID = ProviderServiceMapID, - VanID = VanID, - Processed = Processed!!, - Countyid = Countyid, - stateid = stateid, - districtid = districtid, - districtname = districtname, - villageid = villageid, - hrp_suspected = hrp_suspected ?: false, - suspected_hrp = suspected_hrp ?: "N", - ncd_suspected = ncd_suspected ?: "N", - suspected_ncd = suspected_ncd ?: "N", - suspected_tb = suspected_tb ?: "N", - suspected_ncd_diseases = suspected_ncd_diseases ?: "N", - cbac_reg_id = cbac_reg_id, - ncd_suspected_cancer = false, - ncd_suspected_hypertension = false, - ncd_suspected_breastCancer = false, - ncd_suspected_diabettis = false, - ncd_confirmed = ncd_confirmed ?: false, - confirmed_ncd = confirmed_ncd ?: "No", - confirmed_hrp = null, - confirmed_tb = null, - suspected_confirmed_tb = false, - confirmed_ncd_diseases = null, - diagnosis_status = null, - cbac_growth_in_mouth = when (cbac_growth_in_mouth_posi) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_Any_Growth) - }, - cbac_growth_in_mouth_posi = cbac_growth_in_mouth_posi, - cbac_white_or_red_patch = when (cbac_white_or_red_patch_posi) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_Any_white) - }, - cbac_white_or_red_patch_posi = cbac_white_or_red_patch_posi, - cbac_Pain_while_chewing = when (cbac_Pain_while_chewing_posi) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_Pain_while_chewing) - }, - cbac_Pain_while_chewing_posi = cbac_Pain_while_chewing_posi, - cbac_hyper_pigmented_patch = when (cbac_hyper_pigmented_patch_posi) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_Any_hyper_pigmented) - }, - cbac_hyper_pigmented_patch_posi = cbac_hyper_pigmented_patch_posi, - cbac_any_thickend_skin = when (cbac_any_thickend_skin_posi) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_any_thickend_skin) - }, - cbac_any_thickend_skin_posi = cbac_any_thickend_skin_posi, - cbac_nodules_on_skin = when (cbac_nodules_on_skin_posi) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_any_nodules_skin) - }, - cbac_nodules_on_skin_posi = cbac_nodules_on_skin_posi, - cbac_numbness_on_palm = when (cbac_numbness_on_palm_posi) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_Recurrent_numbness) - }, - cbac_numbness_on_palm_posi = cbac_numbness_on_palm_posi, - cbac_clawing_of_fingers = when (cbac_clawing_of_fingers_posi) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_Clawing_of_fingers) - }, - cbac_clawing_of_fingers_posi = cbac_clawing_of_fingers_posi, - cbac_tingling_or_numbness = when (cbac_tingling_or_numbness_posi) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_Tingling_or_Numbness) - }, - cbac_tingling_or_numbness_posi = cbac_tingling_or_numbness_posi, - cbac_cloudy = when (cbac_cloudy_posi) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_recurrent_cloudy) - }, - cbac_cloudy_posi = cbac_cloudy_posi, - cbac_diffreading = when (cbac_diffreading_posi) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_recurrent_diffculty_reading) - }, - cbac_diffreading_posi = cbac_diffreading_posi, - cbac_pain_ineyes = when (cbac_pain_ineyes_posi) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_recurrent_pain_eyes) - }, - cbac_pain_ineyes_posi = cbac_pain_ineyes_posi, - cbac_redness_ineyes = when (cbac_redness_ineyes_posi) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_recurrent_redness_eyes) - }, - cbac_redness_ineyes_posi = cbac_redness_ineyes_posi, - cbac_diff_inhearing = when (cbac_diff_inhearing_posi) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_recurrent_diff_hearing) - }, - cbac_diff_inhearing_posi = cbac_diff_inhearing_posi, - cbac_inability_close_eyelid = when (cbac_inability_close_eyelid_posi) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_Inability_close_eyelid) - }, - cbac_rec_tingling = when (cbac_tingling_palm_posi) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_recurrent_tingling) - }, - cbac_rec_tingling_posi = cbac_tingling_palm_posi, - cbac_inability_close_eyelid_posi = cbac_inability_close_eyelid_posi, - cbac_diff_holding_obj = when (cbac_diff_holding_obj_posi) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_diff_holding_objects) - }, - cbac_diff_holding_obj_posi = cbac_diff_holding_obj_posi, - cbac_weekness_in_feet = when (cbac_weekness_in_feet_posi) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_Weekness_in_feet) - }, - cbac_weekness_in_feet_posi = cbac_weekness_in_feet_posi, - cbac_feeling_unsteady = when (cbac_feeling_unsteady_posi) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_unsteady) - }, - cbac_feeling_unsteady_posi = cbac_feeling_unsteady_posi, - cbac_suffer_physical_disability = when (cbac_suffer_physical_disability_posi) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_pd_rm) - }, - cbac_suffer_physical_disability_posi = cbac_suffer_physical_disability_posi, - cbac_needing_help = when (cbac_needing_help_posi) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_nhop) - }, - cbac_fuel_used = if (cbac_fuel_used_posi > 0) resources.getStringArray(R.array.cbac_type_Cooking_fuel)[cbac_fuel_used_posi - 1] else "", - cbac_fuel_used_posi = cbac_fuel_used_posi, - cbac_occupational_exposure = if (cbac_occupational_exposure_posi > 0) resources.getStringArray( + else null, + cbacWaistMaleScore = if (benGender == Gender.MALE) cbac_waist_posi else null, + + cbacWaistFemale = if (benGender == Gender.FEMALE) + resources.getStringArray(R.array.cbac_waist_mes_female)[cbac_waist_posi - 1] + else null, + cbacWaistFemaleScore = if (benGender == Gender.FEMALE) cbac_waist_posi else null, + + cbacPhysicalActivity = resources.getStringArray(R.array.cbac_pa)[cbac_pa_posi - 1], + cbacPhysicalActivityScore = cbac_pa_posi, + cbacFamilyHistoryBpdiabetes = resources.getStringArray(R.array.cbac_fh)[cbac_familyhistory_posi - 1], + cbacFamilyHistoryBpdiabetesScore = cbac_familyhistory_posi, + + cbacShortnessBreath = yesNoFromPos(cbac_sortnesofbirth_pos, resources.getString(R.string.cbac_breath)), + cbacCough2weeks = yesNoFromPos(cbac_coughing_pos, resources.getString(R.string.cbac_coughing)), + cbacBloodsputum = yesNoFromPos(cbac_bloodsputum_pos, resources.getString(R.string.cbac_blsputum)), + cbacFever2weeks = yesNoFromPos(cbac_fivermore_pos, resources.getString(R.string.cbac_feverwks)), + cbacWeightLoss = yesNoFromPos(cbac_loseofweight_pos, resources.getString(R.string.cbac_lsweight)), + cbacNightSweats = yesNoFromPos(cbac_nightsweats_pos, resources.getString(R.string.cbac_ntswets)), + cbacAntiTBDrugs = yesNoFromPos(cbac_antitbdrugs_pos, resources.getString(R.string.cbac_taking_tb_drug)), + cbacTb = yesNoFromPos(cbac_sufferingtb_pos, resources.getString(R.string.cbac_fh_tb)), + cbacTBHistory = yesNoFromPos(cbac_tbhistory_pos, resources.getString(R.string.cbac_histb)), + + cbacUlceration = yesNoFromPos(cbac_uicers_pos, resources.getString(R.string.cbac_recurrent_ulceration)), + cbacRecurrentTingling = yesNoFromPos(cbac_tingling_palm_posi, resources.getString(R.string.cbac_recurrent_tingling)), + cbacFitsHistory = yesNoFromPos(cbac_historyoffits_pos, resources.getString(R.string.cbac_hifits)), + cbacMouthopeningDifficulty = yesNoFromPos(cbac_difficultyinmouth_pos, resources.getString(R.string.cbac_difmouth)), + cbacMouthUlcers = yesNoFromPos(cbac_uicers_pos, resources.getString(R.string.cbac_recurrent_ulceration)), + cbacMouthUlcersGrowth = yesNoFromPos(cbac_growth_in_mouth_posi, resources.getString(R.string.cbac_Any_Growth)), + cbacMouthredpatch = yesNoFromPos(cbac_white_or_red_patch_posi, resources.getString(R.string.cbac_Any_white)), + cbacPainchewing = yesNoFromPos(cbac_Pain_while_chewing_posi, resources.getString(R.string.cbac_Pain_while_chewing)), + cbacTonechange = yesNoFromPos(cbac_toneofvoice_pos, resources.getString(R.string.cbac_voice)), + + cbacHypopigmentedpatches = yesNoFromPos(cbac_hyper_pigmented_patch_posi, resources.getString(R.string.cbac_Any_hyper_pigmented)), + cbacThickenedskin = yesNoFromPos(cbac_any_thickend_skin_posi, resources.getString(R.string.cbac_any_thickend_skin)), + cbacNodulesonskin = yesNoFromPos(cbac_nodules_on_skin_posi, resources.getString(R.string.cbac_any_nodules_skin)), + cbacRecurrentNumbness = yesNoFromPos(cbac_numbness_on_palm_posi, resources.getString(R.string.cbac_Recurrent_numbness)), + + cbacBlurredVision = yesNoFromPos(cbac_cloudy_posi, resources.getString(R.string.cbac_recurrent_cloudy)), + cbacDifficultyreading = yesNoFromPos(cbac_diffreading_posi, resources.getString(R.string.cbac_recurrent_diffculty_reading)), + cbacPainineyes = yesNoFromPosNullable(cbac_pain_ineyes_posi, resources.getString(R.string.cbac_recurrent_pain_eyes)), + cbacRednessPain = yesNoFromPos(cbac_redness_ineyes_posi, resources.getString(R.string.cbac_recurrent_redness_eyes)), + cbacDifficultyHearing = yesNoFromPos(cbac_diff_inhearing_posi, resources.getString(R.string.cbac_recurrent_diff_hearing)), + + cbacClawingfingers = yesNoFromPos(cbac_clawing_of_fingers_posi, resources.getString(R.string.cbac_Clawing_of_fingers)), + cbacHandTingling = yesNoFromPos(cbac_tingling_or_numbness_posi, resources.getString(R.string.cbac_Tingling_or_Numbness)), + cbacInabilityCloseeyelid = yesNoFromPos(cbac_inability_close_eyelid_posi, resources.getString(R.string.cbac_Inability_close_eyelid)), + cbacDifficultHoldingObjects = yesNoFromPos(cbac_diff_holding_obj_posi, resources.getString(R.string.cbac_diff_holding_objects)), + cbacFeetweakness = yesNoFromPos(cbac_weekness_in_feet_posi, resources.getString(R.string.cbac_Weekness_in_feet)), + + cbacLumpBreast = yesNoFromPosNullable(cbac_lumpinbreast_pos, resources.getString(R.string.cbac_lumpbrest)), + cbacBloodnippleDischarge = yesNoFromPosNullable(cbac_blooddischage_pos, resources.getString(R.string.cbac_nipple)), + cbacBreastsizechange = yesNoFromPosNullable(cbac_changeinbreast_pos, resources.getString(R.string.cbac_breast)), + cbacBleedingPeriods = yesNoFromPosNullable(cbac_bleedingbtwnperiods_pos, resources.getString(R.string.cbac_blperiods)), + cbacBleedingMenopause = yesNoFromPosNullable(cbac_bleedingaftermenopause_pos, resources.getString(R.string.cbac_blmenopause)), + cbacBleedingIntercourse = yesNoFromPosNullable(cbac_bleedingafterintercourse_pos, resources.getString(R.string.cbac_blintercorse)), + cbacVaginalDischarge = yesNoFromPosNullable(cbac_foulveginaldischarge_pos, resources.getString(R.string.cbac_fouldis)), + + cbacFeelingUnsteady = yesNoFromPosNullable(cbac_feeling_unsteady_posi, resources.getString(R.string.cbac_unsteady)), + cbacPhysicalDisabilitySuffering = yesNoFromPosNullable(cbac_suffer_physical_disability_posi, resources.getString(R.string.cbac_pd_rm)), + cbacNeedhelpEverydayActivities = yesNoFromPosNullable(cbac_needing_help_posi, resources.getString(R.string.cbac_nhop)), + cbacForgetnearones = yesNoFromPosNullable(cbac_forgetting_names_posi, resources.getString(R.string.cbac_forget_names)), + + CbacCookingOil = if (cbac_fuel_used_posi > 0) resources.getStringArray(R.array.cbac_type_Cooking_fuel)[cbac_fuel_used_posi - 1] else "", + CbacCookingOilScore = cbac_fuel_used_posi, + CbacOccupationalExposure = if (cbac_occupational_exposure_posi > 0) resources.getStringArray( R.array.cbac_type_occupational_exposure )[cbac_occupational_exposure_posi - 1] else "", - cbac_occupational_exposure_posi = cbac_occupational_exposure_posi, - cbac_little_interest = if (cbac_little_interest_posi > 0) resources.getStringArray(R.array.cbac_li)[cbac_little_interest_posi - 1] else "", - cbac_little_interest_posi = cbac_little_interest_posi, - cbac_feeling_down = if (cbac_feeling_down_posi > 0) resources.getStringArray(R.array.cbac_fd)[cbac_feeling_down_posi - 1] else "", - cbac_feeling_down_posi = cbac_feeling_down_posi, - cbac_little_interest_score = cbac_little_interest_score, - cbac_feeling_down_score = cbac_feeling_down_score, - cbac_needing_help_posi = cbac_needing_help_posi, - cbac_forgetting_names = when (cbac_forgetting_names_posi) { - 1 -> "Yes" - 2 -> "No" - else -> resources.getString(R.string.cbac_forget_names) - }, - cbac_forgetting_names_posi = cbac_forgetting_names_posi, - - ) + CbacOccupationalExposureScore = cbac_occupational_exposure_posi, + CbacLittleInterestPleasure = if (cbac_little_interest_posi > 0) resources.getStringArray(R.array.cbac_li)[cbac_little_interest_posi - 1] else "", + CbacLittleInterestPleasureScore = cbac_little_interest_posi, + CbacDepressedhopeless = if (cbac_feeling_down_posi > 0) resources.getStringArray(R.array.cbac_fd)[cbac_feeling_down_posi - 1] else "", + CbacDepressedhopelessScore = cbac_feeling_down_posi, + totalScore = total_score, + CbacFeelingDownScore = cbac_feeling_down_score, + isRefer = isReffered!! + ) + } + + // Helper methods + private fun yesNoFromPos(pos: Int, default: String): String { + return when (pos) { + 1 -> "Yes" + 2 -> "No" + else -> default + } + } + + private fun yesNoFromPosNullable(pos: Int, default: String): String? { + return when (pos) { + 1 -> "Yes" + 2 -> "No" + else -> null + } } - fun asDomainModel(): CbacDomain { + + + fun asDomainModel(resources: Resources): CbacDomain { return CbacDomain( cbacId = this.id, - date = "Filled on ${getCbacCreatedDateFromLong(this.fillDate)}", + date = "${resources.getString(R.string.filled_on)}${getCbacCreatedDateFromLong(this.fillDate)}", syncState = this.syncState ) } companion object { - private val dateFormat = SimpleDateFormat("EEE, MMM dd yyyy", Locale.getDefault()) + private val dateFormat = SimpleDateFormat("EEE, MMM dd yyyy", Locale.ENGLISH) private fun getCbacCreatedDateFromLong(long: Long): String { return dateFormat.format(long) @@ -764,6 +557,84 @@ data class CbacPost( ) + + +@JsonClass(generateAdapter = true) +data class CbacPostNew( + val cbacAge: String, + val cbacAgeScore: Int, + val cbacConsumeGutka: String, + val cbacConsumeGutkaScore: Int, + val cbacAlcohol: String, + val cbacAlcoholScore: Int, + val cbacWaistMale: String?, + val cbacWaistMaleScore: Int?, + val cbacWaistFemale: String?, + val cbacWaistFemaleScore: Int?, + val cbacPhysicalActivity: String, + val cbacPhysicalActivityScore: Int, + val cbacFamilyHistoryBpdiabetes: String, + val cbacFamilyHistoryBpdiabetesScore: Int, + val cbacShortnessBreath: String, + val cbacCough2weeks: String, + val cbacBloodsputum: String, + val cbacFever2weeks: String, + val cbacWeightLoss: String, + val cbacNightSweats: String, + val cbacAntiTBDrugs: String, + val cbacTb: String, + val cbacTBHistory: String, + val cbacUlceration: String, + val cbacRecurrentTingling: String, + val cbacFitsHistory: String, + val cbacMouthopeningDifficulty: String, + val cbacMouthUlcers: String, + val cbacMouthUlcersGrowth: String, + val cbacMouthredpatch: String, + val cbacPainchewing: String, + val cbacTonechange: String, + val cbacHypopigmentedpatches: String, + val cbacThickenedskin: String, + val cbacNodulesonskin: String, + val cbacRecurrentNumbness: String, + val cbacBlurredVision: String, + val cbacDifficultyreading: String, + val cbacPainineyes: String?, + val cbacRednessPain: String, + val cbacDifficultyHearing: String, + val cbacClawingfingers: String, + val cbacHandTingling: String, + val cbacInabilityCloseeyelid: String, + val cbacDifficultHoldingObjects: String, + val cbacFeetweakness: String, + val cbacLumpBreast: String?, + val cbacBloodnippleDischarge: String?, + val cbacBreastsizechange: String?, + val cbacBleedingPeriods: String?, + val cbacBleedingMenopause: String?, + val cbacBleedingIntercourse: String?, + val cbacVaginalDischarge: String?, + val cbacFeelingUnsteady: String?, + val cbacPhysicalDisabilitySuffering: String?, + val cbacNeedhelpEverydayActivities: String?, + val cbacForgetnearones: String?, + val CbacOccupationalExposure: String?, + val CbacBotheredProblemLast2weeks: String? ="", + val CbacLittleInterestPleasure: String?, + val CbacDepressedhopeless: String?, + val CbacDiscolorationSkin: String? = "", + val CbacCookingOil: String?, + val totalScore: Int, + val CbacOccupationalExposureScore: Int, + val CbacBotheredProblemLast2weeksScore: Int ? = 0, + val CbacLittleInterestPleasureScore: Int, + val CbacDepressedhopelessScore: Int, + val CbacCookingOilScore: Int, + val CbacFeelingDownScore: Int, + val isRefer: Boolean, +) + + data class BenWithCbacCache( // @ColumnInfo(name = "benId") @Embedded @@ -781,6 +652,37 @@ data class BenWithCbacCache( } } + +data class BenWithCbacAndReferalCache( + @Embedded val referral: ReferalCache, + @Relation( + parentColumn = "benId", + entityColumn = "benId" + ) + val cbacList: List, + @Relation( + parentColumn = "benId", + entityColumn = "benId" + ) + val ben: BenBasicCache + +) { + fun asDomainModel(): BenWithCbacReferDomain { + return BenWithCbacReferDomain( + ben.asBasicDomainModel(), cbacList,referral + ) + } +} + +data class BenWithCbacReferDomain( + val ben: BenBasicDomain, + val savedCbacRecords: List, + val referalCac : ReferalCache, + val allSynced: SyncState? = if (savedCbacRecords.isEmpty()) null else + if (savedCbacRecords.map { it.syncState } + .all { it == SyncState.SYNCED }) SyncState.SYNCED else SyncState.UNSYNCED +) + data class BenWithCbacDomain( // @ColumnInfo(name = "benId") val ben: BenBasicDomain, @@ -788,4 +690,212 @@ data class BenWithCbacDomain( val allSynced: SyncState? = if (savedCbacRecords.isEmpty()) null else if (savedCbacRecords.map { it.syncState } .all { it == SyncState.SYNCED }) SyncState.SYNCED else SyncState.UNSYNCED -) \ No newline at end of file +) + + +data class CbacResponseDto( + val id: Int, + val beneficiaryRegId: Long, + val visitCode: Long, + + val cbacAge: String?, + val cbacAgeScore: Int?, + + val cbacConsumeGutka: String?, + val cbacConsumeGutkaScore: Int?, + + val cbacAlcohol: String?, + val cbacAlcoholScore: Int?, + + val cbacWaistMale: String?, + val cbacWaistMaleScore: Int?, + val cbacWaistFemale: String? = null, + val cbacWaistFemaleScore: Int? = null, + + val cbacPhysicalActivity: String?, + val cbacPhysicalActivityScore: Int?, + + val cbacFamilyHistoryBpdiabetes: String?, + val cbacFamilyHistoryBpdiabetesScore: Int?, + + val cbacShortnessBreath: String?, + val cbacCough2weeks: String?, + val cbacBloodsputum: String?, + val cbacFever2weeks: String?, + val cbacWeightLoss: String?, + val cbacNightSweats: String?, + val cbacAntiTBDrugs: String?, + val cbacTb: String?, + val cbacTBHistory: String?, + val cbacUlceration: String?, + val cbacRecurrentTingling: String?, + val cbacFitsHistory: String?, + val cbacMouthopeningDifficulty: String?, + val cbacMouthUlcers: String?, + val cbacMouthUlcersGrowth: String?, + val cbacMouthredpatch: String?, + val cbacPainchewing: String?, + val cbacTonechange: String?, + val cbacHypopigmentedpatches: String?, + val cbacThickenedskin: String?, + val cbacNodulesonskin: String?, + val cbacRecurrentNumbness: String?, + val cbacBlurredVision: String?, + val cbacDifficultHoldingObjects: String?, + val cbacFeetweakness: String?, + val cbacLumpBreast: String?, + val cbacBloodnippleDischarge: String?, + val cbacBreastsizechange: String?, + val cbacBleedingPeriods: String?, + val cbacBleedingMenopause: String?, + val cbacBleedingIntercourse: String?, + val cbacVaginalDischarge: String?, + val cbacHandTingling: String?, + val cbacClawingfingers: String?, + val cbacDifficultyHearing: String?, + val cbacRednessPain: String?, + val cbacDifficultyreading: String?, + val CbacOccupationalExposure: String?, + val CbacBotheredProblemLast2weeks: String?, + val CbacLittleInterestPleasure: String?, + val CbacDepressedhopeless: String?, + val CbacDiscolorationSkin: String?, + val cbacPainineyes: String?, + val CbacCookingOil: String?, + val cbacInabilityCloseeyelid: String, + val totalScore: Int, + val deleted: Boolean?, + val processed: String?, + val createdBy: String?, + val createdDate: String?, + val lastModDate: String?, + val vanId: Int?, + val parkingPlaceId: Int?, + val CbacOccupationalExposureScore: Int, + val CbacBotheredProblemLast2weeksScore: Int ? = 0, + val CbacLittleInterestPleasureScore: Int, + val CbacDepressedhopelessScore: Int, + val CbacCookingOilScore: Int, + val CbacFeelingDownScore: Int, + val isRefer: Boolean, +) + +fun CbacResponseDto.toEntity():CbacCache { + return CbacCache( + id = id, + fillDate = createdDate.toMillisOrNull() ?: 0L, + benId = beneficiaryRegId, + ashaId = 0, + cbac_age_posi = cbacAgeScore ?: 0, + cbac_smoke_posi = cbacConsumeGutkaScore ?: 0, + cbac_alcohol_posi = cbacAlcoholScore ?: 0, + cbac_waist_posi = cbacWaistMaleScore ?: cbacWaistFemaleScore ?: 0, + cbac_pa_posi = cbacPhysicalActivityScore ?: 0, + cbac_familyhistory_posi = cbacFamilyHistoryBpdiabetesScore ?: 0, + cbac_sufferingtb_pos = if (cbacTb.equals("yes", true)) 1 else 2, + cbac_sortnesofbirth_pos = if (cbacShortnessBreath.equals("yes", true)) 1 else 2, + cbac_coughing_pos = if (cbacCough2weeks.equals("yes", true)) 1 else 2, + cbac_bloodsputum_pos = if (cbacBloodsputum.equals("yes", true)) 1 else 2, + cbac_fivermore_pos = if (cbacFever2weeks.equals("yes", true)) 1 else 2, + cbac_loseofweight_pos = if (cbacWeightLoss.equals("yes", true)) 1 else 2, + cbac_nightsweats_pos = if (cbacNightSweats.equals("yes", true)) 1 else 2, + cbac_antitbdrugs_pos = if (cbacAntiTBDrugs.equals("yes", true)) 1 else 2, + cbac_tbhistory_pos = if (cbacTBHistory.equals("yes", true)) 1 else 2, + cbac_uicers_pos = if (cbacUlceration.equals("yes", true)) 1 else 2, + cbac_tingling_or_numbness_posi = if (cbacRecurrentTingling.equals("yes", true)) 1 else 2, + cbac_historyoffits_pos = if (cbacFitsHistory.equals("yes", true)) 1 else 2, + cbac_difficultyinmouth_pos = if (cbacMouthopeningDifficulty.equals("yes", true)) 1 else 2, + cbac_growth_in_mouth_posi = if (cbacMouthUlcersGrowth.equals("yes", true)) 1 else 2, + cbac_white_or_red_patch_posi = if (cbacMouthredpatch.equals("yes", true)) 1 else 2, + cbac_Pain_while_chewing_posi = if (cbacPainchewing.equals("yes", true)) 1 else 2, + cbac_toneofvoice_pos = if (cbacTonechange.equals("yes", true)) 1 else 0, + cbac_hyper_pigmented_patch_posi = if (cbacHypopigmentedpatches.equals("yes", true)) 1 else 2, + cbac_any_thickend_skin_posi = if (cbacThickenedskin.equals("yes", true)) 1 else 2, + cbac_nodules_on_skin_posi = if (cbacNodulesonskin.equals("yes", true)) 1 else 2, + cbac_numbness_on_palm_posi = if (cbacRecurrentNumbness.equals("yes", true)) 1 else 2, + cbac_cloudy_posi = if (cbacBlurredVision.equals("yes", true)) 1 else 2, + cbac_diff_holding_obj_posi = if (cbacDifficultHoldingObjects.equals("yes", true)) 1 else 2, + cbac_weekness_in_feet_posi = if (cbacFeetweakness.equals("yes", true)) 1 else 2, + cbac_tingling_palm_posi = if (cbacHandTingling.equals("yes", true)) 1 else 2, + cbac_clawing_of_fingers_posi = if (cbacClawingfingers.equals("yes", true)) 1 else 2, + cbac_diff_inhearing_posi = if (cbacDifficultyHearing.equals("yes", true)) 1 else 2, + cbac_redness_ineyes_posi = if (cbacRednessPain.equals("yes", true)) 1 else 2, + cbac_pain_ineyes_posi = if (cbacPainineyes.equals("yes", true)) 1 else 2, + cbac_inability_close_eyelid_posi = if (cbacInabilityCloseeyelid.equals("yes", true)) 1 else 2, + + cbac_lumpinbreast_pos = if (cbacLumpBreast.equals("yes", true)) 1 else 2, + cbac_blooddischage_pos =if (cbacBloodnippleDischarge.equals("yes", true)) 1 else 2, + cbac_changeinbreast_pos = if (cbacBreastsizechange.equals("yes", true)) 1 else 2, + cbac_bleedingbtwnperiods_pos = if (cbacBleedingPeriods.equals("yes", true)) 1 else 2, + cbac_bleedingaftermenopause_pos = if (cbacBleedingMenopause.equals("yes", true)) 1 else 2, + cbac_bleedingafterintercourse_pos = if (cbacBleedingIntercourse.equals("yes", true)) 1 else 2, + cbac_foulveginaldischarge_pos = if (cbacVaginalDischarge.equals("yes", true)) 1 else 2, + + total_score = totalScore, + cbac_feeling_down_score = CbacFeelingDownScore, + cbac_feeling_down_posi = CbacFeelingDownScore, + cbac_little_interest_posi = CbacLittleInterestPleasureScore, + cbac_little_interest_score = CbacLittleInterestPleasureScore, + cbac_fuel_used_posi = CbacCookingOilScore, + cbac_occupational_exposure_posi = CbacOccupationalExposureScore, + createdBy = createdBy, + cbac_diffreading_posi = if (cbacDifficultyreading.equals("yes", true)) 1 else 2, + VanID = vanId!!, + Processed = "P", + syncState = SyncState.SYNCED, + isReffered = isRefer + ) +} + +data class CbacRequest( + val visitDetails: VisitDetailsWrapper, + val cbac: CbacPostNew, + val benFlowID: Long?, + val beneficiaryID: Long, + val sessionID: Int?, + val parkingPlaceID: Int?, + val createdBy: String, + val vanID: Int?, + val beneficiaryRegID: Long, + val benVisitID: Long?, + val providerServiceMapID: Int?, + val isFlw: Boolean? +) + +data class VisitDetailsWrapper( + val visitDetails: CbacVisitDetails +) + +data class CbacVisitDetails( + val beneficiaryRegID: Long, + val providerServiceMapID: Int, + val visitNo: Int? = null, + val visitReason: String, + val visitCategory: String, + val subVisitCategory: String?=null, + val pregnancyStatus: String?=null, + val followUpForFpMethod: String?=null, + val otherFollowUpForFpMethod: String?=null, + val sideEffects: String?=null, + val otherSideEffects: String?=null, + val IdrsOrCbac: String, + val rCHID: String?=null, + val healthFacilityType: String?=null, + val healthFacilityLocation: String?=null, + val reportFilePath: String?=null, + val createdBy: String, + val vanID: Int, + val parkingPlaceID: Int, + val fileIDs:String?=null +) + +fun String?.toMillisOrNull(pattern: String = "MMM dd, yyyy, h:mm:ss a"): Long? { + if (this.isNullOrBlank()) return null + return try { + val format = SimpleDateFormat(pattern, Locale.ENGLISH) + format.timeZone = TimeZone.getTimeZone("UTC") + format.parse(this)?.time + } catch (e: Exception) { + null + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/CDR.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/CDR.kt index d93b5a91d..0ec434c73 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/model/CDR.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/CDR.kt @@ -27,6 +27,9 @@ data class CDRCache( val id: Int = 0, val benId: Long, var visitDate: Long? = System.currentTimeMillis(), + var cdr1File: String? = null, + var cdr2File: String? = null, + var cdrDeathCertFile: String? = null, var motherName: String? = null, var fatherName: String? = null, var address: String? = null, @@ -54,6 +57,9 @@ data class CDRCache( id = id, benId = benId, visitDate = getDateTimeStringFromLong(visitDate), + cdr1File = cdr1File, + cdr2File = cdr2File, + cdrDeathCertFile = cdrDeathCertFile, motherName = motherName, fatherName = fatherName, address = address, @@ -82,6 +88,9 @@ data class CDRPost( val id: Int, val benId: Long, val visitDate: String? = null, + var cdr1File: String? = null, + var cdr2File: String? = null, + var cdrDeathCertFile: String? = null, val motherName: String? = null, val fatherName: String? = null, val address: String? = null, @@ -113,6 +122,9 @@ data class CDRPost( id = id, benId = benId, visitDate = visitDate?.let { getLongFromDate(it) }, + cdr1File = cdr1File, + cdr2File = cdr2File, + cdrDeathCertFile = cdrDeathCertFile, motherName = motherName, fatherName = fatherName, address = address, diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/ChildOption.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/ChildOption.kt new file mode 100644 index 000000000..9fdb01804 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/ChildOption.kt @@ -0,0 +1,12 @@ +package org.piramalswasthya.sakhi.model + +data class ChildOption( + val formType: String, + val title: String, + val description: String, + val isViewMode: Boolean = false, + val visitDay: String? = null, + val formDataJson: String? = null, + val recordId: Int? = null, + val isIFA : Boolean? = false +) \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/DeliveryOutcome.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/DeliveryOutcome.kt index 7c1b60fa9..5da917456 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/model/DeliveryOutcome.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/DeliveryOutcome.kt @@ -7,6 +7,7 @@ import androidx.room.PrimaryKey import org.piramalswasthya.sakhi.configuration.FormDataModel import org.piramalswasthya.sakhi.database.room.SyncState import org.piramalswasthya.sakhi.network.getLongFromDate +import org.piramalswasthya.sakhi.network.getLongFromDateMultipleSupport import java.text.SimpleDateFormat import java.util.Locale @@ -41,7 +42,20 @@ data class DeliveryOutcomeCache( var stillBirth: Int? = 0, var dateOfDischarge: Long? = null, var timeOfDischarge: String? = null, - var isJSYBenificiary: Boolean? = null, + var isJSYBenificiary: Boolean? = false, + + var isDeath: Boolean? = null, + var isDeathValue: String? = null, + var dateOfDeath: String? = null, + var placeOfDeath: String? = null, + var placeOfDeathId:Int ? = 0, + var otherPlaceOfDeath: String? = null, + var mcp1File: String? = null, + var mcp2File: String? = null, + var jsyFile: String? = null, + + + // var isActive: Boolean? = true, var processed: String? = "N", var createdBy: String, @@ -53,7 +67,6 @@ data class DeliveryOutcomeCache( private fun getDateStringFromLong(dateLong: Long?): String? { val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) - dateLong?.let { return dateFormat.format(dateLong) } ?: run { @@ -84,7 +97,17 @@ data class DeliveryOutcomeCache( createdDate = getDateStringFromLong(createdDate), createdBy = createdBy, updatedDate = getDateStringFromLong(updatedDate), - updatedBy = updatedBy + updatedBy = updatedBy, + isDeath = isDeath, + isDeathValue = isDeathValue, + dateOfDeath = dateOfDeath, + placeOfDeath = placeOfDeath, + placeOfDeathId = placeOfDeathId, + otherPlaceOfDeath = otherPlaceOfDeath, + mcp1File = mcp1File, + mcp2File = mcp2File, + jsyFile = jsyFile + ) } } @@ -111,14 +134,25 @@ data class DeliveryOutcomePost( val createdDate: String? = null, val createdBy: String, val updatedDate: String? = null, - val updatedBy: String -) { + val updatedBy: String, + + var isDeath: Boolean? = null, + var isDeathValue: String? = null, + var dateOfDeath: String? = null, + var placeOfDeath: String? = null, + var placeOfDeathId: Int?=0, + var otherPlaceOfDeath: String? = null, + var mcp1File: String? = null, + var mcp2File: String? = null, + var jsyFile: String? = null, + + ) { fun toDeliveryCache(): DeliveryOutcomeCache { return DeliveryOutcomeCache( id = id, benId = benId, isActive = isActive, - dateOfDelivery = getLongFromDate(dateOfDelivery), + dateOfDelivery = getLongFromDateMultipleSupport(dateOfDelivery), timeOfDelivery = timeOfDelivery, placeOfDelivery = placeOfDelivery, typeOfDelivery = typeOfDelivery, @@ -130,15 +164,24 @@ data class DeliveryOutcomePost( deliveryOutcome = deliveryOutcome, liveBirth = liveBirth, stillBirth = stillBirth, - dateOfDischarge = getLongFromDate(dateOfDischarge), + dateOfDischarge = getLongFromDateMultipleSupport(dateOfDischarge), timeOfDischarge = timeOfDischarge, isJSYBenificiary = isJSYBenificiary, processed = "P", createdBy = createdBy, - createdDate = getLongFromDate(createdDate), + createdDate = getLongFromDateMultipleSupport(createdDate) ?: System.currentTimeMillis(), updatedBy = updatedBy, - updatedDate = getLongFromDate(updatedDate), - syncState = SyncState.SYNCED + updatedDate = getLongFromDateMultipleSupport(updatedDate) ?: System.currentTimeMillis(), + syncState = SyncState.SYNCED, + isDeath = isDeath, + isDeathValue = isDeathValue, + dateOfDeath = dateOfDeath, + placeOfDeath = placeOfDeath, + placeOfDeathId = placeOfDeathId, + otherPlaceOfDeath = otherPlaceOfDeath, + mcp1File = mcp1File, + mcp2File = mcp2File, + jsyFile = jsyFile ) } } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/DewormingCache.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/DewormingCache.kt new file mode 100644 index 000000000..cb55af3d0 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/DewormingCache.kt @@ -0,0 +1,71 @@ +package org.piramalswasthya.sakhi.model + +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey +import org.piramalswasthya.sakhi.configuration.FormDataModel +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.network.DewormingDTO +import java.text.SimpleDateFormat +import java.time.LocalDate +import java.time.format.DateTimeFormatter +import java.util.Date +import java.util.Locale + + + +@Entity( + tableName = "DewormingMeeting", + indices = [ + Index( + value = ["dewormingDate"], + unique = true + ) + ] +) +data class DewormingCache( + @PrimaryKey(autoGenerate = true) + var id: Int = 0, + var dewormingDone: String? = null, + var dewormingDate: String? = null, + var dewormingLocation: String? = null, + var ageGroup: Int? = null, + var image1: String? = null, + var image2: String? = null, + var regDate: String? = null, + + var syncState: SyncState = SyncState.UNSYNCED +) : FormDataModel { + + fun toDTO(): DewormingDTO { + val formatter = SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH) + val currentDate = formatter.format(Date()) + return DewormingDTO( + id = id, + dewormingDone = dewormingDone, + dewormingDate = dewormingDate, + dewormingLocation = dewormingLocation, + ageGroup = ageGroup, + image1 = image1, + image2 = image2, + regDate = currentDate, + ) + } + + fun toDewormingCache(): DewormingCache { + val formatter = SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH) + val currentDate = formatter.format(Date()) + return DewormingCache( + id = id, + dewormingDone = dewormingDone, + dewormingDate = dewormingDate, + dewormingLocation = dewormingLocation, + ageGroup = ageGroup, + image1 = image1, + image2 = image2, + regDate = currentDate, + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/EligibleCoupleReg.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/EligibleCoupleReg.kt index e2ae4c5f2..df1bfa63a 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/model/EligibleCoupleReg.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/EligibleCoupleReg.kt @@ -1,5 +1,7 @@ package org.piramalswasthya.sakhi.model +import android.content.Context +import androidx.room.ColumnInfo import androidx.room.Embedded import androidx.room.Entity import androidx.room.ForeignKey @@ -9,6 +11,7 @@ import androidx.room.Relation import com.squareup.moshi.Json import org.piramalswasthya.sakhi.configuration.FormDataModel import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.helpers.ImageUtils import org.piramalswasthya.sakhi.utils.HelperUtil.getDateStringFromLong @Entity( @@ -33,6 +36,8 @@ data class EligibleCoupleRegCache( var bankName: String? = null, var branchName: String? = null, var ifsc: String? = null, + @ColumnInfo(defaultValue = "0") + var lmpDate: Long = 0L, var noOfChildren: Int = 0, var noOfLiveChildren: Int = 0, var noOfMaleChildren: Int = 0, @@ -79,16 +84,34 @@ data class EligibleCoupleRegCache( var createdDate: Long = System.currentTimeMillis(), var updatedBy: String, val updatedDate: Long = System.currentTimeMillis(), + @ColumnInfo(defaultValue = "0") + var lmp_date:Long =0L, + var isKitHandedOver: Boolean ? = false, + var kitHandedOverDate: Long? = null, + var kitPhoto1: String? = null, + var kitPhoto2: String? = null, var syncState: SyncState ) : FormDataModel { - fun asPostModel(): EcrPost { + + fun asPostModel(context : Context): EcrPost { return EcrPost( benId = benId, dateOfReg = getDateStringFromLong(dateOfReg)!!, + kitHandedOverDate = getDateStringFromLong(kitHandedOverDate).toString(), + isKitHandedOver = isKitHandedOver!!, + kitPhoto1 = ImageUtils.getEncodedStringForBenImage( + context, + benId + ) ?: "", + kitPhoto2 = ImageUtils.getEncodedStringForBenImage( + context, + benId + ) ?: "", bankAccount = bankAccount, bankName = bankName, branchName = branchName, ifsc = ifsc, + lmpDate = lmpDate.takeIf { it > 0 }?.let { getDateStringFromLong(it) } ?: "", numChildren = noOfChildren, numLiveChildren = noOfLiveChildren, numMaleChildren = noOfMaleChildren, @@ -133,6 +156,7 @@ data class EligibleCoupleRegCache( createdDate = getDateStringFromLong(createdDate)!!, updatedBy = updatedBy, updatedDate = getDateStringFromLong(updatedDate)!! + ) } } @@ -177,6 +201,7 @@ data class EcrPost( val dob1: String? = null, val age1: Int? = null, val gender1: Gender? = null, + val lmpDate: String? = null, val marriageFirstChildGap: Int? = null, val dob2: String? = null, val age2: Int? = null, @@ -220,4 +245,8 @@ data class EcrPost( val createdDate: String, var updatedBy: String, val updatedDate: String, + val isKitHandedOver: Boolean, + val kitHandedOverDate: String = "", + val kitPhoto1: String = "", + val kitPhoto2: String = "", ) \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/EligibleCoupleTrackingCache.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/EligibleCoupleTrackingCache.kt index 6700cca22..9c7a2b096 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/model/EligibleCoupleTrackingCache.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/EligibleCoupleTrackingCache.kt @@ -1,5 +1,6 @@ package org.piramalswasthya.sakhi.model +import androidx.room.ColumnInfo import androidx.room.Embedded import androidx.room.Entity import androidx.room.ForeignKey @@ -9,6 +10,7 @@ import androidx.room.Relation import com.squareup.moshi.JsonClass import org.piramalswasthya.sakhi.configuration.FormDataModel import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.utils.HelperUtil.getDateStringFromLong import java.text.SimpleDateFormat import java.util.Calendar import java.util.Locale @@ -29,25 +31,46 @@ data class EligibleCoupleTrackingCache( @PrimaryKey(autoGenerate = true) val id: Int = 0, val benId: Long, - var visitDate: Long = System.currentTimeMillis(), + @ColumnInfo(defaultValue = "0") + var lmpDate: Long = 0L, + var visitDate: Long = 0L, + var dateOfSterilisation: Long = 0L, + var dateOfAntraInjection: String? = null, + var dueDateOfAntraInjection: String? = null, + var mpaFile: String? = null, + var dischargeSummary1: String? = null, + var dischargeSummary2: String? = null, + var antraDose: String? = null, var isPregnancyTestDone: String? = null, var pregnancyTestResult: String? = null, var isPregnant: String? = null, var usingFamilyPlanning: Boolean? = null, var methodOfContraception: String? = null, + val createdBy: String, val createdDate: Long = System.currentTimeMillis(), val updatedBy: String, val updatedDate: Long = System.currentTimeMillis(), var processed: String? = "N", var isActive: Boolean = true, - var syncState: SyncState + var syncState: SyncState, + @ColumnInfo(defaultValue = "0") + var lmp_date: Long = 0L ) : FormDataModel { fun asNetworkModel(): ECTNetwork { return ECTNetwork( benId = benId, + lmpDate = getDateStringFromLong(lmpDate)!!, + dateOfSterilisation = getDateStringFromLong(dateOfSterilisation)!!, visitDate = getDateTimeStringFromLong(visitDate)!!, + dateOfAntraInjection = dateOfAntraInjection?.let { SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH).parse(it)?.time }?.let { getDateTimeStringFromLong(it) }, + dueDateOfAntraInjection = dueDateOfAntraInjection, + mpaFile = mpaFile, + antraDose = antraDose, + dischargeSummary1=dischargeSummary1, + dischargeSummary2=dischargeSummary2, + isPregnancyTestDone = isPregnancyTestDone, pregnancyTestResult = pregnancyTestResult, isPregnant = isPregnant, @@ -58,6 +81,7 @@ data class EligibleCoupleTrackingCache( createdDate = getDateTimeStringFromLong(createdDate)!!, updatedBy = updatedBy, updatedDate = getDateTimeStringFromLong(updatedDate)!!, + lmp_date = benId, ) } } @@ -65,7 +89,15 @@ data class EligibleCoupleTrackingCache( @JsonClass(generateAdapter = true) data class ECTNetwork( val benId: Long, + val lmpDate: String? = null, + val dateOfSterilisation: String? = null, val visitDate: String, + var dateOfAntraInjection: String? = null, + var dueDateOfAntraInjection: String? = null, + var mpaFile: String? = null, + var dischargeSummary1: String? = null, + var dischargeSummary2: String? = null, + var antraDose: String? = null, val isPregnancyTestDone: String?, val pregnancyTestResult: String?, val isPregnant: String?, @@ -76,6 +108,7 @@ data class ECTNetwork( val createdDate: String, val updatedBy: String, val updatedDate: String, + val lmp_date: Long ) data class BenWithEcTrackingCache( @@ -96,14 +129,15 @@ data class BenWithEcTrackingCache( ) { companion object { - private val dateFormat = SimpleDateFormat("EEE, MMM dd yyyy", Locale.getDefault()) + private val dateFormat = SimpleDateFormat("EEE, MMM dd yyyy", Locale.ENGLISH) - private fun getECTFilledDateFromLong(long: Long): String { - return "Visited on ${dateFormat.format(long)}" + private fun getECTFilledDateFromLong(long: Long, resources: android.content.res.Resources): String { + val visitedOn = resources.getString(org.piramalswasthya.sakhi.R.string.track_visited_on) + return "$visitedOn ${dateFormat.format(long)}" } } - fun asDomainModel(): BenWithEctListDomain { + fun asDomainModel(childCount: Int? = null, resources: android.content.res.Resources): BenWithEctListDomain { val recentFill = savedECTRecords.maxByOrNull { it.visitDate } val allowFill = recentFill?.let { val cal = Calendar.getInstance() @@ -117,14 +151,16 @@ data class BenWithEcTrackingCache( return BenWithEctListDomain( // ecBenId, ben.asBasicDomainModel(), - ecr.noOfLiveChildren.toString(), + (childCount ?: ben.noOfAliveChildren).toString(), allowFill, + ectDate = recentFill?.visitDate ?: 0L, + lmpDate = recentFill?.lmpDate ?: 0L, savedECTRecords.map { ECTDomain( it.benId, it.createdDate, it.visitDate, - getECTFilledDateFromLong(it.visitDate), + getECTFilledDateFromLong(it.visitDate, resources), it.syncState ) } @@ -145,6 +181,8 @@ data class BenWithEctListDomain( val ben: BenBasicDomain, val numChildren: String, val allowFill: Boolean, + val ectDate: Long = 0L, + val lmpDate: Long = 0L, val savedECTRecords: List, val allSynced: SyncState? = if (savedECTRecords.isEmpty()) null else if (savedECTRecords.map { it.syncState } diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/FailedWorkerInfo.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/FailedWorkerInfo.kt new file mode 100644 index 000000000..8a64f7f62 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/FailedWorkerInfo.kt @@ -0,0 +1,6 @@ +package org.piramalswasthya.sakhi.model + +data class FailedWorkerInfo( + val workerName: String, + val error: String +) diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/FilariaScreening.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/FilariaScreening.kt new file mode 100644 index 000000000..351209ab1 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/FilariaScreening.kt @@ -0,0 +1,84 @@ +package org.piramalswasthya.sakhi.model + +import androidx.room.Embedded +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.Index +import androidx.room.PrimaryKey +import androidx.room.Relation +import org.piramalswasthya.sakhi.configuration.FormDataModel +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.network.FilariaScreeningDTO + +@Entity( + tableName = "FILARIA_SCREENING", + foreignKeys = [ForeignKey( + entity = BenRegCache::class, + parentColumns = arrayOf("beneficiaryId"/* "householdId"*/), + childColumns = arrayOf("benId" /*"hhId"*/), + onUpdate = ForeignKey.CASCADE, + onDelete = ForeignKey.CASCADE + )], + indices = [Index(name = "ind_filariasn", value = ["benId"/* "hhId"*/])] +) +data class FilariaScreeningCache( + @PrimaryKey(autoGenerate = true) + val id: Int = 0, + val benId: Long, + var mdaHomeVisitDate: Long = System.currentTimeMillis(), + var houseHoldDetailsId: Long, + var sufferingFromFilariasis: Boolean ? = false, + var doseStatus: String ? = null, + var affectedBodyPart: String ? = null, + var otherDoseStatusDetails: String ? = null, + var filariasisCaseCount: String ? = null, + var medicineSideEffect: String ? = "", + var otherSideEffectDetails: String ? = "", + var createdBy: String ? = "", + var diseaseTypeID: Int ? = 0, + var createdDate: Long = System.currentTimeMillis(), + var syncState: SyncState = SyncState.UNSYNCED, +): FormDataModel { + fun toDTO(): FilariaScreeningDTO { + return FilariaScreeningDTO( + id = 0, + benId = benId, + mdaHomeVisitDate = getDateTimeStringFromLong(mdaHomeVisitDate).toString(), + houseHoldDetailsId = houseHoldDetailsId, + doseStatus = doseStatus.toString(), + sufferingFromFilariasis = sufferingFromFilariasis!!, + affectedBodyPart = affectedBodyPart.toString(), + otherDoseStatusDetails = otherDoseStatusDetails.toString(), + medicineSideEffect = medicineSideEffect.toString(), + otherSideEffectDetails = otherSideEffectDetails.toString(), + createdBy = createdBy.toString(), + createdDate = getDateTimeStringFromLong(createdDate).toString(), + diseaseTypeID = diseaseTypeID!!, + + + + ) + } +} + +data class BenWithFilariaScreeningCache( + @Embedded + val ben: BenBasicCache, + @Relation( + parentColumn = "benId", entityColumn = "benId" + ) + val filariaScreeningCache: FilariaScreeningCache?, + + ) { + fun asFilariaScreeningDomainModel(): BenWithFilariaScreeningDomain { + return BenWithFilariaScreeningDomain( + ben = ben.asBasicDomainModel(), + filaria = filariaScreeningCache + ) + } +} + +data class BenWithFilariaScreeningDomain( + val ben: BenBasicDomain, + val filaria: FilariaScreeningCache? +) \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/FormElement.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/FormElement.kt index 1dc869e55..90d622647 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/model/FormElement.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/FormElement.kt @@ -1,6 +1,7 @@ package org.piramalswasthya.sakhi.model import androidx.annotation.ArrayRes +import androidx.annotation.DrawableRes data class FormElement( val id: Int, @@ -15,7 +16,7 @@ data class FormElement( val hasAlertError: Boolean = false, var value: String? = null, val regex: String? = null, - val allCaps: Boolean = false, + var allCaps: Boolean = false, val etInputType: Int = android.text.InputType.TYPE_CLASS_TEXT, val isMobileNumber: Boolean = false, val etMaxLength: Int = 50, @@ -32,4 +33,12 @@ data class FormElement( var isEnabled: Boolean = true, var headingLine: Boolean = true, val showYearFirstInDatePicker: Boolean = false, + @DrawableRes val backgroundDrawable: Int? = null, + @DrawableRes val iconDrawableRes: Int? = null, + var minFiles: Int? = null, + var maxFiles: Int? = null, + var allowedFormats: List? = null, + var maxFileSizeMB: Int? = null, + var selectedFiles: MutableList? = null, + val showDrawable: Boolean? = false, ) \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/FormInputOld.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/FormInputOld.kt index d8e7a592f..b9bf41f08 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/model/FormInputOld.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/FormInputOld.kt @@ -1,17 +1,16 @@ package org.piramalswasthya.sakhi.model import android.text.InputType.TYPE_CLASS_TEXT +import androidx.annotation.DrawableRes import kotlinx.coroutines.flow.MutableStateFlow import java.io.File - data class FormInputOld( val inputType: InputType, var title: String, val subtitle: String? = null, var entries: Array? = null, var required: Boolean, - var value: MutableStateFlow = MutableStateFlow(null), val regex: String? = null, val allCaps: Boolean = false, val etInputType: Int = TYPE_CLASS_TEXT, @@ -23,6 +22,9 @@ data class FormInputOld( var minDecimal: Double? = null, var maxDecimal: Double? = null, val orientation: Int? = null, - var imageFile: File? = null -) + var imageFile: File? = null, + @DrawableRes val iconDrawableRes: Int? = null, +) { + var value: MutableStateFlow = MutableStateFlow(null) +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/GeneralOPDBenificiary.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/GeneralOPDBenificiary.kt new file mode 100644 index 000000000..8b750345d --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/GeneralOPDBenificiary.kt @@ -0,0 +1,153 @@ +package org.piramalswasthya.sakhi.model + +import androidx.room.Entity +import androidx.room.PrimaryKey +import com.squareup.moshi.JsonClass + +@Entity(tableName = "GENERAL_OPD_ACTIVITY") +data class GeneralOPEDBeneficiary( + val benFlowID: Long?, + val beneficiaryRegID: Long?, + val benVisitID: Long?, + val visitCode: Long?, + val benVisitNo: Int?, + val nurseFlag: Int?, + val doctorFlag: Int?, + val pharmacist_flag: Int?, + val lab_technician_flag: Int?, + val radiologist_flag: Int?, + val oncologist_flag: Int?, + val specialist_flag: Int?, + val agentId: String?, + val visitDate: String?, + val modified_by: String?, + val modified_date: String?, + val benName: String?, + val deleted: Boolean?, + val firstName: String?, + val lastName: String?, + val age: String?, + val ben_age_val: Int?, + val genderID: Int?, + val genderName: String?, + val preferredPhoneNum: String?, + val fatherName: String?, + val spouseName: String?, + val districtName: String?, + val servicePointName: String?, + val registrationDate: String?, + val benVisitDate: String?, + val consultationDate: String?, + val consultantID: Long?, + val consultantName: String?, + val visitSession: String?, + val servicePointID: Long?, + val districtID: Long?, + val villageID: Long?, + val vanID: Long?, + @PrimaryKey(autoGenerate = false) + val beneficiaryId: Long, + val dob: String?, + val tc_SpecialistLabFlag: Int?, + val visitReason: String?, + val village: String?, + val visitCategory: String? +) +@JsonClass(generateAdapter = true) +data class GeneralOPDNetwork( + val benFlowID: Long?, + val beneficiaryRegID: Long?, + val benVisitID: Long?, + val visitCode: Long?, + val benVisitNo: Int?, + val nurseFlag: Int?, + val doctorFlag: Int?, + val pharmacist_flag: Int?, + val lab_technician_flag: Int?, + val radiologist_flag: Int?, + val oncologist_flag: Int?, + val specialist_flag: Int?, + val agentId: String?, + val visitDate: String?, + val modified_by: String?, + val modified_date: String?, + val benName: String?, + val deleted: Boolean?, + val firstName: String?, + val lastName: String?, + val age: String?, + val ben_age_val: Int?, + val genderID: Int?, + val genderName: String?, + val preferredPhoneNum: String?, + val fatherName: String?, + val spouseName: String?, + val districtName: String?, + val servicePointName: String?, + val registrationDate: String?, + val benVisitDate: String?, + val consultationDate: String?, + val consultantID: Long?, + val consultantName: String?, + val visitSession: String?, + val servicePointID: Long?, + val districtID: Long?, + val villageID: Long?, + val vanID: Long?, + val beneficiaryId: Long, + val dob: String?, + val tc_SpecialistLabFlag: Int?, + val visitReason: String?, + val village: String?, + val visitCategory: String? +) { + fun asGeneralCacheModel(): GeneralOPEDBeneficiary { + return GeneralOPEDBeneficiary( + benFlowID = benFlowID, + beneficiaryRegID = beneficiaryRegID, + benVisitID = benVisitID, + visitCode = visitCode, + benVisitNo = benVisitNo, + nurseFlag = nurseFlag, + doctorFlag = doctorFlag, + pharmacist_flag = pharmacist_flag, + lab_technician_flag = lab_technician_flag, + radiologist_flag = radiologist_flag, + oncologist_flag = oncologist_flag, + specialist_flag = specialist_flag, + agentId = agentId, + visitDate = visitDate, + modified_by = modified_by, + modified_date = modified_date, + benName = benName, + deleted = deleted, + firstName = firstName, + lastName = lastName, + age = age, + ben_age_val = ben_age_val, + genderID = genderID, + genderName = genderName, + preferredPhoneNum = preferredPhoneNum, + fatherName = fatherName, + spouseName = spouseName, + districtName = districtName, + servicePointName = servicePointName, + registrationDate = registrationDate, + benVisitDate = benVisitDate, + consultationDate = consultationDate, + consultantID = consultantID, + consultantName = consultantName, + visitSession = visitSession, + servicePointID = servicePointID, + districtID = districtID, + villageID = villageID, + vanID = vanID, + beneficiaryId = beneficiaryId, + dob = dob, + tc_SpecialistLabFlag = tc_SpecialistLabFlag, + visitReason = visitReason, + village = village, + visitCategory = visitCategory + ) + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/HRPMicroBirthPlanCache.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/HRPMicroBirthPlanCache.kt index 826244d98..86ba65684 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/model/HRPMicroBirthPlanCache.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/HRPMicroBirthPlanCache.kt @@ -6,6 +6,8 @@ import androidx.room.Index import androidx.room.PrimaryKey import org.piramalswasthya.sakhi.configuration.FormDataModel import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.network.HRPMicroBirthPlanDTO +import org.piramalswasthya.sakhi.network.HRPPregnantAssessDTO @Entity( tableName = "HRP_MICRO_BIRTH_PLAN", @@ -30,7 +32,6 @@ data class HRPMicroBirthPlanCache( var scHosp: String? = null, var usg: String? = null, var block: String? = null, - var bankac: String? = null, var nearestPhc: String? = null, var nearestFru: String? = null, var bloodDonors1: String? = null, @@ -40,5 +41,30 @@ data class HRPMicroBirthPlanCache( var communityMember: String? = null, var communityMemberContact: String? = null, var modeOfTransportation: String? = null, - var syncState: SyncState? = SyncState.UNSYNCED -) : FormDataModel + var processed: String? = "N", + var syncState: SyncState? = SyncState.UNSYNCED, + val bankac: String? = null +) : FormDataModel{ +fun toDTO(): HRPMicroBirthPlanDTO { + return HRPMicroBirthPlanDTO( + id = 0, + benId = benId, + nearestSc = nearestSc, + bloodGroup = bloodGroup, + contactNumber1 = contactNumber1, + contactNumber2 = contactNumber2, + scHosp = scHosp, + usg = usg, + block = block, + nearestPhc = nearestPhc, + nearestFru = nearestFru, + bloodDonors1 = bloodDonors1, + bloodDonors2 = bloodDonors2, + birthCompanion = birthCompanion, + careTaker = careTaker, + communityMember = communityMember, + communityMemberContact = communityMemberContact, + modeOfTransportation = modeOfTransportation + ) +} +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/HRPNonPregnantTrackCache.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/HRPNonPregnantTrackCache.kt index 00f7128e9..b48711f35 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/model/HRPNonPregnantTrackCache.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/HRPNonPregnantTrackCache.kt @@ -49,11 +49,12 @@ data class HRPNonPregnantTrackCache( var isPregnant: String? = null, var syncState: SyncState = SyncState.UNSYNCED ) : FormDataModel { - fun asDomainModel(): HRPPregnantTrackDomain { + fun asDomainModel(resources: android.content.res.Resources): HRPPregnantTrackDomain { return HRPPregnantTrackDomain( id = id, dateOfVisit = getDateStrFromLong(visitDate), - filledOnString = "Follow Up " + HelperUtil.getTrackDate(visitDate), + filledOnString = resources.getString(org.piramalswasthya.sakhi.R.string.track_follow_up_label) + + " " + HelperUtil.getTrackDate(visitDate, resources), syncState = syncState ) } @@ -101,21 +102,22 @@ data class BenWithHRNPTrackingCache( ) { companion object { - private val dateFormat = SimpleDateFormat("EEE, MMM dd yyyy", Locale.getDefault()) + private val dateFormat = SimpleDateFormat("EEE, MMM dd yyyy", Locale.ENGLISH) - private fun getHRNPTFilledDateFromLong(long: Long?): String { - return "Visited on ${dateFormat.format(long)}" + private fun getHRNPTFilledDateFromLong(long: Long?, resources: android.content.res.Resources): String { + val visitedOn = resources.getString(org.piramalswasthya.sakhi.R.string.track_visited_on) + return "$visitedOn ${dateFormat.format(long)}" } } - fun asDomainModel(): BenWithHRNPTListDomain { + fun asDomainModel(resources: android.content.res.Resources): BenWithHRNPTListDomain { return BenWithHRNPTListDomain( ben.asBasicDomainModel(), savedTrackings.map { HRNPTDomain( it.benId, it.visitDate, - getHRNPTFilledDateFromLong(it.visitDate), + getHRNPTFilledDateFromLong(it.visitDate, resources), it.syncState ) } diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/HRPPregnantTrackCache.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/HRPPregnantTrackCache.kt index f4f0a9e6a..4d8c2bfbc 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/model/HRPPregnantTrackCache.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/HRPPregnantTrackCache.kt @@ -60,11 +60,11 @@ data class HRPPregnantTrackCache( var syncState: SyncState = SyncState.UNSYNCED ) : FormDataModel { - fun asDomainModel(): HRPPregnantTrackDomain { + fun asDomainModel(resources: android.content.res.Resources): HRPPregnantTrackDomain { return HRPPregnantTrackDomain( id = id, dateOfVisit = visit + " : " + getDateStrFromLong(visitDate), - filledOnString = visit + HelperUtil.getTrackDate(visitDate), + filledOnString = visit + HelperUtil.getTrackDate(visitDate, resources), syncState = syncState ) } @@ -145,14 +145,15 @@ data class BenWithHRPTrackingCache( ) { companion object { - private val dateFormat = SimpleDateFormat("EEE, MMM dd yyyy", Locale.getDefault()) + private val dateFormat = SimpleDateFormat("EEE, MMM dd yyyy", Locale.ENGLISH) - private fun getHRPTFilledDateFromLong(long: Long?): String { - return "Visited on ${dateFormat.format(long)}" + private fun getHRPTFilledDateFromLong(long: Long?, resources: android.content.res.Resources): String { + val visitedOn = resources.getString(org.piramalswasthya.sakhi.R.string.track_visited_on) + return "$visitedOn ${dateFormat.format(long)}" } } - fun asDomainModel(): BenWithHRPTListDomain { + fun asDomainModel(resources: android.content.res.Resources): BenWithHRPTListDomain { return BenWithHRPTListDomain( ben.asBasicDomainModel(), lmpString = getDateString(assessCache.lmpDate), @@ -163,7 +164,7 @@ data class BenWithHRPTrackingCache( HRPTDomain( it.benId, it.visitDate, - getHRPTFilledDateFromLong(it.visitDate), + getHRPTFilledDateFromLong(it.visitDate, resources), it.syncState ) } diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/HomeVisitDomain.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/HomeVisitDomain.kt new file mode 100644 index 000000000..e89d115f3 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/HomeVisitDomain.kt @@ -0,0 +1,39 @@ +package org.piramalswasthya.sakhi.model + +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.model.dynamicEntity.anc.ANCFormResponseJsonEntity +import java.text.SimpleDateFormat +import java.util.Locale + +data class HomeVisitDomain( + val id: Int, + val benId: Long, + val visitNumber: Int, + val visitDate: Long, + val visitDateString: String, + val formDataJson: String, + val syncState: SyncState, + val isSynced: Boolean +) { + companion object { + fun fromEntity(entity: ANCFormResponseJsonEntity, visitNumber: Int): HomeVisitDomain { + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) + val visitDate = try { + dateFormat.parse(entity.visitDate)?.time ?: entity.createdAt + } catch (e: Exception) { + entity.createdAt + } + + return HomeVisitDomain( + id = entity.id, + benId = entity.benId, + visitNumber = visitNumber, + visitDate = visitDate, + visitDateString = entity.visitDate, + formDataJson = entity.formDataJson, + syncState = if (entity.isSynced) SyncState.SYNCED else SyncState.UNSYNCED, + isSynced = entity.isSynced + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/HomeVisitUiState.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/HomeVisitUiState.kt new file mode 100644 index 000000000..9e039a638 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/HomeVisitUiState.kt @@ -0,0 +1,6 @@ +package org.piramalswasthya.sakhi.model + +data class HomeVisitUiState( + val canAddHomeVisit: Boolean, + val canViewHomeVisit: Boolean +) \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/Household.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/Household.kt index 125bd77b8..cde5d4088 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/model/Household.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/Household.kt @@ -76,15 +76,16 @@ data class HouseholdCache( var updatedBy: String? = null, var updatedTimeStamp: Long? = null, var processed: String, - var isDraft: Boolean + var isDraft: Boolean, + var isDeactivate: Boolean =false ) : FormDataModel { - fun asNetworkModel(): HouseholdNetwork { + fun asNetworkModel(user: User): HouseholdNetwork { return HouseholdNetwork( Countyid = locationRecord.country.id, Processed = processed, -// ProviderServiceMapID = userCache.serviceMapId, + providerServiceMapID = user.serviceMapId, // VanID = userCache.vanId, ashaId = ashaId, availabilityOfToilet = amenities?.availabilityOfToilet, @@ -134,6 +135,7 @@ data class HouseholdCache( mohallaName = family?.mohallaName, rationCardDetails = family?.rationCardDetails, districtname = locationRecord.district.name, + isDeactivate = isDeactivate ) } @@ -235,7 +237,7 @@ data class HouseholdNetwork( @Json(name = "updatedDate") val updatedDate: String? = null, -// @Json(name = "ProviderServiceMapID") val ProviderServiceMapID: Int, + @Json(name = "ProviderServiceMapID") val providerServiceMapID: Int, // @Json(name = "VanID") val VanID: Int, @@ -252,6 +254,7 @@ data class HouseholdNetwork( @Json(name = "blockid") val blockid: Int = 0, @Json(name = "villageid") val villageid: Int = 0, + @Json(name = "isDeactivate")var isDeactivate: Boolean =false ) @@ -267,7 +270,9 @@ data class HouseholdBasicCache( contactNumber = household.family?.familyHeadPhoneNo?.toString() ?: "Not Available", headSurname = household.family?.familyName ?: "Not Available", headFullName = "${household.family?.familyHeadName} ${household.family?.familyName ?: ""}", - numMembers = numMembers + numMembers = numMembers, + isDeactivate = household.isDeactivate, + createdTimeStamp = household.createdTimeStamp ) } @@ -280,4 +285,7 @@ data class HouseHoldBasicDomain( val contactNumber: String, val headFullName: String = "$headName $headSurname", val numMembers: Int, -) \ No newline at end of file + var isDeactivate: Boolean =false, + var createdTimeStamp: Long? = null, + + ) \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/IRSRoundScreening.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/IRSRoundScreening.kt new file mode 100644 index 000000000..c7cc43fd5 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/IRSRoundScreening.kt @@ -0,0 +1,50 @@ +package org.piramalswasthya.sakhi.model + +import androidx.room.Embedded +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey +import androidx.room.Relation +import org.piramalswasthya.sakhi.configuration.FormDataModel +import org.piramalswasthya.sakhi.network.ScreeningRoundDTO + + +@Entity( + tableName = "IRS_ROUND", + indices = [Index(name = "ind_irs_round", value = ["householdId"])] +) +data class IRSRoundScreening( + @PrimaryKey(autoGenerate = true) + val id: Int = 0, + var date: Long = System.currentTimeMillis(), + var rounds: Int = 0, + var householdId: Long = 0L +) : FormDataModel { + fun toDTO(): ScreeningRoundDTO { + return ScreeningRoundDTO( + date = getDateTimeStringFromLong(date).toString(), + rounds = rounds, + householdId = householdId + ) + } +} + +data class BenWithScreeningRound( + @Embedded + val ben: BenBasicCache, + @Relation( + parentColumn = "benId", + entityColumn = "householdId" + ) + val round: List +) { + fun asScreeningRoundDomainModel(): BenWithScreeningRoundDomain { + return BenWithScreeningRoundDomain( + round = round + ) + } +} + +data class BenWithScreeningRoundDomain( + val round: List +) \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/Icon.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/Icon.kt index 38409d46b..dc9293092 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/model/Icon.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/Icon.kt @@ -13,6 +13,10 @@ data class Icon( val allowRedBorder: Boolean = false ) + + + + data class ImmunizationIcon( val benId: Long, val hhId: Long, diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/Immunization.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/Immunization.kt index d55680815..e107d152a 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/model/Immunization.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/Immunization.kt @@ -67,7 +67,9 @@ data class ImmunizationCache( var createdDate: Long = System.currentTimeMillis(), var updatedBy: String, val updatedDate: Long = System.currentTimeMillis(), - var syncState: SyncState + var syncState: SyncState, + var mcpCardSummary1 : String ? = null, + var mcpCardSummary2 : String ? = null ) : FormDataModel { fun asPostModel(): ImmunizationPost { return ImmunizationPost( @@ -81,7 +83,9 @@ data class ImmunizationCache( createdDate = getDateStrFromLong(createdDate), createdBy = createdBy, modifiedBy = updatedBy, - lastModDate = getDateStrFromLong(updatedDate) + lastModDate = getDateStrFromLong(updatedDate), + mcpCardSummary1 = mcpCardSummary1, + mcpCardSummary2 = mcpCardSummary2 ) } } @@ -118,6 +122,7 @@ data class VaccineCategoryDomain( val category: ChildImmunizationCategory, val categoryString: String = category.name, val vaccineStateList: List, + var isBenDeath: Boolean =false // val onClick: (Long, Int) -> Unit ) @@ -127,6 +132,7 @@ data class VaccineDomain( val vaccineName: String, val vaccineCategory: ChildImmunizationCategory, val state: VaccineState, + var isSwitchChecked:Boolean = false ) class VaccineClickListener(private val clickListener: (benId: Long, vaccineId: Int) -> Unit) { @@ -153,7 +159,9 @@ data class ImmunizationPost( val createdDate: String? = null, val createdBy: String, var lastModDate: String? = null, - var modifiedBy: String + var modifiedBy: String, + var mcpCardSummary1 : String ? = null, + var mcpCardSummary2 : String ? = null ) { fun toCacheModel(): ImmunizationCache { return ImmunizationCache( @@ -170,7 +178,9 @@ data class ImmunizationPost( createdDate = getLongFromDate(createdDate), updatedBy = modifiedBy, updatedDate = getLongFromDate(lastModDate), - syncState = SyncState.SYNCED + syncState = SyncState.SYNCED, + mcpCardSummary1 = mcpCardSummary1, + mcpCardSummary2 = mcpCardSummary2, ) } } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/Incentives.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/Incentives.kt index 7b44b9480..0f2563108 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/model/Incentives.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/Incentives.kt @@ -1,16 +1,21 @@ package org.piramalswasthya.sakhi.model +import android.os.Parcelable +import androidx.room.ColumnInfo import androidx.room.Embedded import androidx.room.Entity import androidx.room.ForeignKey import androidx.room.Index import androidx.room.PrimaryKey import androidx.room.Relation +import com.google.gson.annotations.SerializedName import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import kotlinx.parcelize.Parcelize import org.piramalswasthya.sakhi.network.getLongFromDate @Entity(tableName = "INCENTIVE_ACTIVITY") +@Parcelize data class IncentiveActivityCache( @PrimaryKey val id: Long, @@ -22,15 +27,17 @@ data class IncentiveActivityCache( val state: Int, val district: Int, val group: String, + val groupName: String, val fmrCode: String?, val fmrCodeOld: String?, // val createdDate: Long, // val createdBy: String, // val updatedDate: Long, // val updatedBy: String, -) +) : Parcelable @JsonClass(generateAdapter = true) +@Parcelize data class IncentiveActivityNetwork( val id: Long, val name: String, @@ -40,13 +47,14 @@ data class IncentiveActivityNetwork( val state: Int, val district: Int, val group: String, + val groupName: String, val fmrCode: String?, val fmrCodeOld: String?, val createdDate: String, val createdBy: String, val updatedDate: String, val updatedBy: String, -) { +) : Parcelable { fun asCacheModel(): IncentiveActivityCache { return IncentiveActivityCache( id = id, @@ -57,6 +65,7 @@ data class IncentiveActivityNetwork( state = state, district = district, group = group, + groupName = groupName, fmrCode = fmrCode, fmrCodeOld = fmrCodeOld ) @@ -64,21 +73,25 @@ data class IncentiveActivityNetwork( } @JsonClass(generateAdapter = true) +@Parcelize data class IncentiveActivityListRequest( @Json(name = "state") val stateId: Int, @Json(name = "district") - val districtId: Int -) + val districtId: Int, + @Json(name = "langCode") + val language: String +) : Parcelable @JsonClass(generateAdapter = true) +@Parcelize data class IncentiveActivityListResponse( val data: List, val statusCode: Int, val errorMessage: String, val status: String -) +) : Parcelable @Entity( tableName = "INCENTIVE_RECORD", @@ -91,9 +104,11 @@ data class IncentiveActivityListResponse( )], indices = [Index(name = "incentiveInd", value = ["activityId"])] ) +@Parcelize data class IncentiveRecordCache( @PrimaryKey val id: Long, + @ColumnInfo val activityId: Long, val ashaId: Int, val benId: Long, @@ -105,9 +120,11 @@ data class IncentiveRecordCache( val createdBy: String, val updatedDate: Long, val updatedBy: String, -) - + @ColumnInfo(defaultValue = "0") + val isEligible : Boolean +) : Parcelable +@Parcelize data class IncentiveRecordNetwork( val id: Long, val activityId: Long, @@ -121,7 +138,8 @@ data class IncentiveRecordNetwork( val createdBy: String, val updatedDate: String, val updatedBy: String, -) { + val isEligible : Boolean +) : Parcelable { fun asCacheModel(): IncentiveRecordCache { return IncentiveRecordCache( id = id, @@ -136,26 +154,32 @@ data class IncentiveRecordNetwork( createdBy = createdBy, updatedDate = getLongFromDate(updatedDate), updatedBy = updatedBy, + isEligible = isEligible ) } } @JsonClass(generateAdapter = true) +@Parcelize data class IncentiveRecordListRequest( @Json(name = "ashaId") val userId: Int, val fromDate: String, val toDate: String, -) + val villageID : Int + +) : Parcelable @JsonClass(generateAdapter = true) +@Parcelize data class IncentiveRecordListResponse( val data: List, val statusCode: Int, val errorMessage: String, val status: String -) +) : Parcelable +@Parcelize data class IncentiveCache( @Embedded val record: IncentiveRecordCache, @@ -163,7 +187,7 @@ data class IncentiveCache( val activity: IncentiveActivityCache, @Relation(parentColumn = "benId", entityColumn = "benId") val ben: BenBasicCache?, -) { +) : Parcelable { fun asDomainModel(): IncentiveDomain { return IncentiveDomain( record, @@ -172,24 +196,65 @@ data class IncentiveCache( ) } } +@Parcelize +data class IncentiveActivityWithRecords( + @Embedded + val activity: IncentiveActivityCache, + @Relation( + parentColumn = "id", + entityColumn = "activityId" + ) + val records: List +) : Parcelable { + fun asDomainModel() = IncentiveActivityDomain(activity, records) +} + + +@Parcelize data class IncentiveDomain( val record: IncentiveRecordCache, val activity: IncentiveActivityCache, - val ben: BenBasicDomain? -) + val ben: BenBasicDomain?, + var uploadedFiles: List = emptyList(), + var fileCount: Int = 0, + var isSubmitted: Boolean = false, + var submittedAt: Long = 0L, + var serverFileUrls: List = emptyList() +) : Parcelable +@Parcelize +data class IncentiveActivityDomain( + val activity: IncentiveActivityCache, + val records: List +) : Parcelable +@Parcelize data class IncentiveDomainDTO( val id: Long = 0, - val group: String, + var group: String, + var groupName : String, val name: String, - val description: String, + var description: String, val paymentParam: String, val rate: Long, var noOfClaims: Int, var amountClaimed: Long, - var fmrCode: String? -) + var fmrCode: String?, + val documentsSubmitted: String? = null +) : Parcelable + +@Parcelize +data class IncentiveGrouped( + val activityName: String, + val totalAmount: Long, + val count: Int, + val groupName: String, + val description: String, + val activity: IncentiveActivityCache, + val hasZeroBen: Boolean = false, + val defaultIncentive : Boolean = false, + val isEligible: Boolean = false +) : Parcelable diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/InfantReg.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/InfantReg.kt index de23c8fea..1ba5b58af 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/model/InfantReg.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/InfantReg.kt @@ -40,6 +40,13 @@ data class InfantRegCache( var referred: String? = null, var hadBirthDefect: String? = null, var birthDefect: String? = null, + + var isSNCU: String? = "No", + var deliveryDischargeSummary1 : String? = null, + var deliveryDischargeSummary2 : String? = null, + var deliveryDischargeSummary3 : String? = null, + var deliveryDischargeSummary4 : String? = null, + var otherDefect: String? = null, var weight: Double? = 0.0, var breastFeedingStarted: Boolean? = null, @@ -70,6 +77,11 @@ data class InfantRegCache( id = id, benId = motherBenId, childBenId = childBenId, + isSNCU = isSNCU, + deliveryDischargeSummary1 = deliveryDischargeSummary1, + deliveryDischargeSummary2 = deliveryDischargeSummary2, + deliveryDischargeSummary3 = deliveryDischargeSummary3, + deliveryDischargeSummary4 = deliveryDischargeSummary4, isActive = isActive, babyName = babyName, babyIndex = babyIndex, @@ -82,7 +94,7 @@ data class InfantRegCache( hadBirthDefect = hadBirthDefect, birthDefect = birthDefect, otherDefect = otherDefect, - weight = weight, + weight = weight?:0.0, breastFeedingStarted = breastFeedingStarted, opv0Dose = opv0Dose?.let { getDateStringFromLong(it) }, bcgDose = bcgDose?.let { getDateStringFromLong(it) }, @@ -138,17 +150,33 @@ data class BenWithDoAndIrCache( data class InfantRegDomain( val motherBen: BenBasicDomain, val babyIndex: Int, - val babyName: String = "Baby $babyIndex of ${motherBen.benFullName}", + var babyName: String = "Baby $babyIndex of ${motherBen.benFullName}", val deliveryOutcome: DeliveryOutcomeCache, val savedIr: InfantRegCache?, val syncState: SyncState? = savedIr?.syncState -) +) { + val customName: String + get() = if (babyIndex == 0) { + "${babyIndex+1}st baby of ${motherBen.benName}" + } else if (babyIndex == 1) { + "${babyIndex+1}nd baby of ${motherBen.benName}" + } else { + "${babyIndex+1}rd baby of ${motherBen.benName}" + } +} data class InfantRegPost( val id: Long = 0, val benId: Long, val childBenId: Long, val isActive: Boolean, + var isSNCU: String? = null, + var deliveryDischargeSummary1 : String? = null, + var deliveryDischargeSummary2 : String? = null, + var deliveryDischargeSummary3 : String? = null, + var deliveryDischargeSummary4 : String? = null, + + val babyName: String? = null, val babyIndex: Int, val infantTerm: String? = null, @@ -160,7 +188,7 @@ data class InfantRegPost( val hadBirthDefect: String? = null, val birthDefect: String? = null, val otherDefect: String? = null, - val weight: Double? = null, + val weight: Double, val breastFeedingStarted: Boolean? = null, val opv0Dose: String? = null, val bcgDose: String? = null, @@ -175,6 +203,11 @@ data class InfantRegPost( return InfantRegCache( id = id, motherBenId = benId, + isSNCU =isSNCU, + deliveryDischargeSummary1=deliveryDischargeSummary1, + deliveryDischargeSummary2=deliveryDischargeSummary2, + deliveryDischargeSummary3=deliveryDischargeSummary3, + deliveryDischargeSummary4=deliveryDischargeSummary4, childBenId = childBenId, isActive = isActive, babyName = babyName, diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/InputType.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/InputType.kt index 8aaf5c9bf..261ad91ed 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/model/InputType.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/InputType.kt @@ -11,5 +11,9 @@ enum class InputType { CHECKBOXES, TIME_PICKER, HEADLINE, - AGE_PICKER + AGE_PICKER, + FILE_UPLOAD, + MULTIFILE_UPLOAD, + BUTTON, + NUMBER_PICKER } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/KalaAzarScreeningCache.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/KalaAzarScreeningCache.kt new file mode 100644 index 000000000..8f533a509 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/KalaAzarScreeningCache.kt @@ -0,0 +1,99 @@ +package org.piramalswasthya.sakhi.model + +import androidx.room.Embedded +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.Index +import androidx.room.PrimaryKey +import androidx.room.Relation +import org.piramalswasthya.sakhi.configuration.FormDataModel +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.network.KALAZARScreeningDTO +import org.piramalswasthya.sakhi.network.getLongFromDate + +@Entity( + tableName = "KALAZAR_SCREENING", + foreignKeys = [ForeignKey( + entity = BenRegCache::class, + parentColumns = arrayOf("beneficiaryId"/* "householdId"*/), + childColumns = arrayOf("benId" /*"hhId"*/), + onUpdate = ForeignKey.CASCADE, + onDelete = ForeignKey.CASCADE + )], + indices = [Index(name = "ind_kalazarsn", value = ["benId"/* "hhId"*/])] +) +data class KalaAzarScreeningCache( + @PrimaryKey(autoGenerate = true) + val id: Int = 0, + val benId: Long, + var visitDate: Long = System.currentTimeMillis(), + val houseHoldDetailsId: Long, + var beneficiaryStatus: String ? = "", + var beneficiaryStatusId: Int = 0, + var dateOfDeath: Long = System.currentTimeMillis(), + var placeOfDeath: String ? = "", + var otherPlaceOfDeath: String ? = "", + var reasonForDeath: String ? = "", + var otherReasonForDeath: String ? = "", + var rapidDiagnosticTest: String ? = "", + var dateOfRdt: Long = System.currentTimeMillis(), + var kalaAzarCaseStatus: String ? = "", + var referredTo: Int ? = 0, + var referToName: String ? = "", + var otherReferredFacility: String ? = "", + var diseaseTypeID: Int ? = 0, + var createdDate: Long = System.currentTimeMillis(), + var createdBy: String ? = "", + var followUpPoint: Int ? = 0, + var syncState: SyncState = SyncState.UNSYNCED, +): FormDataModel { + fun toDTO(): KALAZARScreeningDTO { + return KALAZARScreeningDTO( + id = 0, + benId = benId, + visitDate = getDateTimeStringFromLong(visitDate).toString(), + kalaAzarCaseStatus = kalaAzarCaseStatus, + houseHoldDetailsId = houseHoldDetailsId, + referredTo = referredTo, + referToName = referToName.toString(), + otherReferredFacility = otherReferredFacility.toString(), + createdDate = getDateTimeStringFromLong(createdDate).toString(), + syncState = SyncState.SYNCED, + diseaseTypeID = diseaseTypeID, + beneficiaryStatusId = beneficiaryStatusId, + beneficiaryStatus = beneficiaryStatus.toString(), + createdBy = createdBy.toString(), + rapidDiagnosticTest = rapidDiagnosticTest.toString(), + dateOfRdt = getDateTimeStringFromLong(dateOfRdt).toString(), + dateOfDeath = getDateTimeStringFromLong(dateOfDeath).toString(), + reasonForDeath = reasonForDeath.toString(), + otherReasonForDeath = otherReasonForDeath.toString(), + otherPlaceOfDeath = otherPlaceOfDeath.toString(), + placeOfDeath = placeOfDeath.toString(), + followUpPoint = followUpPoint + + ) + } +} + +data class BenWithKALAZARScreeningCache( + @Embedded + val ben: BenBasicCache, + @Relation( + parentColumn = "benId", entityColumn = "benId" + ) + val kalazarScreening: KalaAzarScreeningCache?, + + ) { + fun asKALAZARScreeningDomainModel(): BenWithKALAZARScreeningDomain { + return BenWithKALAZARScreeningDomain( + ben = ben.asBasicDomainModel(), + kala = kalazarScreening + ) + } +} + +data class BenWithKALAZARScreeningDomain( + val ben: BenBasicDomain, + val kala: KalaAzarScreeningCache? +) \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/LeprosyScreening.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/LeprosyScreening.kt new file mode 100644 index 000000000..ba9d828bf --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/LeprosyScreening.kt @@ -0,0 +1,271 @@ +package org.piramalswasthya.sakhi.model + +import androidx.room.Embedded +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.Index +import androidx.room.PrimaryKey +import androidx.room.Relation +import org.piramalswasthya.sakhi.configuration.FormDataModel +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.network.LeprosyFollowUpDTO +import org.piramalswasthya.sakhi.network.LeprosyScreeningDTO + + +@Entity( + tableName = "LEPROSY_SCREENING", + foreignKeys = [ForeignKey( + entity = BenRegCache::class, + parentColumns = arrayOf("beneficiaryId"), + childColumns = arrayOf("benId"), + onUpdate = ForeignKey.CASCADE, + onDelete = ForeignKey.CASCADE + )], + indices = [Index(name = "ind_leprosysn", value = ["benId"], unique = true)] +) +data class LeprosyScreeningCache( + @PrimaryKey(autoGenerate = true) + val id: Int = 0, + val benId: Long, + var homeVisitDate: Long = System.currentTimeMillis(), + var leprosyStatusDate: Long = System.currentTimeMillis(), + var dateOfDeath: Long = System.currentTimeMillis(), + var houseHoldDetailsId: Long, + var leprosyStatus: String? = "", + var referredTo: Int? = 0, + var leprosyState: String? = "Screening", + var referToName: String? = null, + var otherReferredTo: String? = null, + var typeOfLeprosy: String? = null, + var remarks: String? = null, + var beneficiaryStatus: String? = null, + var placeOfDeath: String? = null, + var otherPlaceOfDeath: String? = null, + var reasonForDeath: String? = null, + var otherReasonForDeath: String? = null, + var diseaseTypeID: Int? = 0, + var beneficiaryStatusId: Int? = 0, + var leprosySymptoms: String? = null, + var leprosySymptomsPosition: Int? = 1, + var lerosyStatusPosition: Int? = 0, + var currentVisitNumber: Int = 1, + var visitLabel: String? = "Visit -1", + var visitNumber: Int? = 1, + var isConfirmed: Boolean = false, + var treatmentStartDate: Long = System.currentTimeMillis(), + var totalFollowUpMonthsRequired: Int = 0, + var treatmentEndDate: Long = System.currentTimeMillis(), + val mdtBlisterPackRecived: String? = null, + var treatmentStatus: String? = null, + var createdBy: String, + var createdDate: Long = System.currentTimeMillis(), + var modifiedBy: String, + var lastModDate: Long = System.currentTimeMillis(), + var recurrentUlceration: String? = null, + var recurrentUlcerationId: Int? = 1, + var recurrentTingling: String? = null, + var recurrentTinglingId: Int? = 1, + var hypopigmentedPatch: String? = null, + var hypopigmentedPatchId: Int? = 1, + var thickenedSkin: String? = null, + var thickenedSkinId: Int? = 1, + var skinNodules: String? = null, + var skinNodulesId: Int? = 1, + var skinPatchDiscoloration: String? = null, + var skinPatchDiscolorationId: Int? = 1, + var recurrentNumbness: String? = null, + var recurrentNumbnessId: Int? = 1, + var clawingFingers: String? = null, + var clawingFingersId: Int? = 1, + var tinglingNumbnessExtremities: String? = null, + var tinglingNumbnessExtremitiesId: Int? = 1, + var inabilityCloseEyelid: String? = null, + var inabilityCloseEyelidId: Int? = 1, + var difficultyHoldingObjects: String? = null, + var difficultyHoldingObjectsId: Int? = 1, + var weaknessFeet: String? = null, + var weaknessFeetId: Int? = 1, + + var syncState: SyncState = SyncState.UNSYNCED, +) : FormDataModel { + + fun toDTO(): LeprosyScreeningDTO { + return LeprosyScreeningDTO( + benId = benId, + homeVisitDate = getDateTimeStringFromLong(homeVisitDate).toString(), + leprosyStatusDate = getDateTimeStringFromLong(leprosyStatusDate).toString(), + dateOfDeath = getDateTimeStringFromLong(dateOfDeath).toString(), + houseHoldDetailsId = houseHoldDetailsId, + leprosyStatus = leprosyStatus, + referredTo = referredTo, + referToName = referToName, + otherReferredTo = otherReferredTo, + typeOfLeprosy = typeOfLeprosy, + remarks = remarks, + beneficiaryStatus = beneficiaryStatus, + placeOfDeath = placeOfDeath, + otherPlaceOfDeath = otherPlaceOfDeath, + reasonForDeath = reasonForDeath, + otherReasonForDeath = otherReasonForDeath, + diseaseTypeID = diseaseTypeID, + beneficiaryStatusId = beneficiaryStatusId, + leprosySymptoms = leprosySymptoms, + leprosySymptomsPosition = leprosySymptomsPosition, + lerosyStatusPosition = lerosyStatusPosition, + currentVisitNumber = currentVisitNumber, + visitLabel = visitLabel, + visitNumber = visitNumber, + isConfirmed = isConfirmed, + leprosyState = leprosyState, + treatmentStartDate = getDateTimeStringFromLong(treatmentStartDate).toString(), + totalFollowUpMonthsRequired = totalFollowUpMonthsRequired, + treatmentEndDate = getDateTimeStringFromLong(treatmentEndDate).toString(), + mdtBlisterPackRecived = mdtBlisterPackRecived, + createdBy = createdBy, + createdDate =getDateTimeStringFromLong(createdDate).toString(), + modifiedBy = modifiedBy, + lastModDate = getDateTimeStringFromLong(lastModDate).toString(), + treatmentStatus = treatmentStatus, + recurrentUlceration = recurrentUlceration, + recurrentUlcerationId = recurrentUlcerationId, + recurrentTingling = recurrentTingling, + recurrentTinglingId = recurrentTinglingId, + hypopigmentedPatchId = hypopigmentedPatchId, + hypopigmentedPatch = hypopigmentedPatch, + thickenedSkin = thickenedSkin, + thickenedSkinId = thickenedSkinId, + skinNodules = skinNodules, + skinNodulesId = skinNodulesId, + skinPatchDiscoloration = skinPatchDiscoloration, + skinPatchDiscolorationId = skinPatchDiscolorationId, + recurrentNumbness = recurrentNumbness, + recurrentNumbnessId = recurrentNumbnessId, + clawingFingers = clawingFingers, + clawingFingersId = clawingFingersId, + tinglingNumbnessExtremities = tinglingNumbnessExtremities, + tinglingNumbnessExtremitiesId = tinglingNumbnessExtremitiesId, + inabilityCloseEyelid = inabilityCloseEyelid, + inabilityCloseEyelidId = inabilityCloseEyelidId, + difficultyHoldingObjects = difficultyHoldingObjects, + difficultyHoldingObjectsId = difficultyHoldingObjectsId, + weaknessFeet = weaknessFeet, + weaknessFeetId = weaknessFeetId, + ) + } +} + + +data class BenWithLeprosyScreeningCache( + @Embedded + val ben: BenBasicCache, + + @Relation( + parentColumn = "benId", + entityColumn = "benId" + ) + val leprosyScreening: LeprosyScreeningCache?, + + @Relation( + parentColumn = "benId", + entityColumn = "benId" + ) + val followUps: List +) { + fun getCurrentFollowUp(): LeprosyFollowUpCache? { + return followUps.find { it.visitNumber == leprosyScreening?.currentVisitNumber } + } + + fun getAllFollowUpsForCurrentVisit(): List { + return followUps.filter { it.visitNumber == leprosyScreening?.currentVisitNumber } + } + + fun getLastFollowUpForCurrentVisit(): LeprosyFollowUpCache? { + return getAllFollowUpsForCurrentVisit().maxByOrNull { it.followUpDate } + } + + fun asLeprosyScreeningDomainModel(): BenWithLeprosyScreeningDomain { + return BenWithLeprosyScreeningDomain( + ben = ben.asBasicDomainModel(), + leprosy = leprosyScreening, + followUps = followUps, + currentFollowUp = getCurrentFollowUp(), + currentVisitFollowUps = getAllFollowUpsForCurrentVisit(), + lastFollowUp = getLastFollowUpForCurrentVisit() + ) + } +} + +data class BenWithLeprosyScreeningDomain( + val ben: BenBasicDomain, + val leprosy: LeprosyScreeningCache?, + val followUps: List, + val currentFollowUp: LeprosyFollowUpCache?, + val currentVisitFollowUps: List, + val lastFollowUp: LeprosyFollowUpCache? +)@Entity( + tableName = "LEPROSY_FOLLOW_UP", + indices = [ + Index(name = "ind_leprosy_followup_ben", value = ["benId"]), + Index(name = "ind_leprosy_followup_visit", value = ["benId", "visitNumber"]) + ] +) +data class LeprosyFollowUpCache( + @PrimaryKey(autoGenerate = true) + val id: Int = 0, + val benId: Long, + val visitNumber: Int, + var followUpDate: Long = System.currentTimeMillis(), + var treatmentStatus: String? = null, + var mdtBlisterPackReceived: String? = null, + var treatmentCompleteDate: Long = 0, + var remarks: String? = null, + var homeVisitDate: Long = System.currentTimeMillis(), + var leprosySymptoms: String? = null, + var typeOfLeprosy: String? = null, + var leprosySymptomsPosition: Int? = 1, + var visitLabel: String? = "Visit -1", + var leprosyStatus: String? = "", + var referredTo: Int? = 0, + var referToName: String? = null, + var treatmentEndDate: Long = System.currentTimeMillis(), + val mdtBlisterPackRecived: String? = null, + var treatmentStartDate: Long = System.currentTimeMillis(), + val createdBy: String, + val createdDate: Long = System.currentTimeMillis(), + val modifiedBy: String, + val lastModDate: Long = System.currentTimeMillis(), + var syncState: SyncState = SyncState.UNSYNCED +) : FormDataModel { + + fun toDTO(): LeprosyFollowUpDTO { + return LeprosyFollowUpDTO( + benId = benId, + visitNumber = visitNumber, + followUpDate = getDateTimeStringFromLong(followUpDate).toString(), + treatmentStatus = treatmentStatus, + mdtBlisterPackReceived = mdtBlisterPackReceived, + treatmentCompleteDate = getDateTimeStringFromLong(treatmentCompleteDate).toString(), + remarks = remarks, + homeVisitDate = getDateTimeStringFromLong(homeVisitDate).toString(), + leprosySymptoms = leprosySymptoms, + typeOfLeprosy = typeOfLeprosy, + leprosySymptomsPosition = leprosySymptomsPosition, + visitLabel = visitLabel, + leprosyStatus = leprosyStatus, + referredTo = referredTo, + referToName = referToName, + treatmentEndDate = getDateTimeStringFromLong(treatmentEndDate).toString(), + mdtBlisterPackRecived = mdtBlisterPackRecived, + createdBy = createdBy, + createdDate =getDateTimeStringFromLong(createdDate).toString(), + modifiedBy = modifiedBy, + lastModDate = getDateTimeStringFromLong(lastModDate).toString(), + treatmentStartDate = getDateTimeStringFromLong(treatmentStartDate).toString() + ) + } +} + +data class LeprosyFollowUpRequestDTO( + val userName: String, + val leprosyFollowUpLists: List +) diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/MDSR.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/MDSR.kt index d4022f202..1a68b6146 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/model/MDSR.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/MDSR.kt @@ -12,6 +12,7 @@ import org.piramalswasthya.sakhi.database.room.SyncState import org.piramalswasthya.sakhi.network.getLongFromDate import java.text.SimpleDateFormat import java.util.Calendar +import java.util.Locale @Entity( tableName = "MDSR", @@ -29,6 +30,9 @@ data class MDSRCache( @PrimaryKey(autoGenerate = true) val id: Int = 0, val benId: Long, + var mdsr1File: String? = null, + var mdsr2File: String? = null, + var mdsrDeathCertFile: String? = null, var dateOfDeath: Long? = System.currentTimeMillis(), var address: String? = null, var husbandName: String? = null, @@ -49,6 +53,9 @@ data class MDSRCache( return MdsrPost( id = id, benId = benId, + mdsr1File=mdsr1File, + mdsr2File=mdsr2File, + mdsrDeathCertFile=mdsrDeathCertFile, dateOfDeath = getDateTimeStringFromLong(dateOfDeath), address = address, husbandName = husbandName, @@ -73,6 +80,9 @@ data class MdsrPost( val id: Int = 0, val benId: Long, + var mdsr1File: String? = null, + var mdsr2File: String? = null, + var mdsrDeathCertFile: String? = null, val dateOfDeath: String? = null, val address: String? = null, val husbandName: String? = null, @@ -91,6 +101,9 @@ data class MdsrPost( return MDSRCache( id = id, benId = benId, + mdsr1File=mdsr1File, + mdsr2File=mdsr2File, + mdsrDeathCertFile=mdsrDeathCertFile, dateOfDeath = getLongFromDate(dateOfDeath), address = address, husbandName = husbandName, @@ -114,21 +127,21 @@ data class MdsrPost( fun getMonth(): String { val calendar = Calendar.getInstance() val date = calendar.time - val month = SimpleDateFormat("MM").format(date) // always 2 digits + val month = SimpleDateFormat("MM", Locale.ENGLISH).format(date) // always 2 digits return month } fun getYear(): String { val calendar = Calendar.getInstance() val date = calendar.time - val year = SimpleDateFormat("yyyy").format(date) // 4 digit y + val year = SimpleDateFormat("yyyy", Locale.ENGLISH).format(date) // 4 digit y return year } fun getDays(): String { val calendar = Calendar.getInstance() val date = calendar.time - val days = SimpleDateFormat("dd").format(date) // 2 digit y + val days = SimpleDateFormat("dd", Locale.ENGLISH).format(date) // 2 digit y return days } diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/MaaMeetingEntity.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/MaaMeetingEntity.kt new file mode 100644 index 000000000..a51fa4012 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/MaaMeetingEntity.kt @@ -0,0 +1,28 @@ +package org.piramalswasthya.sakhi.model + +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey +import androidx.room.TypeConverters +import org.piramalswasthya.sakhi.database.converters.StringListConverter +import org.piramalswasthya.sakhi.database.room.SyncState + +@Entity(tableName = "MAA_MEETING", indices = [Index(value = ["id"], unique = true)]) +data class MaaMeetingEntity( + @PrimaryKey(autoGenerate = true) val id: Long = 0, + val meetingDate: String?, + val place: String?, + val participants: Int?, + var mitaninActivityCheckList : String? = null, + var villageName: String? = null, + var noOfPragnentWomen: String? = null, + var noOfLactingMother: String? = null, + val ashaId: Int?, + @TypeConverters(StringListConverter::class) + val meetingImages: List? = null, + val createdAt: Long = System.currentTimeMillis(), + val updatedAt: Long = System.currentTimeMillis(), + val syncState: SyncState = SyncState.UNSYNCED +) + + diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/MaaMeetingResponseModels.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/MaaMeetingResponseModels.kt new file mode 100644 index 000000000..751ccacf0 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/MaaMeetingResponseModels.kt @@ -0,0 +1,25 @@ +package org.piramalswasthya.sakhi.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class MaaMeetingGetAllResponse( + @Json(name = "data") val data: List?, + @Json(name = "statusCode") val statusCode: Int?, + @Json(name = "status") val status: String? +) + +@JsonClass(generateAdapter = true) +data class MaaMeetingServerItem( + @Json(name = "id") val id: Int?, + @Json(name = "meetingDate") val meetingDate: String?, + @Json(name = "place") val place: String?, + @Json(name = "mitaninActivityCheckList") val mitaninActivityCheckList: String?, + @Json(name = "noOfLactingMother") val noOfLactingMother: String?, + @Json(name = "noOfPragnentWomen") val noOfPragnentWoment: String?, + @Json(name = "villageName") val villageName: String?, + @Json(name = "participants") val participants: Int?, + @Json(name = "ashaId") val ashaId: Int?, + @Json(name = "meetingImages") val meetingImages: List? +) \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/MalariaConfirmedCasesCache.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/MalariaConfirmedCasesCache.kt new file mode 100644 index 000000000..241dba75b --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/MalariaConfirmedCasesCache.kt @@ -0,0 +1,76 @@ +package org.piramalswasthya.sakhi.model + +import androidx.room.Embedded +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.Index +import androidx.room.PrimaryKey +import androidx.room.Relation +import org.piramalswasthya.sakhi.configuration.FormDataModel +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.network.MalariaConfirmedDTO + +@Entity( + tableName = "MALARIA_CONFIRMED", + foreignKeys = [ForeignKey( + entity = BenRegCache::class, + parentColumns = arrayOf("beneficiaryId"/* "householdId"*/), + childColumns = arrayOf("benId" /*"hhId"*/), + onUpdate = ForeignKey.CASCADE, + onDelete = ForeignKey.CASCADE + )], + indices = [Index(name = "ind_malariacs", value = ["benId"/* "hhId"*/])] +) +data class MalariaConfirmedCasesCache( + @PrimaryKey(autoGenerate = true) + val id: Int = 0, + val diseaseId: Int = 0, + val benId: Long, + val houseHoldDetailsId: Long, + var dateOfDiagnosis: Long = System.currentTimeMillis(), + var treatmentStartDate: Long = System.currentTimeMillis(), + var treatmentCompletionDate: Long = System.currentTimeMillis(), + var treatmentGiven: String? = null, + var referralDate: Long = System.currentTimeMillis(), + var day: String? = null, + var syncState: SyncState = SyncState.UNSYNCED, +) : FormDataModel { + fun toDTO(): MalariaConfirmedDTO { + return MalariaConfirmedDTO( + id = 0, + benId = benId, + dateOfDiagnosis = getDateTimeStringFromLong(dateOfDiagnosis).toString(), + treatmentStartDate = getDateTimeStringFromLong(treatmentStartDate).toString(), + treatmentCompletionDate = getDateTimeStringFromLong(treatmentCompletionDate).toString(), + referralDate = getDateTimeStringFromLong(referralDate).toString(), + day = day.toString(), + houseHoldDetailsId = houseHoldDetailsId, + treatmentGiven = treatmentGiven.toString(), + ) + } + +} +data class BenWithMalariaConfirmedCache( + @Embedded + val ben: BenBasicCache, + @Relation( + parentColumn = "benId", entityColumn = "benId" + ) + val malariaConfirmed: MalariaConfirmedCasesCache?, + val slideTestName: String? + + ) { + fun asMalariaConfirmedDomainModel(): BenWithMalariaConfirmedDomain { + return BenWithMalariaConfirmedDomain( + ben = ben.asBasicDomainModel(), + malariaConfirmed = malariaConfirmed, + slideTestName = slideTestName + ) + } +} + +data class BenWithMalariaConfirmedDomain( + val ben: BenBasicDomain, + val malariaConfirmed: MalariaConfirmedCasesCache?, + val slideTestName: String? +) \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/MalariaScreening.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/MalariaScreening.kt new file mode 100644 index 000000000..351a7990d --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/MalariaScreening.kt @@ -0,0 +1,134 @@ +package org.piramalswasthya.sakhi.model + +import androidx.room.Embedded +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.Index +import androidx.room.PrimaryKey +import androidx.room.Relation +import org.piramalswasthya.sakhi.configuration.FormDataModel +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.network.MalariaScreeningDTO + +@Entity( + tableName = "MALARIA_SCREENING", + foreignKeys = [ForeignKey( + entity = BenRegCache::class, + parentColumns = arrayOf("beneficiaryId"/* "householdId"*/), + childColumns = arrayOf("benId" /*"hhId"*/), + onUpdate = ForeignKey.CASCADE, + onDelete = ForeignKey.CASCADE + )], + indices = [Index(name = "ind_malariasn", value = ["benId" , "visitId"] , unique = true)] +) +data class MalariaScreeningCache( + @PrimaryKey(autoGenerate = true) + val id: Int = 0, + val benId: Long, + var visitId: Long, + var caseDate: Long = System.currentTimeMillis(), + val houseHoldDetailsId: Long, + val screeningDate: Long = System.currentTimeMillis(), + var beneficiaryStatus: String ? = null, + var beneficiaryStatusId: Int = 0, + var dateOfDeath: Long = System.currentTimeMillis(), + var placeOfDeath: String ? = null, + var otherPlaceOfDeath: String ? = null, + var reasonForDeath: String ? = null, + var otherReasonForDeath: String ? = null, + var rapidDiagnosticTest: String ? = null, + var dateOfRdt: Long = System.currentTimeMillis(), + var slideTestPf: String ? = null, + var slideTestPv: String ? = null, + var slideTestName: String ? = null, + var dateOfSlideTest: Long = System.currentTimeMillis(), + var dateOfVisitBySupervisor: Long = System.currentTimeMillis(), + var caseStatus: String ? = "", + var referredTo: Int ? = 0, + var referToName: String ? = null, + var otherReferredFacility: String ? = null, + var remarks: String ? = null, + var diseaseTypeID: Int ? = 0, + var followUpDate: Long = System.currentTimeMillis(), + var feverMoreThanTwoWeeks: Boolean ? = false, + var fluLikeIllness: Boolean ? = false, + var shakingChills: Boolean ? = false, + var headache: Boolean ? = false, + var muscleAches: Boolean ? = false, + var tiredness: Boolean ? = false, + var nausea: Boolean ? = false, + var vomiting: Boolean ? = false, + var diarrhea: Boolean ? = false, + var createdBy: String ? = null, + var malariaTestType: Int? = 0, + var malariaSlideTestType: Int? = 0, + var syncState: SyncState = SyncState.UNSYNCED, +): FormDataModel { + fun toDTO(): MalariaScreeningDTO { + return MalariaScreeningDTO( + id = 0, + benId = benId, + visitId = visitId, + malariaTestType = malariaTestType, + malariaSlideTestType = malariaSlideTestType, + caseDate = getDateTimeStringFromLong(caseDate).toString(), + houseHoldDetailsId = houseHoldDetailsId, + caseStatus = caseStatus.toString(), + referredTo = referredTo!!, + otherReferredFacility = otherReferredFacility.toString(), + referToName = referToName.toString(), + remarks = remarks.toString(), + followUpDate = getDateTimeStringFromLong(followUpDate).toString(), + diseaseTypeID = diseaseTypeID!!, + feverMoreThanTwoWeeks = feverMoreThanTwoWeeks, + fluLikeIllness = fluLikeIllness, + shakingChills = shakingChills, + headache = headache, + muscleAches = muscleAches, + tiredness = tiredness, + nausea = nausea, + vomiting = vomiting, + diarrhea = diarrhea, + beneficiaryStatusId = beneficiaryStatusId, + beneficiaryStatus = beneficiaryStatus.toString(), + createdBy = createdBy, + screeningDate = getDateTimeStringFromLong(screeningDate).toString(), + rapidDiagnosticTest = rapidDiagnosticTest.toString(), + slideTestName = slideTestName.toString(), + slideTestPf = slideTestPf.toString(), + slideTestPv = slideTestPv.toString(), + dateOfSlideTest = getDateTimeStringFromLong(dateOfSlideTest).toString(), + dateOfRdt = getDateTimeStringFromLong(dateOfRdt).toString(), + dateOfDeath = getDateTimeStringFromLong(dateOfDeath).toString(), + dateOfVisitBySupervisor = getDateTimeStringFromLong(dateOfVisitBySupervisor).toString(), + reasonForDeath = reasonForDeath.toString(), + otherReasonForDeath = otherReasonForDeath.toString(), + otherPlaceOfDeath = otherPlaceOfDeath.toString(), + placeOfDeath = placeOfDeath.toString() + + + ) + } +} + +data class BenWithMalariaScreeningCache( + @Embedded + val ben: BenBasicCache, + @Relation( + parentColumn = "benId", entityColumn = "benId" + ) + val malaria: MalariaScreeningCache?, + + ) { + fun asMalariaScreeningDomainModel(): BenWithMalariaScreeningDomain { + return BenWithMalariaScreeningDomain( + ben = ben.asBasicDomainModel(), + tb = malaria + ) + } +} + +data class BenWithMalariaScreeningDomain( + val ben: BenBasicDomain, + val tb: MalariaScreeningCache? +) diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/MaternalHealth.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/MaternalHealth.kt index e74431976..ba8551dcf 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/model/MaternalHealth.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/MaternalHealth.kt @@ -1,5 +1,7 @@ package org.piramalswasthya.sakhi.model +import android.util.Log +import android.content.Context import androidx.room.Embedded import androidx.room.Entity import androidx.room.ForeignKey @@ -13,8 +15,10 @@ import org.piramalswasthya.sakhi.helpers.getDateString import org.piramalswasthya.sakhi.helpers.getTodayMillis import org.piramalswasthya.sakhi.helpers.getWeeksOfPregnancy import org.piramalswasthya.sakhi.network.getLongFromDate +import org.piramalswasthya.sakhi.network.getLongFromDateMultipleSupport import org.piramalswasthya.sakhi.utils.HelperUtil.getDateStringFromLong import java.text.SimpleDateFormat +import java.util.Calendar import java.util.Date import java.util.Locale import java.util.concurrent.TimeUnit @@ -62,6 +66,16 @@ data class PregnantWomenVisitDomain( ) data class AncStatus( + val benId: Long, + val visitNumber: Int, + val filledWeek: Int, + val syncState: SyncState? = null, + val anyHighRisk: Boolean?, + val placeOfAncId: Int? +) + + +data class PMSMAStatus( val benId: Long, val visitNumber: Int, val filledWeek: Int, @@ -192,7 +206,7 @@ data class BenWithPwrCache( benId = ben.benId, hhId = ben.hhId, regDate = BenBasicCache.dateFormat.format(Date(ben.regDate)), - benName = ben.benName, + benName = ben.benName.orEmpty(), benSurname = ben.benSurname ?: "", gender = ben.gender.name, dob = ben.dob, @@ -208,7 +222,8 @@ data class BenWithPwrCache( form1Filled = ben.hrppaFilled, syncState = ben.hrppaSyncState, form2Enabled = true, - form2Filled = ben.hrpmbpFilled + form2Filled = ben.hrpmbpFilled, + isConsent = false ) } @@ -319,7 +334,34 @@ data class PregnantWomanAncCache( val benId: Long, var visitNumber: Int, var isActive: Boolean = true, - var ancDate: Long = System.currentTimeMillis(), + var ancDate: Long = 0L, + + + var lmpDate: Long? = null, + var visitDate: Long? = null, + var weekOfPregnancy: Int? = null, + + var serialNo: String? = null, + var methodOfTermination: String? = null, + var methodOfTerminationId: Int? = 0, + + var terminationDoneBy: String? = null, + var terminationDoneById: Int? = 0, + + var placeOfAnc: String? = null, + var placeOfAncId: Int? = 0, + + var isPaiucdId: Int? = 0, + var isYesOrNo: Boolean? = false, + var isPaiucd: String? = null, + var dateSterilisation: Long? = null, + var remarks: String? = null, + var abortionImg1: String? = null, + var abortionImg2: String? = null, + var placeOfDeath: String? = null, + var placeOfDeathId: Int? = 0, + var otherPlaceOfDeath: String? = null, + var isAborted: Boolean = false, var abortionType: String? = null, var abortionTypeId: Int = 0, @@ -358,13 +400,36 @@ data class PregnantWomanAncCache( val createdDate: Long = System.currentTimeMillis(), var updatedBy: String, var updatedDate: Long = System.currentTimeMillis(), - var syncState: SyncState + var syncState: SyncState, + var frontFilePath : String?, + var backFilePath : String? ) : FormDataModel { fun asPostModel(): ANCPost { return ANCPost( benId = benId, ancDate = getDateStringFromLong(ancDate), isActive = true, + lmpDate = lmpDate?.let { getDateStringFromLong(it) }, + visitDate = visitDate?.let { getDateStringFromLong(it) }, + weekOfPregnancy = weekOfPregnancy, + serialNo = serialNo, + methodOfTermination = methodOfTermination, + methodOfTerminationId = methodOfTerminationId, + terminationDoneBy = terminationDoneBy, + terminationDoneById = terminationDoneById, + placeOfAnc=placeOfAnc, + placeOfAncId=placeOfAncId, + isPaiucdId = isPaiucdId, + isPaiucd = isPaiucd, + isYesOrNo = isYesOrNo, + dateSterilisation = dateSterilisation?.let { getDateStringFromLong(it) }, + remarks = remarks, + abortionImg1 = abortionImg1, + abortionImg2 = abortionImg2, + placeOfDeath = placeOfDeath, + placeOfDeathId = placeOfDeathId, + otherPlaceOfDeath = otherPlaceOfDeath, + ancVisit = visitNumber, isAborted = isAborted, abortionType = abortionType, @@ -394,7 +459,10 @@ data class PregnantWomanAncCache( createdDate = getDateStringFromLong(createdDate), createdBy = createdBy, updatedDate = getDateStringFromLong(updatedDate), - updatedBy = updatedBy + updatedBy = updatedBy, + frontFilePath = frontFilePath, + backFilePath = backFilePath + ) } } @@ -404,6 +472,29 @@ data class ANCPost( val benId: Long = 0, val ancDate: String? = null, val isActive: Boolean, + + var lmpDate: String? = null, + var visitDate: String? = null, + var weekOfPregnancy: Int? = null, + var serialNo: String? = null, + var methodOfTermination: String? = null, + var methodOfTerminationId: Int? = 0, + var terminationDoneBy: String? = null, + var terminationDoneById: Int? = 0, + var placeOfAnc: String? = null, + var placeOfAncId: Int? = 0, + var isPaiucdId: Int? = 0, + var isYesOrNo: Boolean? = false, + var isPaiucd: String? = null, + var dateSterilisation: String? = null, + var remarks: String? = null, + var abortionImg1: String? = null, + var abortionImg2: String? = null, + var placeOfDeath: String? = null, + var placeOfDeathId: Int? = 0, + var otherPlaceOfDeath: String? = null, + + val ancVisit: Int, val isAborted: Boolean = false, val abortionType: String? = null, @@ -434,12 +525,39 @@ data class ANCPost( val createdDate: String? = null, val createdBy: String, val updatedDate: String? = null, - val updatedBy: String + val updatedBy: String, + var providerServiceMapID: String? = null, + var frontFilePath : String?, + var backFilePath : String? ) { - fun toAncCache(): PregnantWomanAncCache { + fun toAncCache(context : Context): PregnantWomanAncCache { return PregnantWomanAncCache( id = id, benId = benId, + + lmpDate = getLongFromDateMultipleSupport(lmpDate), + visitDate = getLongFromDateMultipleSupport(visitDate), + weekOfPregnancy = weekOfPregnancy, + serialNo = serialNo, + methodOfTermination = methodOfTermination, + methodOfTerminationId = methodOfTerminationId, + terminationDoneBy = terminationDoneBy, + terminationDoneById = terminationDoneById, + isPaiucdId = isPaiucdId, + isPaiucd = isPaiucd, + isYesOrNo = isYesOrNo, + dateSterilisation = getLongFromDateMultipleSupport(dateSterilisation), + remarks = remarks, + abortionImg1 = abortionImg1, + abortionImg2 = abortionImg2, + placeOfDeath = placeOfDeath, + placeOfDeathId = placeOfDeathId, + otherPlaceOfDeath = otherPlaceOfDeath, + placeOfAnc = placeOfAnc, + placeOfAncId = placeOfAncId, + + + visitNumber = ancVisit, ancDate = getLongFromDate(ancDate), isAborted = isAborted, @@ -447,7 +565,7 @@ data class ANCPost( // abortionTypeId = abortionFacility = abortionFacility, // abortionFacilityId - abortionDate = getLongFromDate(abortionDate), + abortionDate = getLongFromDateMultipleSupport(abortionDate), weight = weightOfPW, bpSystolic = bpSystolic, bpDiastolic = bpDiastolic, @@ -473,14 +591,16 @@ data class ANCPost( maternalDeathProbableCause = probableCauseOfDeath, // maternalDeathProbableCauseId otherMaternalDeathProbableCause = otherCauseOfDeath, - deathDate = getLongFromDate(deathDate), + deathDate = getLongFromDateMultipleSupport(deathDate), pregnantWomanDelivered = isBabyDelivered, processed = "P", createdBy = createdBy, createdDate = getLongFromDate(createdDate), updatedBy = updatedBy, updatedDate = getLongFromDate(updatedDate), - syncState = SyncState.SYNCED + syncState = SyncState.SYNCED, + frontFilePath = frontFilePath, + backFilePath = backFilePath ) } } @@ -502,68 +622,173 @@ data class BenWithAncVisitCache( ) val pmsma: List, + @Relation( + parentColumn = "benId", + entityColumn = "benId", + ) + val savedPmsmaRecords: List, + @Relation( parentColumn = "benId", entityColumn = "benId", entity = PregnantWomanAncCache::class ) val savedAncRecords: List ) { - companion object { - private val dateFormat = SimpleDateFormat("EEE, MMM dd yyyy", Locale.getDefault()) + private val dateFormat = SimpleDateFormat("EEE, MMM dd yyyy", Locale.ENGLISH) - private fun getAncVisitedDateFromLong(long: Long): String { - return "Visited on ${dateFormat.format(long)}" + private fun getAncVisitedDateFromLong(long: Long, resources: android.content.res.Resources): String { + val visitedOn = resources.getString(org.piramalswasthya.sakhi.R.string.track_visited_on) + return "$visitedOn ${dateFormat.format(long)}" } } fun asDomainModel(): BenWithAncListDomain { val lastAncRecord = savedAncRecords.maxByOrNull { it.ancDate } val activePmsma = pmsma.firstOrNull { it.isActive } - val activePwrRecrod = pwr.first { it.active } + val activePwrRecord = pwr.firstOrNull { it.active } + val abortionRecord = savedAncRecords.firstOrNull { it.isAborted } + val lmpDateToUse = activePwrRecord?.lmpDate + ?: abortionRecord?.lmpDate + ?: 0L + + val eddDateToUse = if (lmpDateToUse != 0L) { + lmpDateToUse + TimeUnit.DAYS.toMillis(280) + } else 0L + + val weekOfPregnancyToUse = if (lmpDateToUse != 0L) { + (TimeUnit.MILLISECONDS.toDays(getTodayMillis() - lmpDateToUse) / 7).toInt() + } else 0 return BenWithAncListDomain( -// ecBenId, - ben.asBasicDomainModel(), - activePwrRecrod, - savedAncRecords.filter { it.isActive }.map { + ben = ben.asBasicDomainModel(), + pwr = activePwrRecord, + anc = savedAncRecords.filter { it.isActive }.map { AncStatus( benId = it.benId, visitNumber = it.visitNumber, - filledWeek = (TimeUnit.MILLISECONDS.toDays(it.ancDate - activePwrRecrod.lmpDate) / 7).toInt(), - syncState = it.syncState + filledWeek = if (lmpDateToUse != 0L) + (TimeUnit.MILLISECONDS.toDays(it.ancDate - lmpDateToUse) / 7).toInt() + else 0, + syncState = it.syncState, + anyHighRisk = it.anyHighRisk, + placeOfAncId = it.placeOfAncId, ) }.sortedBy { it.visitNumber }, - pmsmaFillable = if (activePmsma == null) savedAncRecords.any { it.visitNumber == 1 } else true, + ancDate = lastAncRecord?.ancDate ?: 0L, + pmsma = if (savedPmsmaRecords.filter { it.isActive }.isEmpty()) { + listOf( + PMSMAStatus( + benId = ben.benId, + visitNumber = 0, + filledWeek = if (activePmsma == null && savedAncRecords.any { it.visitNumber == 1 }) { + 1 + } else { + 0 + }, + syncState = SyncState.UNSYNCED + ) + ) + } else { + savedPmsmaRecords.filter { it.isActive }.map { + val lastVisitRecord = savedPmsmaRecords.filter { it.isActive } + .maxByOrNull { it.visitNumber } + + var eligibilityFilledWeek = 1 + + if (lastVisitRecord != null) { + val today = Calendar.getInstance().timeInMillis + val gapDays = lastVisitRecord.visitDate?.let { visitDate -> + TimeUnit.MILLISECONDS.toDays(today - visitDate) + } ?: 0 + eligibilityFilledWeek = if (gapDays >= 7) 1 else 0 + } + + PMSMAStatus( + benId = it.benId, + visitNumber = it.visitNumber, + filledWeek = eligibilityFilledWeek, + syncState = it.syncState + ) + }.sortedBy { it.visitNumber } + } + , + savedAncRecords = savedAncRecords, + pmsmaFillable = if (activePmsma == null) { + savedAncRecords.any { it.visitNumber >0 } + } else true, + hasPmsma = activePmsma != null, - showAddAnc = if (savedAncRecords.isEmpty()) - TimeUnit.MILLISECONDS.toDays( - getTodayMillis() - activePwrRecrod.lmpDate - ) >= Konstants.minAnc1Week * 7 - else + showAddAnc = if (activePwrRecord == null) { + false + } else if (savedAncRecords.isEmpty()) { + TimeUnit.MILLISECONDS.toDays(getTodayMillis() - lmpDateToUse) >= Konstants.minAnc1Week * 7 + } else { lastAncRecord != null && - (activePwrRecrod.lmpDate + TimeUnit.DAYS.toMillis(280)) > (lastAncRecord.ancDate + TimeUnit.DAYS.toMillis( - 28 - )) && - lastAncRecord.visitNumber < 4 && TimeUnit.MILLISECONDS.toDays( - getTodayMillis() - lastAncRecord.ancDate - ) > 28, - syncState = if (activePmsma == null && savedAncRecords.isEmpty()) null else if (activePmsma?.syncState == SyncState.UNSYNCED || savedAncRecords.any { it.syncState != SyncState.SYNCED }) SyncState.UNSYNCED else SyncState.SYNCED - ) - - + (lmpDateToUse + TimeUnit.DAYS.toMillis(840)) > + (lastAncRecord.ancDate + TimeUnit.DAYS.toMillis(28)) && + lastAncRecord.visitNumber < 8 && + lastAncRecord.ancDate + TimeUnit.DAYS.toMillis(28) < getTodayMillis() + }, + syncState = if (activePmsma == null && savedAncRecords.isEmpty()) { + null + } else if (activePmsma?.syncState == SyncState.UNSYNCED || + savedAncRecords.any { it.syncState != SyncState.SYNCED } + ) { + SyncState.UNSYNCED + } else { + SyncState.SYNCED + } + ).apply { + if (activePwrRecord == null && abortionRecord != null) { + lmpDate = lmpDateToUse + eddDate = eddDateToUse + weekOfPregnancy = weekOfPregnancyToUse + abortionDate = abortionRecord.ancDate + } + } } + } data class BenWithAncListDomain( val ben: BenBasicDomain, - val pwr: PregnantWomanRegistrationCache, + val pwr: PregnantWomanRegistrationCache?, val anc: List, - val lmpString: String? = getDateString(pwr.lmpDate), - val eddString: String? = getDateString(pwr.lmpDate + TimeUnit.DAYS.toMillis(280)), - val weeksOfPregnancy: String? = (TimeUnit.MILLISECONDS.toDays(getTodayMillis() - pwr.lmpDate) / 7).takeIf { it <= 40 } - ?.toString() ?: "NA", + val ancDate: Long = 0L, + val pmsma: List, + val savedAncRecords: List, + + + var lmpDate: Long? = null, + var eddDate: Long? = null, + var weekOfPregnancy: Int? = null, + var abortionDate: Long? = null, + + val showAddHomeVisit: Boolean = false, + val showViewHomeVisit: Boolean = false, val showAddAnc: Boolean, val pmsmaFillable: Boolean, val hasPmsma: Boolean, val showViewAnc: Boolean = anc.isEmpty(), val syncState: SyncState? -) \ No newline at end of file +) { + val finalLmpDate: Long? get() = pwr?.lmpDate ?: lmpDate + val finalEddDate: Long? get() = + pwr?.let { it.lmpDate + TimeUnit.DAYS.toMillis(280) } ?: eddDate + + val finalWeeksOfPregnancy: Int? + get() = finalLmpDate?.let { + val weeks = (TimeUnit.MILLISECONDS.toDays(getTodayMillis() - it) / 7).toInt() + if (weeks > 40) null else weeks + } ?: weekOfPregnancy?.takeIf { it <= 40 } + val lmpString: String? get() = finalLmpDate?.let { getDateString(it) } + val eddString: String? get() = finalEddDate?.let { getDateString(it) } + val weeksOfPregnancy: String + get() = finalWeeksOfPregnancy?.toString() ?: "N/A" + val abortionDateString: String? get() = abortionDate?.let { getDateString(it) } + + val isAbortionFormFilled: Boolean + get() = savedAncRecords.firstOrNull { it.isAborted }?.terminationDoneBy != null +} + + + diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/ORSCampaignCache.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/ORSCampaignCache.kt new file mode 100644 index 000000000..3363933fa --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/ORSCampaignCache.kt @@ -0,0 +1,27 @@ +package org.piramalswasthya.sakhi.model + +import androidx.room.Entity +import androidx.room.PrimaryKey +import org.json.JSONObject +import org.piramalswasthya.sakhi.configuration.FormDataModel +import org.piramalswasthya.sakhi.database.room.SyncState + +@Entity(tableName = "ORSCampaign") +data class ORSCampaignCache( + @PrimaryKey(autoGenerate = true) + var id: Int = 0, + var formDataJson: String? = null, + var syncState: SyncState = SyncState.UNSYNCED +) : FormDataModel { + val campaignDate: String? + get() { + return try { + val json = formDataJson ?: return null + val jsonObj = JSONObject(json) + val fieldsObj = jsonObj.optJSONObject("fields") ?: return null + fieldsObj.optString("start_date").takeIf { it.isNotEmpty() } + } catch (e: Exception) { + null + } + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/PHCReviewMeetingCache.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/PHCReviewMeetingCache.kt new file mode 100644 index 000000000..1c9cbed97 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/PHCReviewMeetingCache.kt @@ -0,0 +1,79 @@ +package org.piramalswasthya.sakhi.model + +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey +import org.piramalswasthya.sakhi.configuration.FormDataModel +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.network.PHCReviewDTO +import java.util.Date + +@Entity(tableName = "PHCReviewMeeting", indices = [Index(value = ["id"], unique = true)] +) + +data class PHCReviewMeetingCache( + @PrimaryKey(autoGenerate = true) + var id: Int , + var placeId : Int? = 0 , + var phcReviewDate: String, + var place: String? = null, + var villageName: String? = null, + var mitaninHistory: String? = null, + var mitaninActivityCheckList: String? = null, + var noOfBeneficiariesAttended: Int? = null, + var image1: String? = null, + var image2: String? = null, + var syncState: SyncState = SyncState.UNSYNCED +) : FormDataModel { + fun toDTO(): PHCReviewDTO { + return PHCReviewDTO( + id = id, + phcReviewDate = (phcReviewDate), + place = place, + noOfBeneficiariesAttended = noOfBeneficiariesAttended, + Image1 = image1, + Image2 = image2, + villageName = villageName, + mitaninHistory = mitaninHistory, + mitaninActivityCheckList = mitaninActivityCheckList, + placeId = placeId + ) + } + + fun toPHCDTODTO(): PHCReviewMeetingCache { + return PHCReviewMeetingCache( + id = id, + place = place, + noOfBeneficiariesAttended = noOfBeneficiariesAttended, + image2 = image1, + image1 = image2, + phcReviewDate = (phcReviewDate), + villageName = villageName, + mitaninHistory = mitaninHistory, + mitaninActivityCheckList = mitaninActivityCheckList, + placeId = placeId + ) + } +} + + +//data class BenWithHRNPACache( +// @Embedded +// val ben: BenBasicCache, +// @Relation( +// parentColumn = "benId", entityColumn = "benId" +// ) +// val assess: HRPNonPregnantAssessCache?, +// +// ) { +// fun asDomainModel(): BenWithHRNPADomain { +// return BenWithHRNPADomain( +// ben = ben.asBasicDomainModel(), +// assess = assess +// ) +// } +//} +// +//data class VHNDDomain( +// val vhndChache: VHNDCache? +//) diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/PMSMA.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/PMSMA.kt index f3d1bb4c5..3e7362c00 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/model/PMSMA.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/PMSMA.kt @@ -8,6 +8,8 @@ import com.squareup.moshi.JsonClass import org.piramalswasthya.sakhi.configuration.FormDataModel import org.piramalswasthya.sakhi.database.room.SyncState import org.piramalswasthya.sakhi.network.getLongFromDate +import org.piramalswasthya.sakhi.utils.HelperUtil +import org.piramalswasthya.sakhi.utils.HelperUtil.getDateStringFromLong import java.text.SimpleDateFormat import java.util.Locale @@ -29,6 +31,11 @@ data class PMSMACache( val id: Long = 0, val benId: Long, // val hhId: Long, + + var visitDate: Long? = null, + var visitNumber: Int, + var anyOtherHighRiskCondition: String? = null, + var isActive: Boolean, var mctsNumberOrRchNumber: String? = null, var haveMCPCard: Boolean = false, @@ -89,6 +96,9 @@ data class PMSMACache( id = id, benId = benId, isActive = isActive, + visitDate = visitDate?.let { HelperUtil.getDateStringFromLong(it) }, + visitNumber = visitNumber, + anyOtherHighRiskCondition = anyOtherHighRiskCondition, rchNumber = mctsNumberOrRchNumber, haveMCPCard = haveMCPCard, givenMCPCard = givenMCPCard, @@ -136,6 +146,9 @@ data class PmsmaPost( val id: Long = 0, val benId: Long = 0, + var visitDate: String? = null, + var visitNumber: Int, + var anyOtherHighRiskCondition: String? = null, val isActive: Boolean, val rchNumber: String? = null, val haveMCPCard: Boolean = false, @@ -181,6 +194,9 @@ data class PmsmaPost( id = id, benId = benId, isActive = isActive, + visitDate = getLongFromDate(visitDate), + visitNumber = visitNumber, + anyOtherHighRiskCondition = anyOtherHighRiskCondition, // hhId mctsNumberOrRchNumber = rchNumber, haveMCPCard = haveMCPCard, diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/PNC.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/PNC.kt index 35d9618a9..94376af71 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/model/PNC.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/PNC.kt @@ -3,6 +3,7 @@ package org.piramalswasthya.sakhi.model import androidx.room.Embedded import androidx.room.Entity import androidx.room.ForeignKey +import androidx.room.Ignore import androidx.room.Index import androidx.room.PrimaryKey import androidx.room.Relation @@ -11,6 +12,7 @@ import org.piramalswasthya.sakhi.configuration.FormDataModel import org.piramalswasthya.sakhi.database.room.SyncState import org.piramalswasthya.sakhi.helpers.setToStartOfTheDay import org.piramalswasthya.sakhi.network.getLongFromDate +import timber.log.Timber import java.util.Calendar import java.util.concurrent.TimeUnit @@ -31,11 +33,13 @@ data class PNCVisitCache( val benId: Long, var pncPeriod: Int, var isActive: Boolean, - var pncDate: Long = System.currentTimeMillis(), + var pncDate: Long = 0L, var ifaTabsGiven: Int? = 0, var anyContraceptionMethod: Boolean? = null, var contraceptionMethod: String? = null, + var sterilisationDate: Long? = System.currentTimeMillis(), var otherPpcMethod: String? = null, + var anyDangerSign : String? = null, var motherDangerSign: String? = null, var otherDangerSign: String? = null, var referralFacility: String? = null, @@ -44,14 +48,23 @@ data class PNCVisitCache( var causeOfDeath: String? = null, var otherDeathCause: String? = null, var placeOfDeath: String? = null, + var otherPlaceOfDeath: String? = null, var remarks: String? = null, + var deliveryDischargeSummary1 : String? = null, + var deliveryDischargeSummary2 : String? = null, + var deliveryDischargeSummary3 : String? = null, + var deliveryDischargeSummary4 : String? = null, var processed: String? = "N", var createdBy: String, val createdDate: Long = System.currentTimeMillis(), var updatedBy: String, var updatedDate: Long = System.currentTimeMillis(), - var syncState: SyncState + var syncState: SyncState, ) : FormDataModel { + + @Ignore + var dateOfDelivery: Long = 0L + fun asDomainModel(): PncDomain = PncDomain( benId = benId, visitNumber = pncPeriod, @@ -64,12 +77,15 @@ data class PNCVisitCache( id = id, benId = benId, pncPeriod = pncPeriod, + otherPlaceOfDeath = otherPlaceOfDeath, isActive = isActive, pncDate = getDateTimeStringFromLong(pncDate)!!, ifaTabsGiven = ifaTabsGiven, anyContraceptionMethod = anyContraceptionMethod, contraceptionMethod = contraceptionMethod, + sterilisationDate = getDateTimeStringFromLong(sterilisationDate)!!, otherPpcMethod = otherPpcMethod, + anyDangerSign = anyDangerSign, motherDangerSign = motherDangerSign, otherDangerSign = otherDangerSign, referralFacility = referralFacility, @@ -97,7 +113,9 @@ data class PNCNetwork( var ifaTabsGiven: Int?, var anyContraceptionMethod: Boolean?, var contraceptionMethod: String?, + var sterilisationDate : String, var otherPpcMethod: String?, + var anyDangerSign: String?, var motherDangerSign: String?, var otherDangerSign: String?, var referralFacility: String?, @@ -106,7 +124,12 @@ data class PNCNetwork( var causeOfDeath: String?, var otherDeathCause: String?, var placeOfDeath: String?, + var otherPlaceOfDeath: String?, var remarks: String?, + var deliveryDischargeSummary1 : String? = null, + var deliveryDischargeSummary2 : String? = null, + var deliveryDischargeSummary3 : String? = null, + var deliveryDischargeSummary4 : String? = null, var createdBy: String, val createdDate: String, var updatedBy: String, @@ -118,10 +141,14 @@ data class PNCNetwork( pncPeriod = pncPeriod, isActive = isActive, pncDate = getLongFromDate(pncDate), + ifaTabsGiven = ifaTabsGiven, + otherPlaceOfDeath = otherPlaceOfDeath, anyContraceptionMethod = anyContraceptionMethod, contraceptionMethod = contraceptionMethod, + sterilisationDate = getLongFromDate(sterilisationDate), otherPpcMethod = otherPpcMethod, + anyDangerSign = anyDangerSign, motherDangerSign = motherDangerSign, otherDangerSign = otherDangerSign, referralFacility = referralFacility, @@ -131,6 +158,10 @@ data class PNCNetwork( otherDeathCause = otherDeathCause, placeOfDeath = placeOfDeath, remarks = remarks, + deliveryDischargeSummary1 = deliveryDischargeSummary1, + deliveryDischargeSummary2 = deliveryDischargeSummary2, + deliveryDischargeSummary3 = deliveryDischargeSummary3, + deliveryDischargeSummary4 = deliveryDischargeSummary4, processed = "P", createdBy = createdBy, createdDate = getLongFromDate(createdDate), @@ -158,12 +189,14 @@ data class BenWithDoAndPncCache( val savedPncRecords: List ) { fun asBasicDomainModelForPNC(): BenPncDomain { - val activeDo = deliveryOutcomeCache.first { it.isActive } + val activeDo = deliveryOutcomeCache.firstOrNull() { it.isActive } val latestPnc = savedPncRecords.maxByOrNull { it.pncPeriod } val daysSinceDeliveryMillis = Calendar.getInstance() - .setToStartOfTheDay().timeInMillis - activeDo.dateOfDelivery!!.let { + .setToStartOfTheDay().timeInMillis - activeDo?.dateOfDelivery.let { val cal = Calendar.getInstance() - cal.timeInMillis = it + if (it != null) { + cal.timeInMillis = it + } cal.setToStartOfTheDay() cal.timeInMillis } @@ -181,12 +214,13 @@ data class BenWithDoAndPncCache( .filter { it > (latestPnc?.pncPeriod ?: 0) } return BenPncDomain( ben.asBasicDomainModel(), - activeDo.dateOfDelivery?.let { + activeDo?.dateOfDelivery?.let { getDateStrFromLong( it ) } ?: "", availFillDates.isNotEmpty(), + pncDate = (latestPnc?.pncDate ?: 0L), savedPncRecords ) } @@ -198,6 +232,7 @@ data class BenPncDomain( val ben: BenBasicDomain, val deliveryDate: String, val allowFill: Boolean, + val pncDate: Long = 0L, val savedPncRecords: List, val syncState: SyncState? = savedPncRecords.takeIf { it.isNotEmpty() }?.map { it.syncState } ?.let { syncStates -> diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/PreviewItem.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/PreviewItem.kt new file mode 100644 index 000000000..177bab7bf --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/PreviewItem.kt @@ -0,0 +1,11 @@ +package org.piramalswasthya.sakhi.model + +import android.net.Uri + +data class PreviewItem( + val label: String, + val value: String, + val isImage: Boolean = false, + val imageUri: Uri? = null +) + diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/PulsePolioCampaignCache.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/PulsePolioCampaignCache.kt new file mode 100644 index 000000000..7412078d9 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/PulsePolioCampaignCache.kt @@ -0,0 +1,27 @@ +package org.piramalswasthya.sakhi.model + +import androidx.room.Entity +import androidx.room.PrimaryKey +import org.json.JSONObject +import org.piramalswasthya.sakhi.configuration.FormDataModel +import org.piramalswasthya.sakhi.database.room.SyncState + +@Entity(tableName = "PulsePolioCampaign") +data class PulsePolioCampaignCache( + @PrimaryKey(autoGenerate = true) + var id: Int = 0, + var formDataJson: String? = null, + var syncState: SyncState = SyncState.UNSYNCED +) : FormDataModel { + val campaignDate: String? + get() { + return try { + val json = formDataJson ?: return null + val jsonObj = JSONObject(json) + val fieldsObj = jsonObj.optJSONObject("fields") ?: return null + fieldsObj.optString("start_date").takeIf { it.isNotEmpty() } + } catch (e: Exception) { + null + } + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/ReferalCache.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/ReferalCache.kt new file mode 100644 index 000000000..532a20598 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/ReferalCache.kt @@ -0,0 +1,106 @@ +package org.piramalswasthya.sakhi.model + +import androidx.room.Embedded +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.Index +import androidx.room.PrimaryKey +import androidx.room.Relation +import androidx.room.TypeConverter +import org.piramalswasthya.sakhi.configuration.FormDataModel +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.network.NCDReferalDTO + +@Entity( + tableName = "NCD_REFER", + foreignKeys = [ForeignKey( + entity = BenRegCache::class, + parentColumns = arrayOf("beneficiaryId"/* "householdId"*/), + childColumns = arrayOf("benId" /*"hhId"*/), + onUpdate = ForeignKey.CASCADE, + onDelete = ForeignKey.CASCADE + )], + indices = [Index(name = "ind_refcache", value = ["benId"/* "hhId"*/, "referralReason",], unique = true)] +) +data class ReferalCache( + @PrimaryKey(autoGenerate = true) + val id: Int = 0, + var benId: Long, + var referredToInstituteID: Int? = 0, + var refrredToAdditionalServiceList: List? = null, + var referredToInstituteName: String? = null, + var referralReason: String? = null, + var revisitDate: Long = System.currentTimeMillis(), + var vanID: Int? = 0, + var parkingPlaceID: Int? = 0, + var beneficiaryRegID: Long? = 0L, + var benVisitID: Long? = 0L, + var visitCode: Long? = 0L, + var providerServiceMapID: Int? = 0, + var createdBy: String? = "", + var type: String? = null, + var isSpecialist: Boolean? = false, + var syncState: SyncState +) : FormDataModel { + fun toDTO(): NCDReferalDTO { + return NCDReferalDTO( + id = 0, + benId = benId, + revisitDate = getDateTimeStringFromLong(revisitDate).toString(), + referralReason = referralReason, + referredToInstituteID = referredToInstituteID, + referredToInstituteName = referredToInstituteName, + vanID = vanID, + parkingPlaceID = parkingPlaceID, + refrredToAdditionalServiceList = refrredToAdditionalServiceList, + isSpecialist = isSpecialist, + syncState = SyncState.SYNCED, + providerServiceMapID = providerServiceMapID, + createdBy = createdBy, + benVisitID = benVisitID, + visitCode = visitCode, + beneficiaryRegID = benId, + type = type + + + ) + } +} + +data class BenWithNCDReferalCache( + @Embedded + val ben: BenBasicCache, + @Relation( + parentColumn = "benId", entityColumn = "benId" + ) + val refcache: ReferalCache?, + + ) { + fun ncdreferalDomainModel(): BenWithNCDReferalDomain { + return BenWithNCDReferalDomain( + ben = ben.asBasicDomainModel(), + refcache = refcache + ) + } +} + +data class BenWithNCDReferalDomain( + val ben: BenBasicDomain, + val refcache: ReferalCache? +) + +data class ReferralRequest( + val refer: NCDReferalDTO, +) + +class Converters { + @TypeConverter + fun fromStringList(value: List?): String? { + return value?.joinToString(separator = ",") + } + + @TypeConverter + fun toStringList(value: String?): List? { + return value?.split(",")?.map { it.trim() } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/SaasBahuResponseModel.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/SaasBahuResponseModel.kt new file mode 100644 index 000000000..5b7810ca7 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/SaasBahuResponseModel.kt @@ -0,0 +1,21 @@ +package org.piramalswasthya.sakhi.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class SaasBahuSammelanGetAllResponse( + @Json(name = "data") val data: List?, + @Json(name = "statusCode") val statusCode: Int?, + @Json(name = "status") val status: String? +) + +@JsonClass(generateAdapter = true) +data class SaasBahuSammelanServerItem( + @Json(name = "id") val id: Int?, + @Json(name = "date") val meetingDate: Long? = 0L, + @Json(name = "place") val place: String?, + @Json(name = "participants") val participants: Int?, + @Json(name = "ashaId") val ashaId: Int?, + @Json(name = "imagePaths") val meetingImages: List? +) \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/SaasBahuSammelanCache.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/SaasBahuSammelanCache.kt new file mode 100644 index 000000000..36103009d --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/SaasBahuSammelanCache.kt @@ -0,0 +1,23 @@ +package org.piramalswasthya.sakhi.model + +import androidx.room.Entity +import androidx.room.PrimaryKey +import androidx.room.TypeConverters +import org.piramalswasthya.sakhi.database.converters.StringListConverter +import org.piramalswasthya.sakhi.database.room.SyncState + +@Entity( + tableName = "SAAS_BAHU_ACTIVITY", +) +data class SaasBahuSammelanCache( + @PrimaryKey(autoGenerate = true) + val id: Long = 0, + val ashaId: Int, + var place: String ? = null, + var participants: Int ? = 0, + var date: Long? = 0L, + @TypeConverters(StringListConverter::class) + var sammelanImages: List? = null, + var syncState: SyncState = SyncState.UNSYNCED + +) diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/SyncLogEntry.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/SyncLogEntry.kt new file mode 100644 index 000000000..d30145244 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/SyncLogEntry.kt @@ -0,0 +1,11 @@ +package org.piramalswasthya.sakhi.model + +data class SyncLogEntry( + val id: Long, + val timestamp: Long, + val level: LogLevel, + val tag: String, + val message: String +) + +enum class LogLevel { DEBUG, INFO, WARN, ERROR } diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/TBConfirmedTreatmentCache.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/TBConfirmedTreatmentCache.kt new file mode 100644 index 000000000..b7314b2ed --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/TBConfirmedTreatmentCache.kt @@ -0,0 +1,92 @@ +package org.piramalswasthya.sakhi.model + +import androidx.room.Embedded +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.Index +import androidx.room.PrimaryKey +import androidx.room.Relation +import org.piramalswasthya.sakhi.configuration.FormDataModel +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.network.TBConfirmedTreatmentDTO +import java.text.SimpleDateFormat +import java.util.Locale + +@Entity( + tableName = "TB_CONFIRMED_TREATMENT", + foreignKeys = [ForeignKey( + entity = BenRegCache::class, + parentColumns = arrayOf("beneficiaryId"), + childColumns = arrayOf("benId"), + onUpdate = ForeignKey.CASCADE, + onDelete = ForeignKey.CASCADE + )], + indices = [Index(name = "ind_tb_confirmed", value = ["benId"])] +) +data class TBConfirmedTreatmentCache( + @PrimaryKey(autoGenerate = true) + val id: Int = 0, + val benId: Long, + var regimenType: String? = null, + var treatmentStartDate: Long = System.currentTimeMillis(), + var expectedTreatmentCompletionDate: Long? = null, + var followUpDate: Long? = null, + var monthlyFollowUpDone: String? = null, + var adherenceToMedicines: String? = null, + var anyDiscomfort: Boolean? = null, + var treatmentCompleted: Boolean? = null, + var actualTreatmentCompletionDate: Long? = null, + var treatmentOutcome: String? = null, + var dateOfDeath: Long? = null, + var placeOfDeath: String? = null, + var reasonForDeath: String = "Tuberculosis", + var reasonForNotCompleting: String? = null, + var syncState: SyncState = SyncState.UNSYNCED, + var createdAt: Long = System.currentTimeMillis(), + var updatedAt: Long = System.currentTimeMillis() +) : FormDataModel { + + fun toDTO(): TBConfirmedTreatmentDTO { + return TBConfirmedTreatmentDTO( + id = 0, + benId = benId, + regimenType = regimenType, + treatmentStartDate = getDateTimeStringFromLong(treatmentStartDate), + expectedTreatmentCompletionDate = getDateTimeStringFromLong(expectedTreatmentCompletionDate), + followUpDate = getDateTimeStringFromLong(followUpDate), + monthlyFollowUpDone = monthlyFollowUpDone, + adherenceToMedicines = adherenceToMedicines, + anyDiscomfort = anyDiscomfort, + treatmentCompleted = treatmentCompleted, + actualTreatmentCompletionDate = getDateTimeStringFromLong(actualTreatmentCompletionDate), + treatmentOutcome = treatmentOutcome, + dateOfDeath = getDateTimeStringFromLong(dateOfDeath), + placeOfDeath = placeOfDeath, + reasonForDeath = reasonForDeath, + reasonForNotCompleting = reasonForNotCompleting + ) + } +} + +data class BenWithTbConfirmedCache( + @Embedded + val ben: BenBasicCache, + @Relation( + parentColumn = "benId", + entityColumn = "benId" + ) + val tb: TBConfirmedTreatmentCache? +) { + fun asTbSuspectedDomainModel(): BenWithTbConfirmedDomain { + return BenWithTbConfirmedDomain( + ben = ben.asBasicDomainModel(), + tb = tb + ) + } +} + +data class BenWithTbConfirmedDomain( + val ben: BenBasicDomain, + val tb: TBConfirmedTreatmentCache? +) + diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/TBScreening.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/TBScreening.kt index e70601778..24ef72485 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/model/TBScreening.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/TBScreening.kt @@ -35,6 +35,17 @@ data class TBScreeningCache( var historyOfTb: Boolean? = null, var takingAntiTBDrugs: Boolean? = null, var familySufferingFromTB: Boolean? = null, + var riseOfFever: Boolean? = null, + var lossOfAppetite: Boolean? = null, + var age: Boolean? = null, + var diabetic: Boolean? = null, + var tobaccoUser: Boolean? = null, + var bmi: Boolean? = null, + var contactWithTBPatient: Boolean? = null, + var historyOfTBInLastFiveYrs: Boolean? = null, + var sympotomatic :String?=null, + var asymptomatic :String?=null, + var recommandateTest :String?=null, var syncState: SyncState = SyncState.UNSYNCED, ) : FormDataModel { fun toDTO(): TBScreeningDTO { @@ -49,7 +60,19 @@ data class TBScreeningCache( nightSweats = nightSweats, historyOfTb = historyOfTb, takingAntiTBDrugs = takingAntiTBDrugs, - familySufferingFromTB = familySufferingFromTB + familySufferingFromTB = familySufferingFromTB, + riseOfFever = riseOfFever, + lossOfAppetite = lossOfAppetite, + age = age, + diabetic = diabetic, + tobaccoUser = tobaccoUser, + bmi = bmi, + contactWithTBPatient = contactWithTBPatient, + historyOfTBInLastFiveYrs = historyOfTBInLastFiveYrs, + sympotomatic = sympotomatic, + asymptomatic = asymptomatic, + recommandateTest = recommandateTest, + ) } } diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/TBSuspected.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/TBSuspected.kt index 6c8602f6f..1ca8f6aa4 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/model/TBSuspected.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/TBSuspected.kt @@ -9,6 +9,7 @@ import androidx.room.Relation import org.piramalswasthya.sakhi.configuration.FormDataModel import org.piramalswasthya.sakhi.database.room.SyncState import org.piramalswasthya.sakhi.network.TBSuspectedDTO +import kotlin.Boolean @Entity( tableName = "TB_SUSPECTED", @@ -27,10 +28,20 @@ data class TBSuspectedCache( val id: Int = 0, val benId: Long, var visitDate: Long = System.currentTimeMillis(), + var visitLabel: String? = null, + var typeOfTBCase: String? = null, + var reasonForSuspicion: String? = null, + var hasSymptoms: Boolean = false, var isSputumCollected: Boolean? = null, var sputumSubmittedAt: String? = null, var nikshayId: String? = null, var sputumTestResult: String? = null, + var isChestXRayDone: Boolean? = null, + var chestXRayResult: String? = null, + var referralFacility: String? = null, + var isTBConfirmed: Boolean? = null, + var isDRTBConfirmed: Boolean? = null, + var isConfirmed: Boolean = false, var referred: Boolean? = null, var followUps: String? = null, var syncState: SyncState = SyncState.UNSYNCED, @@ -45,7 +56,18 @@ data class TBSuspectedCache( nikshayId = nikshayId, sputumTestResult = sputumTestResult, referred = referred, - followUps = followUps + followUps = followUps, + visitLabel= visitLabel, + typeOfTBCase = typeOfTBCase, + reasonForSuspicion = reasonForSuspicion, + hasSymptoms = hasSymptoms, + isChestXRayDone = isChestXRayDone, + chestXRayResult = chestXRayResult, + referralFacility = referralFacility, + isTBConfirmed = isTBConfirmed, + isDRTBConfirmed = isDRTBConfirmed, + isConfirmed = isConfirmed, + ) } } @@ -53,21 +75,37 @@ data class TBSuspectedCache( data class BenWithTbSuspectedCache( @Embedded val ben: BenBasicCache, + @Relation( - parentColumn = "benId", entityColumn = "benId" + parentColumn = "benId", + entityColumn = "benId" ) - val tb: TBSuspectedCache?, + val tbSuspected: TBSuspectedCache?, - ) { + @Relation( + parentColumn = "benId", + entityColumn = "benId" + ) + val tbConfirmedList: List +) +{ fun asTbSuspectedDomainModel(): BenWithTbSuspectedDomain { return BenWithTbSuspectedDomain( ben = ben.asBasicDomainModel(), - tb = tb + tbSuspected = tbSuspected, + tbConfirmedList = tbConfirmedList ) } + } data class BenWithTbSuspectedDomain( val ben: BenBasicDomain, - val tb: TBSuspectedCache? -) \ No newline at end of file + val tbSuspected: TBSuspectedCache?, + val tbConfirmedList: List +) { + val latestTbSyncState: SyncState? + get() = tbConfirmedList + .maxByOrNull { it.followUpDate ?: 0L } + ?.syncState +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/UWIN.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/UWIN.kt new file mode 100644 index 000000000..1f349621e --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/UWIN.kt @@ -0,0 +1,84 @@ +package org.piramalswasthya.sakhi.model + +import androidx.room.Entity +import androidx.room.PrimaryKey +import org.piramalswasthya.sakhi.configuration.FormDataModel +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.network.getLongFromDate +import org.piramalswasthya.sakhi.model.getDateTimeStringFromLong + + + +@Entity(tableName = "UWIN_SESSION") +data class UwinCache( + @PrimaryKey(autoGenerate = true) + val id: Int, + var sessionDate: Long, + var place: String?, + var participantsCount: Int, + var uploadedFiles1: String? = null, + var uploadedFiles2: String? = null, + var processed: String? = "N", + var createdBy: String, + val createdDate: Long = System.currentTimeMillis(), + var updatedBy: String, + var updatedDate: Long = System.currentTimeMillis(), + var syncState: SyncState +) : FormDataModel { + fun asDomainModel(): UwinNetwork { + return UwinNetwork( + id = id, + sessionDate = sessionDate, + place = place, + participantsCount = participantsCount, + uploadedFiles1 = uploadedFiles1, + uploadedFiles2 = uploadedFiles2, + createdBy = createdBy, + createdDate = getDateTimeStringFromLong(createdDate)!!, + updatedBy = updatedBy, + updatedDate = getDateTimeStringFromLong(updatedDate)!!, + ) + } +} + + +data class UwinNetwork( + val id: Int, + val sessionDate: Long, + val place: String?, + val participantsCount: Int, + var uploadedFiles1: String? = null, + var uploadedFiles2: String? = null, + var createdBy: String, + val createdDate: String, + var updatedBy: String, + val updatedDate: String, +) { + fun asCacheModel(): UwinCache { + return UwinCache( + id = id, + sessionDate = sessionDate, + place = place, + participantsCount = participantsCount, + uploadedFiles1 = uploadedFiles1, + uploadedFiles2 = uploadedFiles2, + processed = "P", + createdBy = createdBy, + createdDate = getLongFromDate(createdDate), + updatedBy = updatedBy, + updatedDate = getLongFromDate(updatedDate), + syncState = SyncState.SYNCED, + ) + } +} + + +data class UwinGetAllRequest( + val villageID: Int, + val fromDate: String, + val toDate: String, + val pageNo: Int, + val userId: Int, + val userName: String, + val ashaId: Int +) \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/UploadResponse.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/UploadResponse.kt new file mode 100644 index 000000000..83e1a4017 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/UploadResponse.kt @@ -0,0 +1,12 @@ +package org.piramalswasthya.sakhi.model + +data class UploadResponse( + val data: UploadData?, + val statusCode: Int, + val errorMessage: String, + val status: String +) + +data class UploadData( + val response: String +) \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/User.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/User.kt index 529b6c700..f4f5155fe 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/model/User.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/User.kt @@ -18,8 +18,8 @@ data class UserCache( @ColumnInfo(name = "Password") val password: String, -// @ColumnInfo(name = "service_map_id") -// val serviceMapId : Int, + @ColumnInfo(name = "service_map_id") + val serviceMapId : Int, // @ColumnInfo(name = "service_id") // val serviceId : Int, @@ -211,7 +211,7 @@ data class UserNetwork( userId = userId, userName = userName, password = password, -// serviceMapId = serviceMapId, + serviceMapId = serviceMapId, // servicePointId = servicePointId, // serviceId = serviceId, // servicePointName = servicePointName?:"", @@ -256,6 +256,7 @@ data class User( val name: String, val userName: String, val password: String, + val role: String, val serviceMapId: Int, var vanId: Int = 4, val state: LocationEntity, @@ -279,8 +280,8 @@ data class UserDetailsInResponse( val userName: String, val stateId: Int, val stateName: String, - val workingDistrictId: Int, - val workingDistrictName: String, + val workingDistrictId: Int?=0, + val workingDistrictName: String?=null, val serviceProviderId: Int, val roleId: Int, val roleName: String, @@ -296,9 +297,10 @@ data class UserDetailsInResponse( name = name, userName = userName, password = password, + role = roleName, serviceMapId = providerServiceMapId, state = LocationEntity(id = stateId, name = stateName), - district = LocationEntity(id = workingDistrictId, name = workingDistrictName), + district = LocationEntity(id = 1, name = workingDistrictName.toString()), block = LocationEntity(id = blockId, name = blockName), villages = getLocationEntityListForVillage(villageId, villageName), ) diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/VHNCCache.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/VHNCCache.kt new file mode 100644 index 000000000..98d29c642 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/VHNCCache.kt @@ -0,0 +1,89 @@ +package org.piramalswasthya.sakhi.model + +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey +import org.piramalswasthya.sakhi.configuration.FormDataModel +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.network.VHNCDTO +import java.util.Date + +@Entity(tableName = "VHNC", +) + +data class VHNCCache( + @PrimaryKey(autoGenerate = true) + var id: Int , + var vhncDate: String, + var place: String? = null, + var noOfBeneficiariesAttended: Int? = null, + var image1: String? = null, + var image2: String? = null, + var villageName: String? = null, + var anm: Int? = 0, + var aww: Int? = 0, + var noOfPragnentWoment: Int? = 0, + var noOfLactingMother: Int? = 0, + var noOfCommittee: Int? = 0, + var followupPrevius: Boolean? = null, + var syncState: SyncState = SyncState.UNSYNCED +) : FormDataModel { + fun toDTO(): VHNCDTO { + return VHNCDTO( + id = id, + vhncDate = (vhncDate), + place = place, + noOfBeneficiariesAttended = noOfBeneficiariesAttended, + Image1 = image1, + Image2 = image2, + villageName = villageName, + anm = anm, + aww = aww, + noOfPragnentWoment = noOfPragnentWoment, + noOfLactingMother = noOfLactingMother, + noOfCommittee = noOfCommittee, + followupPrevius = followupPrevius, + + ) + } + + fun toVhncDTODTO(): VHNCCache { + return VHNCCache( + id = id, + place = place, + noOfBeneficiariesAttended = noOfBeneficiariesAttended, + image2 = image1, + image1 = image2, + vhncDate = (vhncDate), + villageName = villageName, + anm = anm, + aww = aww, + noOfPragnentWoment = noOfPragnentWoment, + noOfLactingMother = noOfLactingMother, + noOfCommittee = noOfCommittee, + followupPrevius = followupPrevius, + ) + } +} + + +//data class BenWithHRNPACache( +// @Embedded +// val ben: BenBasicCache, +// @Relation( +// parentColumn = "benId", entityColumn = "benId" +// ) +// val assess: HRPNonPregnantAssessCache?, +// +// ) { +// fun asDomainModel(): BenWithHRNPADomain { +// return BenWithHRNPADomain( +// ben = ben.asBasicDomainModel(), +// assess = assess +// ) +// } +//} +// +//data class VHNDDomain( +// val vhndChache: VHNDCache? +//) diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/VHNDCache.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/VHNDCache.kt new file mode 100644 index 000000000..23e230d29 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/VHNDCache.kt @@ -0,0 +1,99 @@ +package org.piramalswasthya.sakhi.model + +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey +import org.piramalswasthya.sakhi.configuration.FormDataModel +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.network.VHNDDTO +import java.util.Date + +@Entity(tableName = "VHND", +) + +data class VHNDCache( + @PrimaryKey(autoGenerate = true) + var id: Int , + var vhndDate: String, + var place: String? = null, + var noOfBeneficiariesAttended: Int? = null, + var image1: String? = null, + var image2: String? = null, + var vhndPlaceId: Int? = 0, + var pregnantWomenAnc: String? = null, + var lactatingMothersPnc: String? = null, + var childrenImmunization: String? = null, + var knowledgeBalancedDiet: String? = null, + var careDuringPregnancy: String? = null, + var importanceBreastfeeding: String? = null, + var complementaryFeeding: String? = null, + var hygieneSanitation: String? = null, + var familyPlanningHealthcare: String? = null, + var selectAllEducation: Boolean? = false, + var syncState: SyncState = SyncState.UNSYNCED +) : FormDataModel { + fun toDTO(): VHNDDTO { + return VHNDDTO( + id = id, + vhndDate = (vhndDate), + place = place, + noOfBeneficiariesAttended = noOfBeneficiariesAttended, + Image1 = image1, + Image2 = image2, + vhndPlaceId = vhndPlaceId, + pregnantWomenAnc = pregnantWomenAnc, + lactatingMothersPnc = lactatingMothersPnc, + childrenImmunization = childrenImmunization, + knowledgeBalancedDiet = knowledgeBalancedDiet, + careDuringPregnancy = careDuringPregnancy, + importanceBreastfeeding = importanceBreastfeeding, + complementaryFeeding = complementaryFeeding, + hygieneSanitation = hygieneSanitation, + familyPlanningHealthcare = familyPlanningHealthcare, + + ) + } + + fun toVhndDTODTO(): VHNDCache { + return VHNDCache( + id = id, + place = place, + noOfBeneficiariesAttended = noOfBeneficiariesAttended, + image2 = image1, + image1 = image2, + vhndDate = (vhndDate), + vhndPlaceId = vhndPlaceId, + pregnantWomenAnc = pregnantWomenAnc, + lactatingMothersPnc = lactatingMothersPnc, + childrenImmunization = childrenImmunization, + knowledgeBalancedDiet = knowledgeBalancedDiet, + careDuringPregnancy = careDuringPregnancy, + importanceBreastfeeding = importanceBreastfeeding, + complementaryFeeding = complementaryFeeding, + hygieneSanitation = hygieneSanitation, + familyPlanningHealthcare = familyPlanningHealthcare, + ) + } +} + + +//data class BenWithHRNPACache( +// @Embedded +// val ben: BenBasicCache, +// @Relation( +// parentColumn = "benId", entityColumn = "benId" +// ) +// val assess: HRPNonPregnantAssessCache?, +// +// ) { +// fun asDomainModel(): BenWithHRNPADomain { +// return BenWithHRNPADomain( +// ben = ben.asBasicDomainModel(), +// assess = assess +// ) +// } +//} +// +//data class VHNDDomain( +// val vhndChache: VHNDCache? +//) diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/CUFYFormResponseJsonEntity.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/CUFYFormResponseJsonEntity.kt new file mode 100644 index 000000000..d1a5a76b8 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/CUFYFormResponseJsonEntity.kt @@ -0,0 +1,23 @@ +package org.piramalswasthya.sakhi.model.dynamicEntity + +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey + +@Entity( + tableName = "children_under_five_all_visit", + indices = [Index(value = ["benId","hhId","visitDate","formId"], unique = true)] +) +data class CUFYFormResponseJsonEntity( + @PrimaryKey(autoGenerate = true) val id: Int = 0, + val benId: Long, + val hhId: Long, + val visitDate: String, + val formId: String, + val version: Int, + val formDataJson: String, + val isSynced: Boolean = false, + val createdAt: Long = System.currentTimeMillis(), + val updatedAt: Long = System.currentTimeMillis(), + val syncedAt: Long? = null +) \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/FilariaMDA/FilariaMDAFormResponseJsonEntity.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/FilariaMDA/FilariaMDAFormResponseJsonEntity.kt new file mode 100644 index 000000000..0e2c8730b --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/FilariaMDA/FilariaMDAFormResponseJsonEntity.kt @@ -0,0 +1,26 @@ +package org.piramalswasthya.sakhi.model.dynamicEntity.FilariaMDA + +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey +import org.piramalswasthya.sakhi.database.room.SyncState + +@Entity( + tableName = "FILARIA_MDA_VISIT_HISTORY", + indices = [ + Index(value = ["hhId", "formId", "visitMonth"], unique = true), + Index(value = ["hhId", "visitDate"]) + ] +) +data class FilariaMDAFormResponseJsonEntity( + @PrimaryKey(autoGenerate = true) val id: Int = 0, + val hhId: Long, + val visitDate: String, + val visitMonth: String, + val formId: String, + val version: Int, + val formDataJson: String, + val isSynced: Boolean = false, + val createdAt: Long = System.currentTimeMillis(), + val syncedAt: String? = null +) diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/FormNCDFollowUpSubmitRequest.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/FormNCDFollowUpSubmitRequest.kt new file mode 100644 index 000000000..243792bc6 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/FormNCDFollowUpSubmitRequest.kt @@ -0,0 +1,14 @@ +package org.piramalswasthya.sakhi.model.dynamicEntity +data class FormNCDFollowUpSubmitRequest( + val id: Int, + val benId: Long, + val hhId: Long, + val visitNo: Int, + val followUpNo: Int, + val treatmentStartDate: String, + val followUpDate: String?, + val diagnosisCodes: String?, + val formId: String, + val version: Int, + val formDataJson: String // 🔥 STRING ONLY +) diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/FormResponseJsonEntity.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/FormResponseJsonEntity.kt new file mode 100644 index 000000000..f02dea163 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/FormResponseJsonEntity.kt @@ -0,0 +1,25 @@ +package org.piramalswasthya.sakhi.model.dynamicEntity + +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey + +@Entity( + tableName = "all_visit_history", + indices = [Index(value = ["benId","hhId", "visitDay","visitDate", "formId"], unique = true)] +) +data class FormResponseJsonEntity( + @PrimaryKey(autoGenerate = true) val id: Int = 0, + val benId: Long, + val hhId: Long, + val visitDay: String, + val visitDate: String, + val formId: String, + val version: Int, + val formDataJson: String, + val isSynced: Boolean = false, + val createdAt: Long = System.currentTimeMillis(), + val syncedAt: Long? = null +) + + diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/FormSchemaDto.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/FormSchemaDto.kt new file mode 100644 index 000000000..374d0921e --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/FormSchemaDto.kt @@ -0,0 +1,104 @@ +package org.piramalswasthya.sakhi.model.dynamicEntity + +import com.google.gson.Gson +import com.google.gson.annotations.SerializedName + +data class FormSchemaDto( + @SerializedName("formId") + val formId: String, + + @SerializedName("formName") + val formName: String, + + @SerializedName("version") + val version: Int = 1, + + + + @SerializedName("sections") + val sections: List = emptyList() +) { + companion object { + fun fromJson(json: String): FormSchemaDto = Gson().fromJson(json, FormSchemaDto::class.java) + } + + fun toJson(): String = Gson().toJson(this) +} + +data class FormSectionDto( + @SerializedName("sectionId") + val sectionId: String = "", + + @SerializedName("sectionTitle") + val sectionTitle: String = "", + + @SerializedName("fields") + val fields: List = emptyList() +) + +data class FormFieldDto( + @SerializedName("fieldId") + val fieldId: String = "", + + @SerializedName("label") + val label: String = "", + + @SerializedName("type") + val type: String = "", + + @SerializedName("options") + var options: Any? = null, + + @SerializedName("isRequired") + val required: Boolean = false, + + @SerializedName("conditional") + val conditional: ConditionalLogic? = null, + + @SerializedName("validation") + val validation: FieldValidationDto? = null, + + @SerializedName("placeholder") + val placeholder: String? = null, + + @SerializedName("defaultValue") + val defaultValue: String? = null, + + @SerializedName("default") + val default: Any? = null, + + @SerializedName("value") + var value: Any? = null, + + @Transient var visible: Boolean = true, + @Transient var errorMessage: String? = null, + @Transient var isEditable: Boolean = true +) + +data class ConditionalLogic( + @SerializedName("dependsOn") + val dependsOn: String? = null, + + @SerializedName("expectedValue") + val expectedValue: String? = null +) + +data class FieldValidationDto( + val min: Float? = null, + val max: Float? = null, + val minDate: String? = null, + val maxDate: String? = null, + val maxLength: Int? = null, + val regex: String? = null, + val errorMessage: String? = null, + val decimalPlaces: Int? = null, + val maxSizeMB: Int? = null, + val afterField: String? = null, + val beforeField: String? = null, + val incremental: Boolean? = null +) + +data class OptionItem( + val label: String = "", + val value: String = "" +) diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/FormSchemaEntity.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/FormSchemaEntity.kt new file mode 100644 index 000000000..71419d270 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/FormSchemaEntity.kt @@ -0,0 +1,15 @@ +package org.piramalswasthya.sakhi.model.dynamicEntity + +import androidx.room.Entity +import androidx.room.PrimaryKey +import com.google.gson.annotations.SerializedName + +@Entity(tableName = "form_schema") +data class FormSchemaEntity( + @PrimaryKey(autoGenerate = false) + val formId: String, + val formName: String, + val language: String, + val version: Int = 1, + val schemaJson: String +) diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/FormSubmitRequest.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/FormSubmitRequest.kt new file mode 100644 index 000000000..f2a3936e7 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/FormSubmitRequest.kt @@ -0,0 +1,10 @@ +package org.piramalswasthya.sakhi.model.dynamicEntity + +data class FormSubmitRequest( + val userName: String, + val formId: String, + val beneficiaryId: Long, + val houseHoldId: Long, + val visitDate: String, + val fields: Map +) diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/InfantEntity.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/InfantEntity.kt new file mode 100644 index 000000000..1e9460846 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/InfantEntity.kt @@ -0,0 +1,18 @@ +package org.piramalswasthya.sakhi.model.dynamicEntity + +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "infant") +data class InfantEntity( + @PrimaryKey(autoGenerate = true) + val id: Int = 0, + val rchId: String, + val name: String, + val motherName: String, + val fatherName: String?, + val dob: String, + val gender: String, + val phoneNumber: String, + val sncuDischarged: Boolean = false +) diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/NCDFollowUpResponse.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/NCDFollowUpResponse.kt new file mode 100644 index 000000000..fd1c3b26d --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/NCDFollowUpResponse.kt @@ -0,0 +1,6 @@ +package org.piramalswasthya.sakhi.model.dynamicEntity + +data class NCDFollowUpResponse( + val statusCode: Int, + val data: List +) diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/NCDReferalFormResponseJsonEntity.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/NCDReferalFormResponseJsonEntity.kt new file mode 100644 index 000000000..c5a55c05e --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/NCDReferalFormResponseJsonEntity.kt @@ -0,0 +1,40 @@ +package org.piramalswasthya.sakhi.model.dynamicEntity + +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey + +@Entity( + tableName = "ncd_referal_all_visit", + indices = [ + Index(value = ["benId", "hhId"]), + Index( + value = ["benId", "hhId", "visitNo", "followUpNo"], + unique = true + ) + ] +) +data class NCDReferalFormResponseJsonEntity( + + @PrimaryKey(autoGenerate = true) + val id: Int = 0, + + val benId: Long, + val hhId: Long, + + val visitNo: Int, + val followUpNo: Int, + + val treatmentStartDate: String, + val followUpDate: String? = null, + + val diagnosisCodes: String?, + val formId: String, + val version: Int, + val formDataJson: String, + + val isSynced: Boolean = false, + val createdAt: Long = System.currentTimeMillis(), + val updatedAt: Long = System.currentTimeMillis(), + val syncedAt: Long? = null +) diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/OptionItemListDeserializer.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/OptionItemListDeserializer.kt new file mode 100644 index 000000000..159dfae7f --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/OptionItemListDeserializer.kt @@ -0,0 +1,36 @@ +package org.piramalswasthya.sakhi.model.dynamicEntity + +/** + * Utility to convert raw options (from Gson) into List. + * + * Gson deserializes "options" as Any?, which at runtime becomes: + * - List for [{"id":20,"value":"Home","label":"Home"},...] + * - List for ["Yes","No"] + * - null if not present + * + * This function handles all cases without relying on Gson TypeAdapters. + */ +object OptionItemParser { + + fun parse(raw: Any?): List? { + if (raw == null) return null + if (raw !is List<*>) return null + + val result = raw.mapNotNull { item -> + when (item) { + is OptionItem -> item + is Map<*, *> -> { + val value = item["value"]?.toString() ?: return@mapNotNull null + val label = item["label"]?.toString() ?: value + OptionItem(label = label, value = value) + } + is String -> OptionItem(label = item, value = item) + else -> null + } + } + return result.ifEmpty { null } + } +} + +/** Extension on FormFieldDto to get parsed option items. */ +fun FormFieldDto.optionItems(): List? = OptionItemParser.parse(options) diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/anc/ANCFormResponseJsonEntity.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/anc/ANCFormResponseJsonEntity.kt new file mode 100644 index 000000000..5d375a83b --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/anc/ANCFormResponseJsonEntity.kt @@ -0,0 +1,22 @@ +package org.piramalswasthya.sakhi.model.dynamicEntity.anc + +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey + +@Entity( + tableName = "ALL_VISIT_HISTORY_ANC", + indices = [Index(value = ["benId", "visitDay","visitDate", "formId"], unique = true)] +) +data class ANCFormResponseJsonEntity( + @PrimaryKey(autoGenerate = true) val id: Int = 0, + val benId: Long, + val visitDay: String, + val visitDate: String, + val formId: String, + val version: Int, + val formDataJson: String, + val isSynced: Boolean = false, + val createdAt: Long = System.currentTimeMillis(), + val syncedAt: Long? = null +) diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/ben_ifa/BenIfaFormResponseJsonEntity.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/ben_ifa/BenIfaFormResponseJsonEntity.kt new file mode 100644 index 000000000..6172dfb23 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/ben_ifa/BenIfaFormResponseJsonEntity.kt @@ -0,0 +1,26 @@ +package org.piramalswasthya.sakhi.model.dynamicEntity.ben_ifa + +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey + +@Entity(tableName = "ALL_BEN_IFA_VISIT_HISTORY", + indices = [ + Index( + value = ["benId", "hhId", "visitDate", "formId"], + unique = true + ) +]) + +data class BenIfaFormResponseJsonEntity( + @PrimaryKey(autoGenerate = true) val id: Int = 0, + val benId: Long, + val hhId: Long, + val visitDate: String, + val formId: String, + val version: Int, + val formDataJson: String, + val isSynced: Boolean = false, + val createdAt: Long = System.currentTimeMillis(), + val syncedAt: Long? = null +) \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/eye_surgery/EyeSurgeryFormResponseJsonEntity.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/eye_surgery/EyeSurgeryFormResponseJsonEntity.kt new file mode 100644 index 000000000..85ac04fa7 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/eye_surgery/EyeSurgeryFormResponseJsonEntity.kt @@ -0,0 +1,26 @@ +package org.piramalswasthya.sakhi.model.dynamicEntity.eye_surgery + +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey + +@Entity( + tableName = "ALL_EYE_SURGERY_VISIT_HISTORY", + indices = [ + Index(value = ["benId", "formId", "visitMonth"], unique = true), + Index(value = ["benId", "visitDate"]) + ] +) +data class EyeSurgeryFormResponseJsonEntity( + @PrimaryKey(autoGenerate = true) val id: Int = 0, + val benId: Long, + val hhId: Long, + val visitDate: String, + val visitMonth: String, + val formId: String, + val version: Int, + val formDataJson: String, + val isSynced: Boolean = false, + val createdAt: Long = System.currentTimeMillis(), + val syncedAt: String? = null +) diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/filariaaMdaCampaign/FilariaMdaCampaignResponseJsonEntity.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/filariaaMdaCampaign/FilariaMdaCampaignResponseJsonEntity.kt new file mode 100644 index 000000000..b20242efe --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/filariaaMdaCampaign/FilariaMdaCampaignResponseJsonEntity.kt @@ -0,0 +1,26 @@ +package org.piramalswasthya.sakhi.model.dynamicEntity.filariaaMdaCampaign + +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey +import org.piramalswasthya.sakhi.database.room.SyncState + +@Entity( + tableName = "FILARIA_MDA_CAMPAIGN_HISTORY", + indices = [ + Index(value = [ "formId", "visitYear"], unique = true), + Index(value = ["visitDate"]) + ] +) +data class FilariaMDACampaignFormResponseJsonEntity( + @PrimaryKey(autoGenerate = true) val id: Int = 0, + val visitDate: String, + val visitYear: String, + val formId: String, + val version: Int, + val formDataJson: String, + val isSynced: Boolean = false, + var syncState: SyncState = SyncState.UNSYNCED, + val createdAt: Long = System.currentTimeMillis(), + val syncedAt: String? = null +) \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/hbyc/FormResponseJsonEntityHBYC.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/hbyc/FormResponseJsonEntityHBYC.kt new file mode 100644 index 000000000..87ea44f20 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/hbyc/FormResponseJsonEntityHBYC.kt @@ -0,0 +1,25 @@ +package org.piramalswasthya.sakhi.model.dynamicEntity.hbyc + +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey + +@Entity( + tableName = "ALL_VISIT_HISTORY_HBYC", + indices = [Index(value = ["benId","hhId", "visitDay","visitDate", "formId"], unique = true)] +) +data class FormResponseJsonEntityHBYC( + @PrimaryKey(autoGenerate = true) val id: Int = 0, + val benId: Long, + val hhId: Long, + val visitDay: String, + val visitDate: String, + val formId: String, + val version: Int, + val formDataJson: String, + val isSynced: Boolean = false, + val createdAt: Long = System.currentTimeMillis(), + val syncedAt: Long? = null +) + + diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/mosquitonetEntity/MosquitoNetFormResponseJsonEntity.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/mosquitonetEntity/MosquitoNetFormResponseJsonEntity.kt new file mode 100644 index 000000000..465486662 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicEntity/mosquitonetEntity/MosquitoNetFormResponseJsonEntity.kt @@ -0,0 +1,23 @@ +package org.piramalswasthya.sakhi.model.dynamicEntity.mosquitonetEntity + +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey + +@Entity( + tableName = "mosquito_net_visit", + indices = [Index(value = ["hhId", "visitDate","formId"], unique = true)] + +) + +data class MosquitoNetFormResponseJsonEntity( + @PrimaryKey(autoGenerate = true) + val id: Int = 0, + val hhId: Long, + val formId: String, + val version: Int, + val visitDate: String, + val formDataJson: String, + val isSynced: Boolean = false, + val syncedAt: String? = null +) diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicModel/ApiResponse.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicModel/ApiResponse.kt new file mode 100644 index 000000000..8ff51bb77 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicModel/ApiResponse.kt @@ -0,0 +1,16 @@ +package org.piramalswasthya.sakhi.model.dynamicModel + +import com.google.gson.annotations.SerializedName +data class ApiResponse( + @SerializedName("success") + val success: Boolean, + + @SerializedName("message") + val message: String? = null, + + @SerializedName("statusCode") + val statusCode: Int? = null, + + @SerializedName("data") + val data: T? = null +) diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicModel/HBNCFormDownloadResponse.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicModel/HBNCFormDownloadResponse.kt new file mode 100644 index 000000000..2156fa2ec --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicModel/HBNCFormDownloadResponse.kt @@ -0,0 +1,8 @@ +package org.piramalswasthya.sakhi.model.dynamicModel + +data class HBNCFormDownloadResponse( + val id: Int, + val beneficiaryId: Int, + val visitDate: String, + val fields: Map +) diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicModel/HBNCVisitListResponse.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicModel/HBNCVisitListResponse.kt new file mode 100644 index 000000000..7d4356197 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicModel/HBNCVisitListResponse.kt @@ -0,0 +1,17 @@ +package org.piramalswasthya.sakhi.model.dynamicModel + +import com.google.gson.annotations.SerializedName + +data class HBNCVisitListResponse( + @SerializedName("data") + val data: List = emptyList(), + + @SerializedName("statusCode") + val statusCode: Int = 0, + + @SerializedName("errorMessage") + val errorMessage: String? = null, + + @SerializedName("status") + val status: String? = null +) diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicModel/HBNCVisitRequest.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicModel/HBNCVisitRequest.kt new file mode 100644 index 000000000..8279d55e7 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicModel/HBNCVisitRequest.kt @@ -0,0 +1,9 @@ +package org.piramalswasthya.sakhi.model.dynamicModel + +data class HBNCVisitRequest( + val fromDate: String, + val toDate: String, + val pageNo: Int, + val ashaId: Int, + val userName: String +) diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicModel/HBNCVisitResponse.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicModel/HBNCVisitResponse.kt new file mode 100644 index 000000000..ce6e2b3b7 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicModel/HBNCVisitResponse.kt @@ -0,0 +1,22 @@ +package org.piramalswasthya.sakhi.model.dynamicModel + +import com.google.gson.JsonObject +import com.google.gson.annotations.SerializedName + +data class HBNCVisitResponse( + @SerializedName("id") + val id: Int, + + @SerializedName("houseHoldId") + val houseHoldId: Long, + + @SerializedName("beneficiaryId") + val beneficiaryId: Long, + + + @SerializedName("visitDate") + val visitDate: String, + + @SerializedName("fields") + val fields: JsonObject +) diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicModel/MDACampaignItem.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicModel/MDACampaignItem.kt new file mode 100644 index 000000000..90fe40ff2 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicModel/MDACampaignItem.kt @@ -0,0 +1,10 @@ +package org.piramalswasthya.sakhi.model.dynamicModel + + +data class MDACampaignItem( + val srNo: Int, + val startDate: String, + val endDate: String, + val noOffamilies: String, + val noOfIndividuals: String, +) \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicModel/VisitCard.kt b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicModel/VisitCard.kt new file mode 100644 index 000000000..5db748cad --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/model/dynamicModel/VisitCard.kt @@ -0,0 +1,9 @@ +package org.piramalswasthya.sakhi.model.dynamicModel + +data class VisitCard( + val visitDay: String, + val visitDate: String, + val isCompleted: Boolean, + val isEditable: Boolean, + val isBabyDeath: Boolean +) diff --git a/app/src/main/java/org/piramalswasthya/sakhi/network/AbhaApiService.kt b/app/src/main/java/org/piramalswasthya/sakhi/network/AbhaApiService.kt index 7fe9dac1f..9e7e4db36 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/network/AbhaApiService.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/network/AbhaApiService.kt @@ -1,30 +1,62 @@ package org.piramalswasthya.sakhi.network import okhttp3.ResponseBody +import org.piramalswasthya.sakhi.utils.KeyUtils import retrofit2.Response -import retrofit2.http.* +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.Header +import retrofit2.http.Headers +import retrofit2.http.POST +import retrofit2.http.Url interface AbhaApiService { @Headers("No-Auth: true") @POST suspend fun getToken( - @Url url: String = "https://dev.abdm.gov.in/gateway/v0.5/sessions", + @Url url: String = KeyUtils.abhaTokenUrl(),//BuildConfig.ABHA_TOKEN_URL, + @Header("X-CM-ID") id: String = "sbx", + @Header("REQUEST-ID") requestId: String, + @Header("TIMESTAMP") timestamp: String, @Body request: AbhaTokenRequest = AbhaTokenRequest() ): Response + // Generate OTP (v1) @POST("v1/registration/aadhaar/generateOtp") suspend fun generateAadhaarOtp(@Body aadhaar: AbhaGenerateAadhaarOtpRequest): Response + // Generate OTP (v2) @POST("v2/registration/aadhaar/generateOtp") suspend fun generateAadhaarOtpV2(@Body aadhaar: AbhaGenerateAadhaarOtpRequest): Response + // Generate OTP (v3) + @POST("v3/enrollment/request/otp") + suspend fun generateAadhaarOtpV3(@Body aadhaar: AbhaGenerateAadhaarOtpRequest, @Header("REQUEST-ID") requestId: String, @Header("TIMESTAMP") timestamp: String): Response + @POST("v1/registration/aadhaar/resendAadhaarOtp") suspend fun resendAadhaarOtp(@Body aadhaar: AbhaResendAadhaarOtpRequest): Response + // Verify OTP (v1/v2) @POST("v1/registration/aadhaar/verifyOTP") suspend fun verifyAadhaarOtp(@Body request: AbhaVerifyAadhaarOtpRequest): Response + // Verify OTP (v3) + @POST("v3/enrollment/enrol/byAadhaar") + suspend fun verifyAadhaarOtp3(@Body request: AbhaVerifyAadhaarOtpRequest, @Header("REQUEST-ID") requestId: String, @Header("TIMESTAMP") timestamp: String): Response + + @GET("v3/profile/account/abha-card") + suspend fun printAbhaCard(@Header("REQUEST-ID") requestId: String, @Header("TIMESTAMP") timestamp: String): Response + + @POST("v3/profile/account/abha/search") + suspend fun searchAbha(@Body searchAbha: SearchAbhaRequest, @Header("REQUEST-ID") requestId: String, @Header("TIMESTAMP") timestamp: String): Response + + @POST("v3/profile/login/request/otp") + suspend fun loginGenerateOtp(@Body loginOtp: LoginGenerateOtpRequest, @Header("REQUEST-ID") requestId: String, @Header("TIMESTAMP") timestamp: String): Response + + @POST("v3/profile/login/verify") + suspend fun loginVerifyOtp(@Body loginOtp: LoginVerifyOtpRequest, @Header("REQUEST-ID") requestId: String, @Header("TIMESTAMP") timestamp: String): Response + @POST("v1/registration/aadhaar/generateMobileOTP") suspend fun generateMobileOtp(@Body mobile: AbhaGenerateMobileOtpRequest): Response @@ -34,6 +66,10 @@ interface AbhaApiService { @POST("v1/registration/aadhaar/verifyMobileOTP") suspend fun verifyMobileOtp(@Body request: AbhaVerifyMobileOtpRequest): Response + // Verify Updated Mobile OTP (v3) + @POST("v3/enrollment/auth/byAbdm") + suspend fun verifyMobileOtp3(@Body request: AbhaVerifyMobileOtpRequest, @Header("REQUEST-ID") requestId: String, @Header("TIMESTAMP") timestamp: String): Response + @POST("v1/registration/aadhaar/createHealthIdWithPreVerified") suspend fun createAbhaId(@Body request: CreateAbhaIdRequest): Response @@ -48,7 +84,9 @@ interface AbhaApiService { @GET suspend fun getAuthCert( - @Url url: String = "https://healthidsbx.abdm.gov.in/api/v2/auth/cert" + @Url url: String =KeyUtils.abhaAuthUrl(), //BuildConfig.ABHA_AUTH_URL, + @Header("REQUEST-ID") requestId: String, + @Header("TIMESTAMP") timestamp: String ): Response @GET("v2/ha/lgd/states") diff --git a/app/src/main/java/org/piramalswasthya/sakhi/network/AmritApiService.kt b/app/src/main/java/org/piramalswasthya/sakhi/network/AmritApiService.kt index cc986b1a6..6dea02f8c 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/network/AmritApiService.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/network/AmritApiService.kt @@ -1,205 +1,500 @@ package org.piramalswasthya.sakhi.network +import okhttp3.MultipartBody +import okhttp3.RequestBody import okhttp3.ResponseBody import org.piramalswasthya.sakhi.model.* +import org.piramalswasthya.sakhi.model.dynamicEntity.FormNCDFollowUpSubmitRequest +import org.piramalswasthya.sakhi.model.dynamicEntity.FormSchemaDto +import org.piramalswasthya.sakhi.model.dynamicEntity.FormSubmitRequest +import org.piramalswasthya.sakhi.model.dynamicEntity.NCDFollowUpResponse +import org.piramalswasthya.sakhi.model.dynamicModel.ApiResponse +import org.piramalswasthya.sakhi.model.dynamicModel.HBNCVisitListResponse +import org.piramalswasthya.sakhi.model.dynamicModel.HBNCVisitRequest import retrofit2.Response import retrofit2.http.* interface AmritApiService { + @Multipart + @POST("flw-api/maa-meetings/saveAll") + suspend fun postMaaMeetingMultipart( + @Part("villageName") villageName: RequestBody, + @Part("noOfPragnentWoment") noOfPragnentWoment: RequestBody, + @Part("noOfLactingMother") noOfLactingMother: RequestBody, + @Part("mitaninActivityCheckList") mitaninActivityCheckList: RequestBody, + @Part("meetingDate") meetingDate: RequestBody, + @Part("place") place: RequestBody, + @Part("participants") participants: RequestBody, + @Part("ashaId") ashaId: RequestBody, + @Part("createdBy") createdBy: RequestBody, + @Part meetingImages: List + ): Response + + + @Multipart + @POST("flw-api/sammelans/saveAll") + suspend fun postSaasBahuSammelanMultipart( + @Part("date") meetingDate: RequestBody, + @Part("place") place: RequestBody, + @Part("participants") participants: RequestBody, + @Part("ashaId") ashaId: RequestBody, + @Part meetingImages: List + ): Response - @Headers("No-Auth: true") - @POST("commonapi-v1.0/user/userAuthenticate/") + @POST("flw-api/maa-meetings/getAll") + suspend fun getMaaMeetings(@Body userDetail: GetDataRequest): Response + + @POST("flw-api/sammelans/getAll") + suspend fun getSaasBahuSammelans(@Body userDetail: GetDataRequest): Response + + @Headers("No-Auth: true", "User-Agent: okhttp") + @POST("common-api/user/userAuthenticate") suspend fun getJwtToken(@Body json: TmcAuthUserRequest): Response - @GET("flw-0.0.1/user/getUserDetail") + @Headers("No-Auth: true","User-Agent: okhttp") + @POST("common-api/user/refreshToken") + suspend fun getRefreshToken(@Body json: TmcRefreshTokenRequest): Response + + @GET("flw-api/user/getUserDetail") // @GET("user/getUserRole") suspend fun getUserDetailsById( @Query("userId") userId: Int ): UserNetworkResponse - @POST("tmapi-v1.0/registrar/registrarBeneficaryRegistrationNew") + @POST("common-api/firebaseNotification/userToken") + suspend fun saveFirebaseToken(@Body json: Map): Response + + @POST("tm-api/registrar/registrarBeneficaryRegistrationNew") suspend fun getBenIdFromBeneficiarySending(@Body beneficiaryDataSending: BeneficiaryDataSending): Response @POST("hwc-facility-service/registrar/registrarBeneficaryRegistrationNew") suspend fun getBenIdFromBeneficiarySending(@Body benCHOPost: BenCHOPost): Response - @POST("identity-0.0.1/rmnch/syncDataToAmrit") + @POST("identity-api/rmnch/syncDataToAmrit") suspend fun submitRmnchDataAmrit(@Body sendingRMNCHData: SendingRMNCHData): Response + @POST("flw-api/asha/editProfile") + suspend fun submitAshaProfileData(@Body sendAshaPost: ProfileActivityCache): Response + // @POST("beneficiary/getBeneficiaryData") - @POST("flw-0.0.1/beneficiary/getBeneficiaryData") + @POST("flw-api/beneficiary/getBeneficiaryData") suspend fun getBeneficiaries(@Body userDetail: GetDataPaginatedRequest): Response - @POST("flw-0.0.1/cbac/getAll") + @POST("flw-api/generalOpd/getBeneficiaries") + suspend fun getgeneralOPDBeneficiaries(@Body userDetail: GetDataPaginatedRequestForGeneralOPD): Response + + @POST("common-api/beneficiaryConsent/sendConsent") + suspend fun sendOtp(@Body sendOtpRequest: sendOtpRequest): Response + + @POST("common-api/beneficiaryConsent/resendConsent") + suspend fun resendOtp(@Body sendOtpRequest: sendOtpRequest): Response + + @POST("common-api/beneficiaryConsent/validateConsent") + suspend fun validateOtp(@Body validateOtp: ValidateOtpRequest ): Response + + @POST("flw-api/cbac/getAll") suspend fun getCbacs(@Body userDetail: GetDataPaginatedRequest): Response - @POST("flw-0.0.1/cbac/saveAll") - suspend fun postCbacs(/*@Url url : String ="http://192.168.1.94:8081/cbac/saveAll",*/@Body list: List): Response + @POST("/hwc-api/NCD/getByUserCbacDetails") + suspend fun getCbacData(@Body getcbacRequest: GetCBACRequest) : Response + + @POST("/hwc-api/common/getBenReferDetailsByCreatedBy") + suspend fun getCbacReferData(@Body getcbacRequest: GetCBACRequest) : Response + +// @POST("flw-api/cbac/saveAll") + @POST("hwc-api/NCD/save/nurseData") + suspend fun postCbacs(/*@Url url : String ="http://192.168.1.94:8081/cbac/saveAll",*/@Body list: CbacRequest): Response + + @POST("hwc-api/NCD/save/referDetails") + suspend fun postRefer(@Body list: ReferralRequest): Response + // @POST("tb/screening/getAll") - @POST("flw-0.0.1/tb/screening/getAll") + @POST("flw-api/tb/screening/getAll") suspend fun getTBScreeningData(@Body userDetail: GetDataPaginatedRequest): Response - @POST("flw-0.0.1/tb/suspected/getAll") + @POST("flw-api/tb/suspected/getAll") // @POST("tb/suspected/getAll") suspend fun getTBSuspectedData(@Body userDetail: GetDataPaginatedRequest): Response - @POST("flw-0.0.1/tb/screening/saveAll") + @GET("flw-api/tb/confirmed/getAll") + suspend fun getTBConfirmedData(): Response + + + @POST("flw-api/tb/screening/saveAll") // @POST("tb/screening/saveAll") suspend fun saveTBScreeningData(@Body tbScreeningRequestDTO: TBScreeningRequestDTO): Response - @POST("flw-0.0.1/tb/suspected/saveAll") -// @POST("tb/suspected/saveAll") + @POST("flw-api/disease/kalaAzar/saveAll") + suspend fun saveKalaAzarScreeningData(@Body kalaAzarScreenRequestDTO: KalaAzarScreeningRequestDTO): Response + + @POST("flw-api/disease/malaria/saveAll") + suspend fun saveMalariaScreeningData(@Body malariaScreeningRequestDTO: MalariaScreeningRequestDTO): Response + + @POST("flw-api/disease/leprosy/saveAll") + suspend fun saveLeprosyScreeningData(@Body leprosyScreeningRequestDTO: LeprosyScreeningRequestDTO): Response + + + @POST("flw-api/disease/aesJe/saveAll") + suspend fun saveAESScreeningData(@Body aesScreeningRequestDTO: AESScreeningRequestDTO): Response + + @POST("flw-api/disease/filaria/saveAll") + suspend fun saveFilariaScreeningData(@Body filariaScreeningRequestDTO: FilariaScreeningRequestDTO): Response + + @POST("flw-api/disease/leprosy/getAll") + suspend fun getAllLeprosyData(@Body request: GetDataPaginatedRequestForDisease): Response + + @POST("flw-api/disease/leprosy/followUp/getAll") + suspend fun getAllLeprosyFollowUpData(@Body request: GetDataPaginatedRequestForDisease): Response + + @POST("flw-api/disease/leprosy/followUp/saveAll") + suspend fun saveLeprosyFollowUpData(@Body request: List): Response + + @POST("flw-api/disease/getAllDisease") + suspend fun getMalariaScreeningData(@Body userDetail: GetDataPaginatedRequestForDisease): Response + + @GET("flw-api/irsRound/list/{householdId}") + suspend fun getScreeningData( + @Path("householdId") householdId: Long + ): Response + + @POST("flw-api/adolescentHealth/getAll") + suspend fun getAdolescentHealthData(@Body userDetail: GetDataPaginatedRequest): Response + + @POST("flw-api/adolescentHealth/saveAll") + suspend fun saveAdolescentHealthData(@Body adolescentHealthRequestDTO: AdolescentHealthRequestDTO): Response + + @POST("flw-api/tb/suspected/saveAll") suspend fun saveTBSuspectedData(@Body tbSuspectedRequestDTO: TBSuspectedRequestDTO): Response - @POST("flw-0.0.1/highRisk/pregnant/assess/getAll") + @POST("flw-api/tb/confirmed/save") + suspend fun saveTBConfirmedData(@Body tbConfirmedRequestDTO: TBConfirmedRequestDTO): Response + + @POST("flw-api/follow-up/save") + suspend fun saveMalariaConfirmedData(@Body malariaConfirmedRequestDTO: MalariaConfirmedRequestDTO): Response + + @POST("flw-api/follow-up/get") + suspend fun getMalariaConfirmedData(@Body malariaConfirmedRequestDTO: GetDataPaginatedRequestForDisease): Response + + @POST("flw-api/highRisk/pregnant/assess/getAll") // @POST("highRisk/pregnant/assess/getAll") suspend fun getHRPAssessData(@Body userDetail: GetDataPaginatedRequest): Response - @POST("flw-0.0.1/highRisk/pregnant/assess/saveAll") + @POST("flw-api/forms/villageLevel/getAll") + suspend fun getVLFData(@Body userDetail: GetVHNDRequest): Response + + @POST("flw-api/forms/villageLevel/vhnd/saveAll") + suspend fun saveVHNDData(@Body userDataDTO: UserDataDTO): Response + + @POST("flw-api/forms/villageLevel/vhnc/saveAll") + suspend fun saveVHNCData(@Body userDataDTO: UserDataDTO): Response + + @POST("flw-api/forms/villageLevel/phc/saveAll") + suspend fun savePHCData(@Body userDataDTO: UserDataDTO): Response + + @POST("flw-api/forms/villageLevel/ahd/saveAll") + suspend fun saveAHDData(@Body userDataDTO: UserDataDTO): Response + + @POST("flw-api/forms/villageLevel/deworming/saveAll") + suspend fun saveDewormingData(@Body userDataDTO: UserDataDTO): Response + + @Multipart + @POST("flw-api/campaign/ors/distribution/saveAll") + suspend fun saveORSCampaignData( + @Part campaignData: List + ): Response + + @POST("flw-api/campaign/ors/distribution/getAll") + suspend fun getORSCampaignData(): Response + + @Multipart + @POST("flw-api/campaign/polio/campaign/saveAll") + suspend fun savePulsePolioCampaignData( + @Part campaignData: List + ): Response + + @POST("flw-api/campaign/polio/campaign/getAll") + suspend fun getPulsePolioCampaignData(): Response + + @POST("flw-api/highRisk/pregnant/assess/saveAll") // @POST("highRisk/pregnant/assess/saveAll") suspend fun saveHRPAssessData(@Body userDataDTO: UserDataDTO): Response - @POST("flw-0.0.1/highRisk/assess/getAll") + @POST("flw-api/highRisk/assess/getAll") // @POST("highRisk/pregnant/assess/getAll") suspend fun getHighRiskAssessData(@Body userDetail: GetDataPaginatedRequest): Response - @POST("flw-0.0.1/highRisk/assess/saveAll") + @GET("flw-api/micro-birthPlan/getAll") + suspend fun getMicroBirthPlanAssessData(@Query("userId") userId: Int): Response + + @POST("flw-api/micro-birthPlan/saveAll") + suspend fun saveMicroBirthPlanAssessData(@Body userDataDTO: UserDataDTO): Response + + + @POST("flw-api/highRisk/assess/saveAll") // @POST("highRisk/pregnant/assess/saveAll") suspend fun saveHighRiskAssessData(@Body userDataDTO: UserDataDTO): Response - @POST("flw-0.0.1/highRisk/pregnant/track/getAll") + @POST("flw-api/highRisk/pregnant/track/getAll") // @POST("highRisk/pregnant/track/getAll") suspend fun getHRPTrackData(@Body userDetail: GetDataPaginatedRequest): Response - @POST("flw-0.0.1/highRisk/pregnant/track/saveAll") + @POST("flw-api/highRisk/pregnant/track/saveAll") // @POST("highRisk/pregnant/track/saveAll") suspend fun saveHRPTrackData(@Body userDataDTO: UserDataDTO): Response - @POST("flw-0.0.1/highRisk/nonPregnant/assess/getAll") + @POST("flw-api/highRisk/nonPregnant/assess/getAll") // @POST("highRisk/nonPregnant/assess/getAll") suspend fun getHRNonPAssessData(@Body userDetail: GetDataPaginatedRequest): Response - @POST("flw-0.0.1/highRisk/nonPregnant/assess/saveAll") + @POST("flw-api/highRisk/nonPregnant/assess/saveAll") // @POST("highRisk/nonPregnant/assess/saveAll") suspend fun saveHRNonPAssessData(@Body userDataDTO: UserDataDTO): Response - @POST("flw-0.0.1/highRisk/nonPregnant/track/getAll") + @POST("flw-api/highRisk/nonPregnant/track/getAll") // @POST("highRisk/nonPregnant/track/getAll") suspend fun getHRNonPTrackData(@Body userDetail: GetDataPaginatedRequest): Response - @POST("flw-0.0.1/highRisk/nonPregnant/track/saveAll") + @POST("flw-api/highRisk/nonPregnant/track/saveAll") // @POST("highRisk/nonPregnant/track/saveAll") suspend fun saveHRNonPTrackData(@Body userDataDTO: UserDataDTO): Response - @POST("identity-0.0.1/id/getByBenId") + @POST("identity-api/id/getByBenId") suspend fun getBeneficiaryWithId(@Query("benId") benId: Long): Response - @POST("fhirapi-v1.0/healthIDWithUID/createHealthIDWithUID") + @POST("fhir-api/healthIDWithUID/createHealthIDWithUID") suspend fun createHid(@Body createHealthIdRequest: CreateHealthIdRequest): Response - @POST("fhirapi-v1.0/healthID/getBenhealthID") + @POST("fhir-api/healthID/getBenhealthID") suspend fun getBenHealthID(@Body getBenHealthIdRequest: GetBenHealthIdRequest): Response - @POST("fhirapi-v1.0/healthID/mapHealthIDToBeneficiary") + //@POST("fhir-api/healthID/mapHealthIDToBeneficiary") + @POST("fhir-api/healthIDRecord/mapHealthIDToBeneficiary") suspend fun mapHealthIDToBeneficiary(@Body mapHIDtoBeneficiary: MapHIDtoBeneficiary): Response - @POST("fhirapi-v1.0/healthIDCard/generateOTP") + @POST("fhir-api/healthIDRecord/addHealthIdRecord") + suspend fun addHealthIdRecord(@Body addHealthIdRecord: AddHealthIdRecord): Response + + @POST("fhir-api/healthIDCard/generateOTP") suspend fun generateOtpHealthId(@Body generateOtpHid: GenerateOtpHid): Response - @POST("fhirapi-v1.0/healthIDCard/verifyOTPAndGenerateHealthCard") + @POST("fhir-api/healthIDCard/verifyOTPAndGenerateHealthCard") suspend fun verifyOtpAndGenerateHealthCard(@Body validateOtpHid: ValidateOtpHid): Response - @POST("/flw-0.0.1/couple/register/saveAll") + @POST("flw-api/couple/register/saveAll") suspend fun postEcrForm(@Body ecrPostList: List): Response - @POST("/flw-0.0.1/couple/tracking/saveAll") + @POST("flw-api/couple/tracking/saveAll") suspend fun postEctForm(@Body ectPostList: List): Response - @POST("/flw-0.0.1/couple/register/getAll") + @POST("flw-api/couple/register/getAll") suspend fun getEcrFormData(@Body userDetail: GetDataPaginatedRequest): Response - @POST("/flw-0.0.1/couple/tracking/getAll") + @POST("flw-api/couple/tracking/getAll") suspend fun getEctFormData(@Body userDetail: GetDataPaginatedRequest): Response - @POST("/flw-0.0.1/maternalCare/deliveryOutcome/saveAll") + @POST("flw-api/maternalCare/deliveryOutcome/saveAll") suspend fun postDeliveryOutcomeForm( @Body deliveryOutcomeList: List, /*@Url url : String ="http://192.168.1.105:8081/maternalCare/deliveryOutcome/saveAll"*/ ): Response - @POST("/flw-0.0.1/maternalCare/deliveryOutcome/getAll") + @POST("flw-api/maternalCare/deliveryOutcome/getAll") suspend fun getDeliveryOutcomeData(@Body userDetail: GetDataPaginatedRequest): Response - @POST("/flw-0.0.1/maternalCare/ancVisit/saveAll") + @POST("flw-api/maternalCare/ancVisit/saveAll") suspend fun postAncForm(@Body ancPostList: List): Response - @POST("/flw-0.0.1/maternalCare/ancVisit/getAll") + @POST("flw-api/maternalCare/ancVisit/getAll") suspend fun getAncVisitsData(@Body userDetail: GetDataPaginatedRequest): Response - @POST("/flw-0.0.1/maternalCare/pregnantWoman/saveAll") + @POST("flw-api/maternalCare/pregnantWoman/saveAll") suspend fun postPwrForm(@Body pwrPostList: List): Response - @POST("/flw-0.0.1/maternalCare/pregnantWoman/getAll") + @POST("flw-api/maternalCare/pregnantWoman/getAll") suspend fun getPwrData(@Body userDetail: GetDataPaginatedRequest): Response - @POST("/flw-0.0.1/maternalCare/pmsma/saveAll") + @POST("flw-api/maternalCare/pmsma/saveAll") suspend fun postPmsmaForm(@Body pmsmaPostList: List): Response - @POST("/flw-0.0.1/maternalCare/pmsma/getAll") + @POST("flw-api/maternalCare/pmsma/getAll") suspend fun getPmsmaData(@Body userDetail: GetDataPaginatedRequest): Response - @POST("/flw-0.0.1/maternalCare/infant/saveAll") + @POST("flw-api/maternalCare/infant/saveAll") suspend fun postInfantRegForm(@Body deliveryOutcomeList: List): Response - @POST("/flw-0.0.1/maternalCare/infant/getAll") + @POST("flw-api/maternalCare/infant/getAll") suspend fun getInfantRegData(@Body userDetail: GetDataPaginatedRequest): Response - @POST("/flw-0.0.1/child-care/vaccination/saveAll") + @POST("flw-api/child-care/vaccination/saveAll") suspend fun postChildImmunizationDetails(@Body immunizationList: List): Response - @POST("/flw-0.0.1/child-care/vaccination/getAll") + @POST("flw-api/child-care/vaccination/getAll") suspend fun getChildImmunizationDetails(@Body userDetail: GetDataPaginatedRequest): Response - @POST("/flw-0.0.1/maternalCare/pnc/saveAll") + @POST("flw-api/maternalCare/pnc/saveAll") suspend fun postPncForm(@Body ancPostList: List): Response - @POST("/flw-0.0.1/maternalCare/pnc/getAll") + @POST("flw-api/maternalCare/pnc/getAll") suspend fun getPncVisitsData(@Body userDetail: GetDataPaginatedRequest): Response - @GET("/flw-0.0.1/child-care/vaccine/getAll") + @GET("flw-api/child-care/vaccine/getAll") suspend fun getAllChildVaccines(@Query("category") category: String): Response - @POST("/flw-0.0.1/death-reports/mdsr/saveAll") + @POST("flw-api/death-reports/mdsr/saveAll") suspend fun postMdsrForm(@Body mdsrPostList: List): Response - @POST("/flw-0.0.1/death-reports/mdsr/getAll") + @POST("flw-api/death-reports/mdsr/getAll") suspend fun getMdsrData(@Body userDetail: GetDataPaginatedRequest): Response - @POST("/flw-0.0.1/death-reports/cdr/saveAll") + @POST("flw-api/death-reports/cdr/saveAll") suspend fun postCdrForm(@Body cdrPostList: List): Response - @POST("/flw-0.0.1/death-reports/cdr/getAll") + @POST("flw-api/death-reports/cdr/getAll") suspend fun getCdrData(@Body userDetail: GetDataPaginatedRequest): Response - @POST("/flw-0.0.1/incentive/masterData/getAll") + @POST("flw-api/incentive/masterData/getAll") suspend fun getAllIncentiveActivities(@Body requestBody: IncentiveActivityListRequest): Response - @POST("/flw-0.0.1/incentive/fetchUserData") + @GET("/flw-api/asha/getProfile") + suspend fun getAshaProfileData(@Query("employeeId") userId: Int): Response + + @POST("flw-api/incentive/fetchUserData") suspend fun getAllIncentiveRecords(@Body requestBody: IncentiveRecordListRequest): Response - @POST("/flw-0.0.1/child-care/hbncVisit/getAll") + @POST("flw-api/child-care/hbncVisit/getAll") suspend fun getHBNCDetailsFromServer(@Body getDataPaginatedRequest: GetDataPaginatedRequest): Response - @POST("/flw-0.0.1/child-care/hbncVisit/saveAll") + @POST("flw-api/child-care/hbncVisit/saveAll") suspend fun pushHBNCDetailsToServer(@Body hbncPostList: List): Response - @POST("/flw-0.0.1/child-care/hbyc/getAll") + @POST("flw-api/child-care/hbyc/getAll") suspend fun getHBYCFromServer(@Body getDataPaginatedRequest: GetDataPaginatedRequest): Response - @POST("/flw-0.0.1/child-care/hbyc/saveAll") + @POST("flw-api/child-care/hbyc/saveAll") suspend fun pushHBYCToServer(@Body hbncPostList: List): Response + @GET("common-api/dynamicForm/form/{formId}/fields") + suspend fun fetchFormSchema( + @Path("formId") formId: String, + @Query("lang") lang: String + ): Response> + + + @POST("flw-api/child-care/hbncVisit/saveAll") + suspend fun submitForm( + @Body request: List + ): Response + + @POST("flw-api/child-care/{formName}/saveAll") + suspend fun submitChildCareForm( + @Path("formName") formName: String, + @Body request: List + ): Response + + @POST("flw-api/disease/cdtfVisit/saveAll") + suspend fun submitNCDFollowUp( + @Body request: List + ): Response + + @POST("flw-api/disease/cdtfVisit/getAll") + suspend fun getAllFormNCDFollowUp( + @Body request: HBNCVisitRequest + ): Response + + @POST("flw-api/beneficiary/{formName}/saveAll") + suspend fun submitEyeSurgeryForm( + @Path("formName") formName: String, + @Body request: List + ): Response + + @POST("flw-api/disease/{formName}/saveAll") + suspend fun submitDiseaseMosquitoForm( + @Path("formName") formName: String, + @Body request: List + ): Response + + + @POST("flw-api/child-care/hbncVisit/getAll") + suspend fun getAllHbncVisits( + @Body request: HBNCVisitRequest + ): Response + + @POST("flw-api/child-care/{formName}/getAll") + suspend fun getAllFormVisits( + @Path("formName") formName: String, + @Body request: HBNCVisitRequest + ): Response + + @POST("flw-api/beneficiary/{formName}/getAll") + suspend fun getAllEyeSurgeryFormVisits( + @Path("formName") formName: String, + @Body request: HBNCVisitRequest + ): Response + + @POST("flw-api/disease/{formName}/getAll") + suspend fun getAllDiseaseMosquitoFormVisits( + @Path("formName") formName: String, + @Body request: HBNCVisitRequest + ): Response + + @POST("flw-api/child-care/hbycVisit/saveAll") + suspend fun submitFormhbyc( + @Body request: List + ): Response + + @POST("flw-api/maternalCare/ancVisit/counselling/saveAll") + suspend fun submitFromANC( + @Body request: List + ): Response + + + @POST("flw-api/child-care/hbycVisit/getAll") + suspend fun getAllHbycVisits( + @Body request: HBNCVisitRequest + ): Response + + @POST("flw-api/maternalCare/ancVisit/counselling/getAll") + suspend fun getAllAncVisits( + @Body request: HBNCVisitRequest + ): Response + + @Multipart + @POST("flw-api/uwin/session/saveAll") + suspend fun saveUwinSession( + @Part("meetingDate") meetingDate: RequestBody, + @Part("place") place: RequestBody, + @Part("participants") participants: RequestBody, + @Part("ashaId") ashaId: RequestBody, + @Part("createdBy") createdBy: RequestBody, + @Part images: List? = null + ): Response + + @POST("flw-api/uwin/session/getAll") + suspend fun getAllUwinSessions( + @Body request: UwinGetAllRequest + ): Response + + @POST("flw-api/campaign/filariasis/campaign/getAll") + suspend fun getFilariaMdaCampaign(): Response + + @Multipart + @POST("flw-api/campaign/filariasis/campaign/saveAll") + suspend fun saveFilariaMdaCampaign( @Part campaignData: List): Response + + @Multipart + @POST("flw-api/incentive/update") + suspend fun uploadIncentiveDocuments( + @Part("id") id: RequestBody, + @Part("userId") userId: RequestBody, + @Part("moduleName") moduleName: RequestBody, + @Part("activityName") activityName : RequestBody, + @Part images: List + + + ): Response + + } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/network/JsonAdapters.kt b/app/src/main/java/org/piramalswasthya/sakhi/network/JsonAdapters.kt index a3b92c57b..5491dd8fe 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/network/JsonAdapters.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/network/JsonAdapters.kt @@ -1,13 +1,37 @@ package org.piramalswasthya.sakhi.network +import android.os.Parcelable +import com.google.gson.annotations.SerializedName import com.squareup.moshi.JsonClass +import kotlinx.parcelize.Parcelize import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.model.ABHAModel +import org.piramalswasthya.sakhi.model.AESScreeningCache +import org.piramalswasthya.sakhi.model.AHDCache +import org.piramalswasthya.sakhi.model.AdolescentHealthCache +import org.piramalswasthya.sakhi.model.DewormingCache +import org.piramalswasthya.sakhi.model.FilariaScreeningCache +import org.piramalswasthya.sakhi.model.HRPMicroBirthPlanCache import org.piramalswasthya.sakhi.model.HRPNonPregnantAssessCache import org.piramalswasthya.sakhi.model.HRPNonPregnantTrackCache import org.piramalswasthya.sakhi.model.HRPPregnantAssessCache import org.piramalswasthya.sakhi.model.HRPPregnantTrackCache +import org.piramalswasthya.sakhi.model.IRSRoundScreening +import org.piramalswasthya.sakhi.model.KalaAzarScreeningCache +import org.piramalswasthya.sakhi.model.LeprosyFollowUpCache +import org.piramalswasthya.sakhi.model.LeprosyScreeningCache +import org.piramalswasthya.sakhi.model.MalariaConfirmedCasesCache +import org.piramalswasthya.sakhi.model.MalariaScreeningCache +import org.piramalswasthya.sakhi.model.PHCReviewMeetingCache +import org.piramalswasthya.sakhi.model.ReferalCache +import org.piramalswasthya.sakhi.model.TBConfirmedTreatmentCache import org.piramalswasthya.sakhi.model.TBScreeningCache import org.piramalswasthya.sakhi.model.TBSuspectedCache +import org.piramalswasthya.sakhi.model.VHNCCache +import org.piramalswasthya.sakhi.model.VHNDCache +import org.piramalswasthya.sakhi.model.getDateTimeStringFromLong +import org.piramalswasthya.sakhi.utils.KeyUtils +import timber.log.Timber import java.text.SimpleDateFormat import java.util.Locale @@ -45,6 +69,11 @@ data class TmcAuthUserRequest( val doLogout: Boolean = true ) +@JsonClass(generateAdapter = true) +data class TmcRefreshTokenRequest( + val refreshToken: String +) + @JsonClass(generateAdapter = true) data class TmcUserDetailsRequest( val userID: Int @@ -77,6 +106,64 @@ data class GetDataPaginatedRequest( val toDate: String ) +@JsonClass(generateAdapter = true) +data class GetCBACRequest( + val createdBy: String, +) + + +@JsonClass(generateAdapter = true) +data class GetDataPaginatedRequestForGeneralOPD( + val userId: Int, + val villageID: Int, + val userName: String, + val ashaId: Int, + val pageNo: Int, + val fromDate: String, + val toDate: String +) + + +@JsonClass(generateAdapter = true) +data class GetVHNDRequest( + val formType: String, + val userId: Int, + +) + +@JsonClass(generateAdapter = true) + +data class GetDataPaginatedRequestForDisease( + val ashaId: Int, + val pageNo: Int, + val fromDate: String, + val toDate: String, + val diseaseTypeID: Int, + val userName : String? = null + +) + +data class ValidateOtpRequest( + val otp: Int, + val mobNo: String, +) + + +data class sendOtpRequest( + val mobNo: String, +) + +@JsonClass(generateAdapter = true) +data class GetDataRequest( + val villageID: Int, + val fromDate: String, + val toDate: String, + val pageNo: Int, + val userId: Long, + val userName: String, + val ashaId: Long +) + @JsonClass(generateAdapter = true) data class BenResponse( val benId: String, @@ -90,7 +177,8 @@ data class BenHealthDetails( val benHealthID: Int, val healthIdNumber: String, val beneficiaryRegID: Long, - val healthId: String + val healthId: String, + val isNewAbha: Boolean ) @JsonClass(generateAdapter = true) @@ -105,11 +193,9 @@ data class BenAbhaResponse( @JsonClass(generateAdapter = true) data class AbhaTokenRequest( - val clientId: String = "SBX_001542", - val clientSecret: String = "87b7eb89-b236-43b6-82b0-6eef154a9b90", + val clientId: String = KeyUtils.abhaClientID(), + val clientSecret: String = KeyUtils.abhaClientSecret(), val grantType: String = "client_credentials" -// val clientId: String = "healthid-api", -// val clientSecret: String = "9042c774-f57b-46ba-bb11-796a4345ada1" ) @JsonClass(generateAdapter = true) @@ -121,9 +207,21 @@ data class AbhaTokenResponse( val tokenType: String ) +// ABHA v1/v2 request +//@JsonClass(generateAdapter = true) +//data class AbhaGenerateAadhaarOtpRequest( +// var aadhaar: String +//) + +// ABHA v3 request @JsonClass(generateAdapter = true) data class AbhaGenerateAadhaarOtpRequest( - var aadhaar: String +// var aadhaar: String + val txnId: String, + val scope: List, + val loginHint: String, + var loginId: String, + var otpSystem: String ) @JsonClass(generateAdapter = true) @@ -141,7 +239,34 @@ data class AbhaGenerateAadhaarOtpResponse( @JsonClass(generateAdapter = true) data class AbhaGenerateAadhaarOtpResponseV2( val txnId: String, - val mobileNumber: String + val mobileNumber: String, + val message:String +) + +@JsonClass(generateAdapter = true) +data class SendOtpResponse( + val data: Data, + val statusCode: Long, + val errorMessage: String, + val status: String, +) + +data class Data( + val response: String, +) + +@JsonClass(generateAdapter = true) + +data class ValidateOtpResponse( + val data: ResponseOtp, + val statusCode: Long, + val errorMessage: String, + val status: String, +) + +data class ResponseOtp( + val userName: String, + val userId: String, ) @JsonClass(generateAdapter = true) @@ -149,19 +274,163 @@ data class AbhaResendAadhaarOtpRequest( val txnId: String ) +// ABHA v1/v2 request +//@JsonClass(generateAdapter = true) +//data class AbhaVerifyAadhaarOtpRequest( +// val otp: String, +// val txnId: String +//) +// ABHA v3 request @JsonClass(generateAdapter = true) data class AbhaVerifyAadhaarOtpRequest( - val otp: String, - val txnId: String + val authData: AuthData, + val consent: Consent +) + +@JsonClass(generateAdapter = true) +data class SearchAbhaRequest( + val scope: List, + var mobile: String ) +@JsonClass(generateAdapter = true) +data class SearchAbhaResponse( + val txnId: String, + val ABHA: List +) @JsonClass(generateAdapter = true) -data class AbhaVerifyAadhaarOtpResponse( +data class Abha( + val index: Int, + val ABHANumber: String, + val name: String, + val gender: String +) + +@JsonClass(generateAdapter = true) +data class LoginGenerateOtpRequest( + val scope: List, + val loginHint: String, + var loginId: String, + val otpSystem: String, val txnId: String ) +@JsonClass(generateAdapter = true) +data class LoginGenerateOtpResponse( + val txnId: String, + val message: String +) + +@JsonClass(generateAdapter = true) +data class LoginVerifyOtpRequest( + val scope: List, + val authData: AuthData3 +) + +@JsonClass(generateAdapter = true) +data class AuthData3( + val authMethods: List, + val otp: Otp3 +) + +@JsonClass(generateAdapter = true) +data class Otp3( + val txnId: String, + var otpValue: String +) + +@JsonClass(generateAdapter = true) +data class LoginVerifyOtpResponse( + val txnId: String, + val authResult: String, + val message: String, + val token: String, + val expiresIn: Long, + val refreshToken: String, + val refreshExpiresIn: Long, + val accounts: List +) + +@JsonClass(generateAdapter = true) +data class Accounts( + val ABHANumber: String, + val preferredAbhaAddress: String, + val name: String, + val status: String, + val profilePhoto: String, + val mobileVerified: Boolean +) + +@JsonClass(generateAdapter = true) +data class AuthData( + val authMethods: List, + val otp: Otp +) + +@JsonClass(generateAdapter = true) +data class Consent( + val code: String, + val version: String +) + +@JsonClass(generateAdapter = true) +data class Otp( + var timeStamp: String, + val txnId: String, + var otpValue: String, + var mobile: String +) + +// ABHA v1/v2 request +//@JsonClass(generateAdapter = true) +//data class AbhaVerifyAadhaarOtpResponse( +// val txnId: String +//) + +// ABHA v3 request +@Parcelize +@JsonClass(generateAdapter = true) +data class AbhaVerifyAadhaarOtpResponse( + val message: String="", + val txnId: String="", + val tokens: Tokens = Tokens(), + val ABHAProfile: ABHAProfile=ABHAProfile(), + val isNew: Boolean=false +) : Parcelable + +@Parcelize +@JsonClass(generateAdapter = true) +data class Tokens( + val token: String="", + val expiresIn: Int=0, + val refreshToken: String="", + val refreshExpiresIn: Int=0 +) : Parcelable + +@Parcelize +@JsonClass(generateAdapter = true) +data class ABHAProfile( + val firstName: String="", + val middleName: String="", + val lastName: String="", + val dob: String="", + val gender: String="", + val photo: String="", + val mobile: String?="", + val email: String?="", + val phrAddress:List?= listOf(), + val address: String="", + val districtCode: String="", + val stateCode: String="", + val pinCode: String="", + val abhaType: String="", + val stateName: String="", + val districtName: String="", + val ABHANumber: String="", + val abhaStatus: String="" +) : Parcelable @JsonClass(generateAdapter = true) data class AbhaGenerateMobileOtpRequest( @@ -180,10 +449,31 @@ data class AbhaCheckAndGenerateMobileOtpResponse( ) +// ABHA v1/v2 request +//@JsonClass(generateAdapter = true) +//data class AbhaVerifyMobileOtpRequest( +// val otp: String, +// val txnId: String +//) + +// ABHA v3 request @JsonClass(generateAdapter = true) data class AbhaVerifyMobileOtpRequest( - val otp: String, - val txnId: String + val scope: List, + val authData: AuthData2 +) + +@JsonClass(generateAdapter = true) +data class AuthData2( + val authMethods: List, + val otp: Otp2 +) + +@JsonClass(generateAdapter = true) +data class Otp2( + var timeStamp: String, + val txnId: String, + var otpValue: String ) @@ -192,6 +482,12 @@ data class AbhaVerifyMobileOtpResponse( val txnId: String ) +@JsonClass(generateAdapter = true) +data class AbhaPublicCertificateResponse( + val publicKey: String, + val encryptionAlgorithm: String +) + @JsonClass(generateAdapter = true) data class StateCodeResponse( val code: String, @@ -332,14 +628,30 @@ data class CreateHealthIdRequest( val providerServiceMapID: Int?, val createdBy: String? ) - +@JsonClass(generateAdapter = true) data class MapHIDtoBeneficiary( val beneficiaryRegID: Long?, val beneficiaryID: Long?, val healthId: String?, val healthIdNumber: String?, var providerServiceMapId: Int?, - var createdBy: String? + var createdBy: String?, + var message: String?, + var txnId: String?, + var ABHAProfile: ABHAProfile?, + var isNew: Boolean? +) + +@JsonClass(generateAdapter = true) +data class AddHealthIdRecord( + val healthId: String?, + val healthIdNumber: String?, + var providerServiceMapId: Int?, + var createdBy: String?, + var message: String?, + var txnId: String?, + var ABHAProfile: ABHAProfile?, + var isNew: Boolean? ) data class TBScreeningRequestDTO( @@ -347,6 +659,40 @@ data class TBScreeningRequestDTO( val tbScreeningList: List ) +data class KalaAzarScreeningRequestDTO( + val userId: Int, + val kalaAzarLists: List +) + +data class MalariaScreeningRequestDTO( + val userId: Int, + val malariaLists: List +) + +data class IRSScreeningRequestDTO( + val rounds: List +) + +data class LeprosyScreeningRequestDTO( + val userId: Int, + val leprosyLists: List +) + +data class AESScreeningRequestDTO( + val userId: Int, + val aesJeLists: List +) + +data class FilariaScreeningRequestDTO( + val userId: Int, + val filariaLists: List +) + +data class AdolescentHealthRequestDTO( + val userId: Int, + val adolescentHealths: List +) + data class UserDataDTO( val userId: Int, val entries: List @@ -450,6 +796,56 @@ data class HRPPregnantAssessDTO( } } +@JsonClass(generateAdapter = true) +data class HRPMicroBirthPlanDTO( + val id: Int = 0, + val benId: Long, + var nearestSc: String? = null, + var bloodGroup: String? = null, + var contactNumber1: String? = null, + var contactNumber2: String? = null, + var scHosp: String? = null, + var usg: String? = null, + var block: String? = null, + var nearestPhc: String? = null, + var nearestFru: String? = null, + var bloodDonors1: String? = null, + var bloodDonors2: String? = null, + var birthCompanion: String? = null, + var careTaker: String? = null, + var communityMember: String? = null, + var communityMemberContact: String? = null, + var modeOfTransportation: String? = null, +) { + fun toCache(): HRPMicroBirthPlanCache { + return HRPMicroBirthPlanCache( + id = 0, + benId = benId, + nearestSc = nearestSc, + bloodGroup = bloodGroup, + contactNumber1 = contactNumber1, + contactNumber2 = contactNumber2, + scHosp = scHosp, + usg = usg, + block = block, + nearestPhc = nearestPhc, + nearestFru = nearestFru, + bloodDonors1 = bloodDonors1, + bloodDonors2 = bloodDonors2, + birthCompanion = birthCompanion, + careTaker = careTaker, + communityMember = communityMember, + communityMemberContact = communityMemberContact, + modeOfTransportation = modeOfTransportation, + processed = "P", + syncState = SyncState.SYNCED + ) + } +} + + + + data class HRPNonPregnantTrackDTO( var id: Int = 0, val benId: Long, @@ -530,6 +926,160 @@ data class HRPNonPregnantAssessDTO( } } +data class VHNDDTO( + val id: Int = 0, + var vhndDate: String?, + var place: String? = null, + var noOfBeneficiariesAttended: Int? = null, + var Image1: String? = null, + var Image2: String? = null, + var vhndPlaceId: Int? = 0, + var pregnantWomenAnc: String? = null, + var lactatingMothersPnc: String? = null, + var childrenImmunization: String? = null, + var knowledgeBalancedDiet: String? = null, + var careDuringPregnancy: String? = null, + var importanceBreastfeeding: String? = null, + var complementaryFeeding: String? = null, + var hygieneSanitation: String? = null, + var familyPlanningHealthcare: String? = null, + var selectAllEducation: Boolean? = false, +) { + fun toCache(): VHNDCache { + return VHNDCache( + id = 0, + vhndDate = vhndDate!!, + place = place, + noOfBeneficiariesAttended = noOfBeneficiariesAttended, + image1 = Image1, + image2 = Image2, + vhndPlaceId = vhndPlaceId, + pregnantWomenAnc = pregnantWomenAnc, + lactatingMothersPnc = lactatingMothersPnc, + childrenImmunization = childrenImmunization, + knowledgeBalancedDiet = knowledgeBalancedDiet, + careDuringPregnancy = careDuringPregnancy, + importanceBreastfeeding = importanceBreastfeeding, + complementaryFeeding = complementaryFeeding, + hygieneSanitation = hygieneSanitation, + familyPlanningHealthcare = familyPlanningHealthcare, + syncState = SyncState.SYNCED + ) + } +} + +data class VHNCDTO( + val id: Int = 0, + var vhncDate: String?, + var place: String? = null, + var noOfBeneficiariesAttended: Int? = null, + var Image1: String? = null, + var Image2: String? = null, + var villageName: String? = null, + var anm: Int? = 0, + var aww: Int? = 0, + var noOfPragnentWoment: Int? = 0, + var noOfLactingMother: Int? = 0, + var noOfCommittee: Int? = 0, + var followupPrevius: Boolean? = null, +) { + fun toCache(): VHNCCache { + return VHNCCache( + id = 0, + vhncDate = vhncDate!!, + place = place, + noOfBeneficiariesAttended = noOfBeneficiariesAttended, + image1 = Image1, + image2 = Image2, + villageName = villageName, + anm = anm, + aww = aww, + noOfPragnentWoment = noOfPragnentWoment, + noOfLactingMother = noOfLactingMother, + noOfCommittee = noOfCommittee, + followupPrevius = followupPrevius, + syncState = SyncState.SYNCED + ) + } +} + +data class PHCReviewDTO( + val id: Int = 0, + var placeId : Int? = 0 , + var phcReviewDate: String?, + var place: String? = null, + var noOfBeneficiariesAttended: Int? = null, + var Image1: String? = null, + var Image2: String? = null, + var villageName: String? = null, + var mitaninHistory: String? = null, + var mitaninActivityCheckList: String? = null, +) { + fun toCache(): PHCReviewMeetingCache { + return PHCReviewMeetingCache( + id = 0, + phcReviewDate = phcReviewDate!!, + place = place, + noOfBeneficiariesAttended = noOfBeneficiariesAttended, + image1 = Image1, + image2 = Image2, + syncState = SyncState.SYNCED, + villageName = villageName, + mitaninHistory = mitaninHistory, + mitaninActivityCheckList = mitaninActivityCheckList, + placeId = placeId + ) + } +} + +data class AHDDTO( + val id: Int = 0, + var mobilizedForAHD: String?, + var ahdPlace: String? = null, + var ahdDate: String? = null, + var image1: String? = null, + var image2: String? = null, +) { + fun toCache(): AHDCache { + return AHDCache( + id = 0, + mobilizedForAHD = mobilizedForAHD!!, + ahdPlace = ahdPlace, + ahdDate = ahdDate, + image1 = image1, + image2 = image2, + syncState = SyncState.SYNCED + ) + } +} + +data class DewormingDTO( + var id: Int = 0, + var dewormingDone: String? = null, + var dewormingDate: String? = null, + var dewormingLocation: String? = null, + var ageGroup: Int? = null, + var image1: String? = null, + var image2: String? = null, + var regDate: String? = null, +) { + fun toCache(): DewormingCache { + return DewormingCache( + id = id, + dewormingDone = dewormingDone, + dewormingDate = dewormingDate, + dewormingLocation = dewormingLocation, + ageGroup = ageGroup, + image1 = image1, + image2 = image2, + regDate = regDate, + syncState = SyncState.SYNCED + ) + } +} + + + data class TBScreeningDTO( val id: Long, val benId: Long, @@ -541,7 +1091,18 @@ data class TBScreeningDTO( var nightSweats: Boolean?, var historyOfTb: Boolean?, var takingAntiTBDrugs: Boolean?, - var familySufferingFromTB: Boolean? + var familySufferingFromTB: Boolean?, + var riseOfFever: Boolean? = null, + var lossOfAppetite: Boolean? = null, + var age: Boolean? = null, + var diabetic: Boolean? = null, + var tobaccoUser: Boolean? = null, + var bmi: Boolean? = null, + var contactWithTBPatient: Boolean? = null, + var historyOfTBInLastFiveYrs: Boolean? = null, + var sympotomatic :String?=null, + var asymptomatic :String?=null, + var recommandateTest :String?=null, ) { fun toCache(): TBScreeningCache { return TBScreeningCache( @@ -555,11 +1116,96 @@ data class TBScreeningDTO( historyOfTb = historyOfTb, takingAntiTBDrugs = takingAntiTBDrugs, familySufferingFromTB = familySufferingFromTB, + riseOfFever = riseOfFever, + lossOfAppetite = lossOfAppetite, + age = age, + diabetic = diabetic, + tobaccoUser = tobaccoUser, + bmi = bmi, + contactWithTBPatient = contactWithTBPatient, + historyOfTBInLastFiveYrs = historyOfTBInLastFiveYrs, + sympotomatic = sympotomatic, + asymptomatic = asymptomatic, + recommandateTest = recommandateTest, + syncState = SyncState.SYNCED + ) + } +} + +data class AdolscentHealthDTO( + var id :Int? = null, + var userID :Int? =null, + var benId:Long, + var visitDate: String, + var healthStatus: String? = null, + var ifaTabletDistributed: Boolean? = null, + var quantityOfIfaTablets: Int? = null, + var menstrualHygieneAwarenessGiven: Boolean? = null, + var sanitaryNapkinDistributed: Boolean? = null, + var noOfPacketsDistributed: Int? = null, + var place: String? = null, + var distributionDate: String? = null, + var referredToHealthFacility: String? = null, + var counselingProvided: Boolean? = null, + var counselingType: String? = null, + var followUpDate: String? = null, + var referralStatus: String? = null, +) { + fun toCache(): AdolescentHealthCache { + return AdolescentHealthCache( + benId = benId, + visitDate = getLongFromDate(visitDate), + healthStatus = healthStatus, + ifaTabletDistributed = ifaTabletDistributed, + quantityOfIfaTablets = quantityOfIfaTablets, + menstrualHygieneAwarenessGiven = menstrualHygieneAwarenessGiven, + sanitaryNapkinDistributed = sanitaryNapkinDistributed, + noOfPacketsDistributed = noOfPacketsDistributed, + place = place, + distributionDate = getLongFromDate(distributionDate), + referredToHealthFacility = referredToHealthFacility, + counselingProvided = counselingProvided, + counselingType = counselingType, + followUpDate = getLongFromDate(followUpDate), + referralStatus = referralStatus, syncState = SyncState.SYNCED ) } } + +data class ABHAGeneratedDTO( + val id: Int = 0, + val beneficiaryID: Long, + val beneficiaryRegID: Long, + val benName: String, + val createdBy: String, + val message: String, + val txnId: String, + val benSurname: String? = null, + var healthId: String = "", + var healthIdNumber: String = "", + var isNewAbha: Boolean= false, + val providerServiceMapId: Int, + +) { + fun toCache(): ABHAModel { + return ABHAModel( + beneficiaryID = beneficiaryID, + beneficiaryRegID = beneficiaryRegID, + benName = benName, + benSurname = benSurname, + healthId = healthId, + txnId = txnId, + message = message, + createdBy = createdBy, + healthIdNumber = healthIdNumber, + isNewAbha = isNewAbha, + providerServiceMapId = providerServiceMapId + ) + } +} + data class TBSuspectedDTO( val id: Long, val benId: Long, @@ -569,8 +1215,19 @@ data class TBSuspectedDTO( val nikshayId: String?, val sputumTestResult: String?, val referred: Boolean?, - val followUps: String? -) { + val followUps: String?, + var visitLabel: String?, + var typeOfTBCase: String? = null, + var reasonForSuspicion: String? = null, + var hasSymptoms: Boolean? = null, + var isChestXRayDone: Boolean? = null, + var chestXRayResult: String? = null, + var referralFacility: String? = null, + var isTBConfirmed: Boolean? = null, + var isDRTBConfirmed: Boolean? = null, + var isConfirmed: Boolean = false, + + ) { fun toCache(): TBSuspectedCache { return TBSuspectedCache( benId = benId, @@ -581,18 +1238,612 @@ data class TBSuspectedDTO( sputumTestResult = sputumTestResult, referred = referred, followUps = followUps, + visitLabel= visitLabel, + typeOfTBCase = typeOfTBCase, + reasonForSuspicion = reasonForSuspicion, + hasSymptoms = hasSymptoms ?: false, + isChestXRayDone = isChestXRayDone, + chestXRayResult = chestXRayResult, + referralFacility = referralFacility, + isTBConfirmed = isTBConfirmed, + isDRTBConfirmed = isDRTBConfirmed, + isConfirmed = isConfirmed, + syncState = SyncState.SYNCED + ) + } +} + +data class TBConfirmedTreatmentDTO( + val id: Long, + val benId: Long, + val regimenType: String?, + val treatmentStartDate: String?, + val expectedTreatmentCompletionDate: String?, + val followUpDate: String?, + val monthlyFollowUpDone: String?, + val adherenceToMedicines: String?, + val anyDiscomfort: Boolean?, + val treatmentCompleted: Boolean?, + val actualTreatmentCompletionDate: String?, + val treatmentOutcome: String?, + val dateOfDeath: String?, + val placeOfDeath: String?, + val reasonForDeath: String?, + val reasonForNotCompleting: String? +) { + + fun toCache(): TBConfirmedTreatmentCache { + return TBConfirmedTreatmentCache( + benId = benId, + regimenType = regimenType, + treatmentStartDate = getLongFromDateMultipleSupport(treatmentStartDate) ?: System.currentTimeMillis(), + expectedTreatmentCompletionDate = getLongFromDateMultipleSupport(expectedTreatmentCompletionDate), + followUpDate = getLongFromDateMultipleSupport(followUpDate), + monthlyFollowUpDone = monthlyFollowUpDone, + adherenceToMedicines = adherenceToMedicines, + anyDiscomfort = anyDiscomfort, + treatmentCompleted = treatmentCompleted, + actualTreatmentCompletionDate = getLongFromDateMultipleSupport(actualTreatmentCompletionDate), + treatmentOutcome = treatmentOutcome, + dateOfDeath = getLongFromDateMultipleSupport(dateOfDeath), + placeOfDeath = placeOfDeath, + reasonForDeath = reasonForDeath ?: "Tuberculosis", + reasonForNotCompleting = reasonForNotCompleting, syncState = SyncState.SYNCED ) } } +data class TBConfirmedRequestDTO( + @SerializedName("userId") + val userId: Int, + @SerializedName("tbConfirmedCases") + val tbConfirmedList: List +) + data class TBSuspectedRequestDTO( val userId: Int, val tbSuspectedList: List ) +data class MalariaConfirmedRequestDTO( + val userId: Int, + val malariaFollowListUp: List +) + + +data class MalariaScreeningDTO( + val id: Int = 0, + val benId: Long, + val visitId: Long, + val caseDate: String, + val houseHoldDetailsId: Long, + val screeningDate: String, + val beneficiaryStatus: String, + val beneficiaryStatusId: Int = 0, + val dateOfDeath: String, + val placeOfDeath: String, + val otherPlaceOfDeath: String, + val reasonForDeath: String, + val otherReasonForDeath: String, + val rapidDiagnosticTest: String, + val dateOfRdt: String, + val slideTestName: String, + val slideTestPf: String, + val slideTestPv: String, + val dateOfSlideTest: String, + val dateOfVisitBySupervisor: String, + var caseStatus: String ? = "", + var referredTo: Int ? = 0, + var referToName: String ? = null, + var otherReferredFacility: String ? = null, + var remarks: String ? = null, + var diseaseTypeID: Int ? = 0, + var followUpDate: String, + var feverMoreThanTwoWeeks: Boolean ? = false, + var fluLikeIllness: Boolean ? = false, + var shakingChills: Boolean ? = false, + var headache: Boolean ? = false, + var muscleAches: Boolean ? = false, + var tiredness: Boolean ? = false, + var nausea: Boolean ? = false, + var vomiting: Boolean ? = false, + var diarrhea: Boolean ? = false, + var createdBy: String ? = "", + + var malariaTestType: Int? = 0, + + var malariaSlideTestType: Int? = 0, + +) { + fun toCache(): MalariaScreeningCache { + return MalariaScreeningCache( + benId = benId, + caseDate = getLongFromDate(caseDate), + caseStatus = caseStatus, + houseHoldDetailsId = houseHoldDetailsId, + referredTo = referredTo, + referToName = referToName.toString(), + otherReferredFacility = otherReferredFacility, + remarks = remarks, + followUpDate = getLongFromDate(followUpDate), + syncState = SyncState.SYNCED, + diseaseTypeID = diseaseTypeID, + feverMoreThanTwoWeeks = feverMoreThanTwoWeeks, + fluLikeIllness = fluLikeIllness, + shakingChills = shakingChills, + headache = headache, + muscleAches = muscleAches, + tiredness = tiredness, + nausea = nausea, + vomiting = vomiting, + diarrhea = diarrhea, + beneficiaryStatusId = beneficiaryStatusId, + beneficiaryStatus = beneficiaryStatus, + createdBy = createdBy, + screeningDate = getLongFromDate(screeningDate), + rapidDiagnosticTest = rapidDiagnosticTest, + slideTestName = slideTestName, + slideTestPf = slideTestPf, + slideTestPv = slideTestPv, + dateOfSlideTest = getLongFromDate(dateOfSlideTest), + dateOfRdt = getLongFromDate(dateOfRdt), + dateOfDeath = getLongFromDate(dateOfDeath), + dateOfVisitBySupervisor = getLongFromDate(dateOfVisitBySupervisor), + reasonForDeath = reasonForDeath, + otherReasonForDeath = otherReasonForDeath, + otherPlaceOfDeath = otherPlaceOfDeath, + placeOfDeath = placeOfDeath, + visitId = visitId, + malariaTestType = malariaTestType, + malariaSlideTestType = malariaSlideTestType + + + ) + } +} + +data class MalariaConfirmedDTO( + val id: Int = 0, + val diseaseId: Int = 0, + val benId: Long, + val houseHoldDetailsId: Long, + var dateOfDiagnosis: String, + var treatmentStartDate: String, + var treatmentCompletionDate: String, + var treatmentGiven: String, + var referralDate: String, + var day: String, +) { + fun toCache(): MalariaConfirmedCasesCache { + return MalariaConfirmedCasesCache( + benId = benId, + dateOfDiagnosis = getLongFromDate(dateOfDiagnosis), + treatmentStartDate = getLongFromDate(treatmentStartDate), + treatmentCompletionDate = getLongFromDate(treatmentCompletionDate), + referralDate = getLongFromDate(referralDate), + treatmentGiven = treatmentGiven, + houseHoldDetailsId = houseHoldDetailsId, + diseaseId = diseaseId, + day = day, + ) + } +} + +data class AESScreeningDTO( + val id: Int = 0, + val benId: Long, + var visitDate: String, + val houseHoldDetailsId: Long, + var beneficiaryStatus: String ? = null, + var beneficiaryStatusId: Int = 0, + var dateOfDeath: String, + var placeOfDeath: String ? = null, + var otherPlaceOfDeath: String ? = null, + var reasonForDeath: String ? = null, + var otherReasonForDeath: String ? = null, + var aesJeCaseStatus: String ? = "", + var referredTo: Int ? = 0, + var referToName: String ? = null, + var otherReferredFacility: String ? = null, + var diseaseTypeID: Int ? = 0, + var createdDate: String, + var createdBy: String ? = null, + var followUpPoint: Int ? = 1, + var syncState: SyncState = SyncState.UNSYNCED, + + ) { + fun toCache(): AESScreeningCache { + return AESScreeningCache( + benId = benId, + visitDate = getLongFromDate(visitDate), + aesJeCaseStatus = aesJeCaseStatus, + houseHoldDetailsId = houseHoldDetailsId, + referredTo = referredTo, + referToName = referToName.toString(), + otherReferredFacility = otherReferredFacility, + createdDate = getLongFromDate(createdDate), + syncState = SyncState.SYNCED, + diseaseTypeID = diseaseTypeID, + beneficiaryStatusId = beneficiaryStatusId, + beneficiaryStatus = beneficiaryStatus, + createdBy = createdBy, + dateOfDeath = getLongFromDate(dateOfDeath), + reasonForDeath = reasonForDeath, + otherReasonForDeath = otherReasonForDeath, + otherPlaceOfDeath = otherPlaceOfDeath, + placeOfDeath = placeOfDeath, + followUpPoint = followUpPoint + + + ) + } +} + + +data class NCDReferalDTO( + val id: Int = 0, + val benId: Long, + val referredToInstituteID: Int?, + val refrredToAdditionalServiceList: List?, + val referredToInstituteName: String?, + val referralReason: String?, + val revisitDate: String, + val vanID: Int?, + val parkingPlaceID: Int?, + val beneficiaryRegID: Long?, + val benVisitID: Long?, + val visitCode: Long?, + val providerServiceMapID: Int?, + val createdBy: String?, + var type: String?, + + val isSpecialist: Boolean? = false, + var syncState: SyncState = SyncState.UNSYNCED, + + ) { + fun toCache(): ReferalCache { + return ReferalCache( + id = 0, + benId = benId, + revisitDate = getLongFromDate(revisitDate), + referredToInstituteID = referredToInstituteID, + refrredToAdditionalServiceList = refrredToAdditionalServiceList, + referredToInstituteName = referredToInstituteName, + visitCode = visitCode, + benVisitID = benVisitID, + createdBy = createdBy, + isSpecialist = false, + vanID = vanID, + providerServiceMapID = providerServiceMapID, + beneficiaryRegID = beneficiaryRegID, + referralReason = referralReason, + parkingPlaceID = parkingPlaceID, + syncState = SyncState.SYNCED, + type = type + + + + + ) + } +} + + + +data class LeprosyScreeningDTO( + val id: Int = 0, + val benId: Long, + val homeVisitDate: String, + val leprosyStatusDate: String, + val dateOfDeath: String, + val houseHoldDetailsId: Long, + var leprosyStatus: String? = "", + var referredTo: Int? = 0, + var referToName: String? = null, + var otherReferredTo: String? = null, + var typeOfLeprosy: String? = null, + var remarks: String? = null, + var beneficiaryStatus: String? = null, + var placeOfDeath: String? = null, + var otherPlaceOfDeath: String? = null, + var reasonForDeath: String? = null, + var otherReasonForDeath: String? = null, + var diseaseTypeID: Int? = 0, + var beneficiaryStatusId: Int? = 0, + var leprosySymptoms: String? = null, + var leprosySymptomsPosition: Int? = 1, + var lerosyStatusPosition: Int? = 0, + var currentVisitNumber: Int = 1, + var visitLabel: String? = "Visit -1", + var visitNumber: Int? = 1, + var isConfirmed: Boolean = false, + var leprosyState: String? = "Screening", + var treatmentStartDate: String = getDateTimeStringFromLong(System.currentTimeMillis()).toString(), + var totalFollowUpMonthsRequired: Int = 0, + var treatmentEndDate: String = getDateTimeStringFromLong(System.currentTimeMillis()).toString(), + var mdtBlisterPackRecived: String? = null, + var treatmentStatus: String? = null, + val createdBy: String, + val createdDate:String, + val modifiedBy: String, + val lastModDate: String, + var recurrentUlceration: String? = null, + var recurrentUlcerationId: Int? = 1, + var recurrentTingling: String? = null, + var recurrentTinglingId: Int? = 1, + var hypopigmentedPatch: String? = null, + var hypopigmentedPatchId: Int? = 1, + var thickenedSkin: String? = null, + var thickenedSkinId: Int? = 1, + var skinNodules: String? = null, + var skinNodulesId: Int? = 1, + var skinPatchDiscoloration: String? = null, + var skinPatchDiscolorationId: Int? = 1, + var recurrentNumbness: String? = null, + var recurrentNumbnessId: Int? = 1, + var clawingFingers: String? = null, + var clawingFingersId: Int? = 1, + var tinglingNumbnessExtremities: String? = null, + var tinglingNumbnessExtremitiesId: Int? = 1, + var inabilityCloseEyelid: String? = null, + var inabilityCloseEyelidId: Int? = 1, + var difficultyHoldingObjects: String? = null, + var difficultyHoldingObjectsId: Int? = 1, + var weaknessFeet: String? = null, + var weaknessFeetId: Int? = 1, +) { + fun toCache(): LeprosyScreeningCache { + return LeprosyScreeningCache( + benId = benId, + homeVisitDate = getLongFromDate(homeVisitDate), + leprosyStatusDate = getLongFromDate(leprosyStatusDate), + dateOfDeath = getLongFromDate(dateOfDeath), + houseHoldDetailsId = houseHoldDetailsId, + leprosyStatus = leprosyStatus, + referredTo = referredTo, + referToName = referToName, + otherReferredTo = otherReferredTo, + typeOfLeprosy = typeOfLeprosy, + remarks = remarks, + beneficiaryStatus = beneficiaryStatus, + placeOfDeath = placeOfDeath, + otherPlaceOfDeath = otherPlaceOfDeath, + reasonForDeath = reasonForDeath, + otherReasonForDeath = otherReasonForDeath, + diseaseTypeID = diseaseTypeID, + beneficiaryStatusId = beneficiaryStatusId, + leprosySymptoms = leprosySymptoms, + leprosySymptomsPosition = leprosySymptomsPosition, + lerosyStatusPosition = lerosyStatusPosition, + currentVisitNumber = currentVisitNumber, + visitLabel = visitLabel, + visitNumber = visitNumber, + isConfirmed = isConfirmed, + leprosyState = leprosyState, + treatmentStartDate = getLongFromDate(treatmentStartDate), + totalFollowUpMonthsRequired = totalFollowUpMonthsRequired, + treatmentEndDate = getLongFromDate(treatmentEndDate), + mdtBlisterPackRecived = mdtBlisterPackRecived, + treatmentStatus = treatmentStatus, + createdBy = createdBy, + createdDate = getLongFromDate(createdDate), + modifiedBy = modifiedBy, + lastModDate =getLongFromDate(lastModDate), + syncState = SyncState.SYNCED, + recurrentUlceration = recurrentUlceration, + recurrentUlcerationId = recurrentUlcerationId, + recurrentTingling = recurrentTingling, + recurrentTinglingId = recurrentTinglingId, + hypopigmentedPatchId = hypopigmentedPatchId, + hypopigmentedPatch = hypopigmentedPatch, + thickenedSkin = thickenedSkin, + thickenedSkinId = thickenedSkinId, + skinNodules = skinNodules, + skinNodulesId = skinNodulesId, + skinPatchDiscoloration = skinPatchDiscoloration, + skinPatchDiscolorationId = skinPatchDiscolorationId, + recurrentNumbness = recurrentNumbness, + recurrentNumbnessId = recurrentNumbnessId, + clawingFingers = clawingFingers, + clawingFingersId = clawingFingersId, + tinglingNumbnessExtremities = tinglingNumbnessExtremities, + tinglingNumbnessExtremitiesId = tinglingNumbnessExtremitiesId, + inabilityCloseEyelid = inabilityCloseEyelid, + inabilityCloseEyelidId = inabilityCloseEyelidId, + difficultyHoldingObjects = difficultyHoldingObjects, + difficultyHoldingObjectsId = difficultyHoldingObjectsId, + weaknessFeet = weaknessFeet, + weaknessFeetId = weaknessFeetId, + ) + } +} + +data class LeprosyFollowUpDTO( + val benId: Long, + val visitNumber: Int, + var followUpDate: String = getDateTimeStringFromLong(System.currentTimeMillis()).toString(), + var treatmentStatus: String? = null, + var mdtBlisterPackReceived: String? = null, + var treatmentCompleteDate: String = getDateTimeStringFromLong(0).toString(), + var remarks: String? = null, + var homeVisitDate: String = getDateTimeStringFromLong(System.currentTimeMillis()).toString(), + var leprosySymptoms: String? = null, + var typeOfLeprosy: String? = null, + var leprosySymptomsPosition: Int? = 1, + var visitLabel: String? = "Visit -1", + var leprosyStatus: String? = "", + var referredTo: Int? = 0, + var referToName: String? = null, + var treatmentEndDate: String = getDateTimeStringFromLong(System.currentTimeMillis()).toString(), + var mdtBlisterPackRecived: String? = null, + val createdBy: String, + val createdDate: String, + val modifiedBy: String, + val lastModDate: String, + var treatmentStartDate: String = getDateTimeStringFromLong(System.currentTimeMillis()).toString() +) { + fun toCache(): LeprosyFollowUpCache { + return LeprosyFollowUpCache( + benId = benId, + visitNumber = visitNumber, + followUpDate = getLongFromDate(followUpDate), + treatmentStatus = treatmentStatus, + mdtBlisterPackReceived = mdtBlisterPackReceived, + treatmentCompleteDate = getLongFromDate(treatmentCompleteDate), + remarks = remarks, + homeVisitDate = getLongFromDate(homeVisitDate), + leprosySymptoms = leprosySymptoms, + typeOfLeprosy = typeOfLeprosy, + leprosySymptomsPosition = leprosySymptomsPosition, + visitLabel = visitLabel, + leprosyStatus = leprosyStatus, + referredTo = referredTo, + referToName = referToName, + treatmentEndDate = getLongFromDate(treatmentEndDate), + mdtBlisterPackRecived = mdtBlisterPackRecived, + treatmentStartDate = getLongFromDate(treatmentStartDate), + createdBy = createdBy, + createdDate =getLongFromDate(createdDate), + modifiedBy = modifiedBy, + lastModDate =getLongFromDate(lastModDate), + syncState = SyncState.SYNCED + ) + } +} + +data class FilariaScreeningDTO( + val id: Int = 0, + val benId: Long, + val mdaHomeVisitDate: String, + val houseHoldDetailsId: Long, + var sufferingFromFilariasis: Boolean ? = false, + var doseStatus: String ? = null, + var affectedBodyPart: String ? = null, + var otherDoseStatusDetails: String ? = null, + var filariasisCaseCount: String ? = null, + var medicineSideEffect: String ? = "", + var otherSideEffectDetails: String ? = "", + var createdBy: String ? = "", + var diseaseTypeID: Int ? = 0, + var createdDate: String, + var syncState: SyncState = SyncState.UNSYNCED, + + ) { + fun toCache(): FilariaScreeningCache { + return FilariaScreeningCache( + benId = benId, + syncState = SyncState.SYNCED, + diseaseTypeID = diseaseTypeID, + mdaHomeVisitDate = getLongFromDate(mdaHomeVisitDate), + houseHoldDetailsId = houseHoldDetailsId, + doseStatus = doseStatus.toString(), + sufferingFromFilariasis = sufferingFromFilariasis!!, + affectedBodyPart = affectedBodyPart.toString(), + otherDoseStatusDetails = otherDoseStatusDetails.toString(), + medicineSideEffect = medicineSideEffect.toString(), + otherSideEffectDetails = otherSideEffectDetails.toString(), + createdBy = createdBy.toString(), + createdDate = getLongFromDate(createdDate), + + ) + } +} + +data class ScreeningRoundDTO( + val date: String, + val rounds: Int, + val householdId: Long +) { + fun toCache(): IRSRoundScreening { + return IRSRoundScreening( + date = getLongFromDate(date), + rounds = rounds, + householdId = householdId, + ) + } +} + +data class KALAZARScreeningDTO( + val id: Int = 0, + val benId: Long, + var visitDate: String, + val houseHoldDetailsId: Long, + var beneficiaryStatus: String, + var beneficiaryStatusId: Int = 0, + var dateOfDeath: String, + var placeOfDeath: String, + var otherPlaceOfDeath: String, + var reasonForDeath: String, + var otherReasonForDeath: String, + var rapidDiagnosticTest: String, + var dateOfRdt: String, + var kalaAzarCaseStatus: String ? = "", + var referredTo: Int ? = 0, + var referToName: String, + var otherReferredFacility: String, + var diseaseTypeID: Int ? = 0, + var createdDate: String, + var createdBy: String , + var followUpPoint: Int ? = 0, + var syncState: SyncState = SyncState.UNSYNCED, + + ) { + fun toCache(): KalaAzarScreeningCache { + return KalaAzarScreeningCache( + benId = benId, + visitDate = getLongFromDate(visitDate), + kalaAzarCaseStatus = kalaAzarCaseStatus, + houseHoldDetailsId = houseHoldDetailsId, + referredTo = referredTo, + referToName = referToName.toString(), + otherReferredFacility = otherReferredFacility, + createdDate = getLongFromDate(createdDate), + syncState = SyncState.SYNCED, + diseaseTypeID = diseaseTypeID, + beneficiaryStatusId = beneficiaryStatusId, + beneficiaryStatus = beneficiaryStatus, + createdBy = createdBy, + rapidDiagnosticTest = rapidDiagnosticTest, + dateOfRdt = getLongFromDate(dateOfRdt), + dateOfDeath = getLongFromDate(dateOfDeath), + reasonForDeath = reasonForDeath, + otherReasonForDeath = otherReasonForDeath, + otherPlaceOfDeath = otherPlaceOfDeath, + placeOfDeath = placeOfDeath, + followUpPoint = followUpPoint + + + ) + } +} + fun getLongFromDate(dateString: String?): Long { val f = SimpleDateFormat("MMM d, yyyy h:mm:ss a", Locale.ENGLISH) val date = dateString?.let { f.parse(it) } return date?.time ?: 0L } + +fun getLongFromDateMultipleSupport(dateStr: String?): Long? { + if (dateStr.isNullOrBlank() || dateStr == "1970-01-01") return null + + val formats = listOf( + "yyyy-MM-dd", + "yyyy-MM-dd HH:mm:ss", + "MMM dd, yyyy hh:mm:ss a", + "MMM dd, yyyy", + "dd/MM/yyyy" + ) + + for (format in formats) { + try { + val sdf = SimpleDateFormat(format, Locale.ENGLISH) + sdf.isLenient = false + return sdf.parse(dateStr)?.time + } catch (e: Exception) { + } + } + + Timber.e("Date parsing failed for: $dateStr") + return null +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/network/interceptors/AccountDeactivationInterceptor.kt b/app/src/main/java/org/piramalswasthya/sakhi/network/interceptors/AccountDeactivationInterceptor.kt new file mode 100644 index 000000000..eed5e8fef --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/network/interceptors/AccountDeactivationInterceptor.kt @@ -0,0 +1,40 @@ +package org.piramalswasthya.sakhi.network.interceptors + +import okhttp3.Interceptor +import okhttp3.Response +import org.json.JSONObject +import org.piramalswasthya.sakhi.helpers.AccountDeactivationManager +import timber.log.Timber +import javax.inject.Inject + +class AccountDeactivationInterceptor @Inject constructor( + private val deactivationManager: AccountDeactivationManager +) : Interceptor { + + override fun intercept(chain: Interceptor.Chain): Response { + val response = chain.proceed(chain.request()) + + try { + val peekBody = response.peekBody(1024 * 1024) // peek up to 1MB + val bodyString = peekBody.string() + if (bodyString.isNotBlank()) { + val json = JSONObject(bodyString) + val statusCode = json.optInt("statusCode", -1) + if (statusCode == 5002) { + val errorMessage = json.optString("errorMessage", "") + if (errorMessage.contains("deactivat", ignoreCase = true) || + errorMessage.contains("locked", ignoreCase = true) + ) { + Timber.w("Account deactivation detected: $errorMessage") + deactivationManager.emitIfCooldownPassed(errorMessage) + } + } + } + } catch (e: Exception) { + // Silently ignore parse errors — don't break the normal flow + Timber.d("AccountDeactivationInterceptor: skipping non-JSON response") + } + + return response + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/network/interceptors/LoggingInterceptor.kt b/app/src/main/java/org/piramalswasthya/sakhi/network/interceptors/LoggingInterceptor.kt new file mode 100644 index 000000000..964d893f4 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/network/interceptors/LoggingInterceptor.kt @@ -0,0 +1,10 @@ +package org.piramalswasthya.sakhi.network.interceptors + +import okhttp3.logging.HttpLoggingInterceptor +import timber.log.Timber + +class LoggingInterceptor : HttpLoggingInterceptor.Logger { + override fun log(message: String) { + Timber.tag("OkHttp").d(message) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/network/interceptors/TokenAuthenticator.kt b/app/src/main/java/org/piramalswasthya/sakhi/network/interceptors/TokenAuthenticator.kt new file mode 100644 index 000000000..dd6a2ac84 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/network/interceptors/TokenAuthenticator.kt @@ -0,0 +1,96 @@ +package org.piramalswasthya.sakhi.network.interceptors + +import kotlinx.coroutines.runBlocking +import okhttp3.Authenticator +import okhttp3.Request +import okhttp3.Response +import okhttp3.Route +import org.json.JSONObject +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao +import org.piramalswasthya.sakhi.helpers.TokenExpiryManager +import org.piramalswasthya.sakhi.network.AmritApiService +import org.piramalswasthya.sakhi.network.TmcRefreshTokenRequest +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Named + +class TokenAuthenticator @Inject constructor( + private val pref: PreferenceDao, + @Named("authApi") private val authApi: AmritApiService, + private val tokenExpiryManager: TokenExpiryManager +) : Authenticator { + + private val refreshLock = Any() + + override fun authenticate(route: Route?, response: Response): Request? { + + if (responseCount(response) >= 3) return null + if (response.request.header("No-Auth") == "true") return null + + val oldJwt = response.request.header("Jwttoken") + val refreshToken = pref.getRefreshToken() ?: return null + + val newJwt = synchronized(refreshLock) { + val currentJwt = pref.getJWTAmritToken() + if (!currentJwt.isNullOrBlank() && currentJwt != oldJwt) { + return@synchronized currentJwt + } + runBlocking { + try { + val resp = authApi.getRefreshToken( + TmcRefreshTokenRequest(refreshToken) + ) + + if (!resp.isSuccessful) { + resp.errorBody()?.close() + Timber.w( + "Token refresh failed: HTTP ${resp.code()}" + ) + tokenExpiryManager.onRefreshFailed() + return@runBlocking null + } + + val body = resp.body()?.string().orEmpty() + if (body.isEmpty()) { + tokenExpiryManager.onRefreshFailed() + return@runBlocking null + } + + val json = JSONObject(body) + val jwt = json.optString("jwtToken", "") + val newRefresh = json.optString("refreshToken", refreshToken) + + if (jwt.isBlank()) { + tokenExpiryManager.onRefreshFailed() + null + } else { + pref.registerJWTAmritToken(jwt) + pref.registerRefreshToken(newRefresh) + tokenExpiryManager.onRefreshSuccess() + jwt + } + + } catch (e: Exception) { + Timber.e(e, "Token refresh failed") + tokenExpiryManager.onRefreshFailed() + null + } + } + } ?: return null + + return response.request.newBuilder() + .removeHeader("Jwttoken") + .header("Jwttoken", newJwt) + .build() + } + + private fun responseCount(response: Response): Int { + var count = 1 + var prior = response.priorResponse + while (prior != null) { + count++ + prior = prior.priorResponse + } + return count + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/network/interceptors/TokenInsertAbhaInterceptor.kt b/app/src/main/java/org/piramalswasthya/sakhi/network/interceptors/TokenInsertAbhaInterceptor.kt index 1b8f9a3f5..439b7509e 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/network/interceptors/TokenInsertAbhaInterceptor.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/network/interceptors/TokenInsertAbhaInterceptor.kt @@ -8,16 +8,17 @@ class TokenInsertAbhaInterceptor : Interceptor { companion object { private var TOKEN: String = "" private var XToken: String = "" - fun setToken(iToken: String) { - TOKEN = iToken + + fun setToken(iToken: String?) { + TOKEN = iToken ?: "" } fun getToken(): String { return TOKEN } - fun setXToken(xToken: String) { - XToken = xToken + fun setXToken(xToken: String?) { + XToken = xToken ?: "" } fun getXToken(): String { @@ -36,16 +37,22 @@ class TokenInsertAbhaInterceptor : Interceptor { ) .build() } + val url = request.url.toString() - if (url.contains("getCard") || url.contains("getPngCard")) { - request = request - .newBuilder() - .addHeader( - "x-token", - "Bearer $XToken" - ) - .build() + if (url.contains("getCard") || url.contains("getPngCard") || url.contains("abha-card")) { + if (XToken.isNotEmpty()) { + request = request + .newBuilder() + .addHeader( + "x-token", + "Bearer $XToken" + ) + .build() + } else { + Timber.w("x-token is empty, not adding x-token header") + } } + Timber.d("Request : $request") return chain.proceed(request) } diff --git a/app/src/main/java/org/piramalswasthya/sakhi/network/interceptors/TokenInsertTmcInterceptor.kt b/app/src/main/java/org/piramalswasthya/sakhi/network/interceptors/TokenInsertTmcInterceptor.kt index d0786c9cd..7a3edd773 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/network/interceptors/TokenInsertTmcInterceptor.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/network/interceptors/TokenInsertTmcInterceptor.kt @@ -2,10 +2,13 @@ package org.piramalswasthya.sakhi.network.interceptors import okhttp3.Interceptor import okhttp3.Response +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao import timber.log.Timber -class TokenInsertTmcInterceptor : Interceptor { - companion object { +class TokenInsertTmcInterceptor( + private val preferenceDao: PreferenceDao +) : Interceptor { + companion object { private var TOKEN: String = "" fun setToken(iToken: String) { TOKEN = iToken @@ -14,17 +17,41 @@ class TokenInsertTmcInterceptor : Interceptor { fun getToken(): String { return TOKEN } + + private var JWT: String = "" + fun setJwt(iJWT: String) { + JWT = iJWT + } + + fun getJwt(): String { + return JWT + } } override fun intercept(chain: Interceptor.Chain): Response { - var request = chain.request() - if (request.header("No-Auth") == null) { - request = request - .newBuilder() - .addHeader("Authorization", TOKEN) - .build() + val originalRequest = chain.request() + + if (originalRequest.header("No-Auth") == "true") { + return chain.proceed(originalRequest) + } + + val jwt = preferenceDao.getJWTAmritToken() + val user = preferenceDao.getLoggedInUser() + + val requestBuilder = originalRequest.newBuilder() + + if (!jwt.isNullOrBlank()) { + requestBuilder.header("Jwttoken", jwt) + } + + user?.userId?.let { + requestBuilder.header("userId", it.toString()) } - Timber.d("Request : $request") - return chain.proceed(request) + + val finalRequest = requestBuilder.build() + + // Timber.d("Request URL=${finalRequest.url}, headers=${finalRequest.headers}") + + return chain.proceed(finalRequest) } } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/ABHAGenratedRepo.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/ABHAGenratedRepo.kt new file mode 100644 index 000000000..a1125122c --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/ABHAGenratedRepo.kt @@ -0,0 +1,24 @@ +package org.piramalswasthya.sakhi.repositories + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.piramalswasthya.sakhi.database.room.dao.ABHAGenratedDao +import org.piramalswasthya.sakhi.model.ABHAModel +import javax.inject.Inject + +class ABHAGenratedRepo @Inject constructor( + private val abhaGenratedDao: ABHAGenratedDao, +) { + suspend fun saveAbhaGenrated(abhaModel: ABHAModel) { + withContext(Dispatchers.IO) { + abhaGenratedDao.saveABHA(abhaModel) + } + } + + suspend fun deleteAbhaByBenId(benId: Long) { + withContext(Dispatchers.IO) { + abhaGenratedDao.deleteAbhaByBenId(benId) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/AESRepo.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/AESRepo.kt new file mode 100644 index 000000000..f709430a3 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/AESRepo.kt @@ -0,0 +1,248 @@ +package org.piramalswasthya.sakhi.repositories + +import com.google.gson.Gson +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.json.JSONObject +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.database.room.dao.AesDao +import org.piramalswasthya.sakhi.database.room.dao.BenDao +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao +import org.piramalswasthya.sakhi.helpers.Konstants +import org.piramalswasthya.sakhi.model.AESScreeningCache +import org.piramalswasthya.sakhi.network.AESScreeningDTO +import org.piramalswasthya.sakhi.network.AESScreeningRequestDTO +import org.piramalswasthya.sakhi.network.AmritApiService +import org.piramalswasthya.sakhi.network.GetDataPaginatedRequestForDisease +import timber.log.Timber +import java.net.SocketTimeoutException +import java.text.SimpleDateFormat +import java.util.Locale +import javax.inject.Inject + +class AESRepo @Inject constructor( + private val aesDao: AesDao, + private val benDao: BenDao, + private val preferenceDao: PreferenceDao, + private val userRepo: UserRepo, + private val tmcNetworkApiService: AmritApiService +) { + + suspend fun getAESScreening(benId: Long): AESScreeningCache? { + return withContext(Dispatchers.IO) { + aesDao.getAESScreening(benId) + } + } + + suspend fun saveAESScreening(aesScreeningCache: AESScreeningCache) { + withContext(Dispatchers.IO) { + aesDao.saveAESScreening(aesScreeningCache) + } + } + + suspend fun getAESScreeningDetailsFromServer(): Int { + return withContext(Dispatchers.IO) { + val user = + preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") + try { + val response = tmcNetworkApiService.getMalariaScreeningData( + GetDataPaginatedRequestForDisease( + ashaId = user.userId, + pageNo = 0, + fromDate = BenRepo.getCurrentDate(Konstants.defaultTimeStamp), + toDate = getCurrentDate(), + diseaseTypeID = 3 + ) + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + + val errorMessage = jsonObj.optString("errorMessage", "") + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Pull from amrit AES screening data : $responseStatusCode") + when (responseStatusCode) { + 200 -> { + try { + val dataObj = jsonObj.getString("data") + saveAESScreeningCacheFromResponse(dataObj) + } catch (e: Exception) { + Timber.d("AES Screening entries not synced $e") + return@withContext 0 + } + + return@withContext 1 + } + + 5002 -> { + if (userRepo.refreshTokenTmc( + user.userName, user.password + ) + ) throw SocketTimeoutException("Refreshed Token!") + else throw IllegalStateException("User Logged out!!") + } + + 5000 -> { + if (errorMessage == "No record found") return@withContext 0 + } + + else -> { + throw IllegalStateException("$responseStatusCode received, dont know what todo!?") + } + } + } + } + + } catch (e: SocketTimeoutException) { + Timber.e("get_tb error : $e") + return@withContext -2 + + } catch (e: java.lang.IllegalStateException) { + Timber.e("get_tb error : $e") + return@withContext -1 + } + -1 + } + } + + private suspend fun saveAESScreeningCacheFromResponse(dataObj: String): MutableList { + val aesScreeningList = mutableListOf() + val aesJeLists: List = if (dataObj.trimStart().startsWith("[")) { + Gson().fromJson(dataObj, Array::class.java)?.toList() ?: emptyList() + } else { + val requestDTO = Gson().fromJson(dataObj, AESScreeningRequestDTO::class.java) + requestDTO?.aesJeLists ?: emptyList() + } + aesJeLists.forEach { aesScreeningDTO -> + aesScreeningDTO.visitDate.let { + var aesScreeningCache: AESScreeningCache? = + aesDao.getAESScreening( + aesScreeningDTO.benId, + getLongFromDate(aesScreeningDTO.visitDate), + getLongFromDate(aesScreeningDTO.visitDate) - 19_800_000 + ) + if (aesScreeningCache == null) { + benDao.getBen(aesScreeningDTO.benId)?.let { + aesDao.saveAESScreening(aesScreeningDTO.toCache()) + } + } + } + } + return aesScreeningList + } + + + // RECORD-LEVEL ISOLATION: Coordinator always returns true so the + // WorkManager worker succeeds. Failed records stay UNSYNCED for next cycle. + suspend fun pushUnSyncedRecords(): Boolean { + val screeningResult = pushUnSyncedRecordsAESScreening() + Timber.d("AES push result: screening=$screeningResult") + // Worker succeeds — failed records stay UNSYNCED for next cycle + return true + } + + // RECORD-LEVEL ISOLATION: AES Screening records are now sent in + // chunks of 20 instead of one giant batch. Previously, if ANY record in + // the batch was malformed, the ENTIRE batch failed and ALL records stayed + // UNSYNCED. Now each chunk is independent — one bad chunk doesn't affect + // the others. Failed chunks' records stay UNSYNCED for the next sync cycle. + private suspend fun pushUnSyncedRecordsAESScreening(): Int { + + return withContext(Dispatchers.IO) { + val user = + preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") + + val aesSnList: List = aesDao.getAESScreening(SyncState.UNSYNCED) + + if (aesSnList.isEmpty()) return@withContext 1 + + val CHUNK_SIZE = 20 + val chunks = aesSnList.chunked(CHUNK_SIZE) + var successCount = 0 + var failCount = 0 + + for (chunk in chunks) { + try { + val chunkDtos = chunk.map { it.toDTO() } + + val response = tmcNetworkApiService.saveAESScreeningData( + AESScreeningRequestDTO( + userId = user.userId, + aesJeLists = chunkDtos + ) + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Push to Amrit AES Screening chunk: $responseStatusCode") + when (responseStatusCode) { + 200 -> { + updateSyncStatusScreening(chunk) + successCount += chunk.size + } + + 401, 5002 -> { + if (userRepo.refreshTokenTmc(user.userName, user.password)) { + Timber.d("Token refreshed, AES chunk will retry next cycle") + } + failCount += chunk.size + } + + else -> { + Timber.e("AES Screening chunk failed with statusCode: $responseStatusCode") + failCount += chunk.size + } + } + } + } else { + Timber.e("AES Screening chunk HTTP error: $statusCode") + failCount += chunk.size + } + } catch (e: Exception) { + Timber.e(e, "AES Screening chunk push failed: ${chunk.size} records") + failCount += chunk.size + } + } + + Timber.d("AES Screening push complete: $successCount succeeded, $failCount failed out of ${aesSnList.size}") + return@withContext 1 + } + } + + + private suspend fun updateSyncStatusScreening(aesAsList: List) { + aesAsList.forEach { + it.syncState = SyncState.SYNCED + aesDao.saveAESScreening(it) + } + } + + + companion object { + private val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) + private val timeFormat = SimpleDateFormat("HH:mm:ss", Locale.ENGLISH) + private fun getCurrentDate(millis: Long = System.currentTimeMillis()): String { + val dateString = dateFormat.format(millis) + val timeString = timeFormat.format(millis) + return "${dateString}T${timeString}.000Z" + } + + private fun getLongFromDate(dateString: String): Long { + val date = try { + SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH).parse(dateString) + } catch (_: Exception) { + SimpleDateFormat("MMM d, yyyy h:mm:ss a", Locale.ENGLISH).parse(dateString) + } + return date?.time ?: throw IllegalStateException("Invalid date for dateReg") + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/AbhaIdRepo.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/AbhaIdRepo.kt index 5d6a44188..e74c005f9 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/repositories/AbhaIdRepo.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/AbhaIdRepo.kt @@ -6,9 +6,13 @@ import com.google.gson.reflect.TypeToken import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import okhttp3.ResponseBody +import org.json.JSONArray import org.json.JSONException import org.json.JSONObject +import org.piramalswasthya.sakhi.BuildConfig import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao +import org.piramalswasthya.sakhi.model.ABHAModel +import org.piramalswasthya.sakhi.model.BenRegCache import org.piramalswasthya.sakhi.network.AadhaarVerifyBioRequest import org.piramalswasthya.sakhi.network.AbhaApiService import org.piramalswasthya.sakhi.network.AbhaCheckAndGenerateMobileOtpResponse @@ -16,12 +20,14 @@ import org.piramalswasthya.sakhi.network.AbhaGenerateAadhaarOtpRequest import org.piramalswasthya.sakhi.network.AbhaGenerateAadhaarOtpResponse import org.piramalswasthya.sakhi.network.AbhaGenerateAadhaarOtpResponseV2 import org.piramalswasthya.sakhi.network.AbhaGenerateMobileOtpRequest +import org.piramalswasthya.sakhi.network.AbhaPublicCertificateResponse import org.piramalswasthya.sakhi.network.AbhaResendAadhaarOtpRequest import org.piramalswasthya.sakhi.network.AbhaTokenResponse import org.piramalswasthya.sakhi.network.AbhaVerifyAadhaarOtpRequest import org.piramalswasthya.sakhi.network.AbhaVerifyAadhaarOtpResponse import org.piramalswasthya.sakhi.network.AbhaVerifyMobileOtpRequest import org.piramalswasthya.sakhi.network.AbhaVerifyMobileOtpResponse +import org.piramalswasthya.sakhi.network.AddHealthIdRecord import org.piramalswasthya.sakhi.network.AmritApiService import org.piramalswasthya.sakhi.network.CreateAbhaIdGovRequest import org.piramalswasthya.sakhi.network.CreateAbhaIdRequest @@ -29,8 +35,14 @@ import org.piramalswasthya.sakhi.network.CreateAbhaIdResponse import org.piramalswasthya.sakhi.network.CreateHIDResponse import org.piramalswasthya.sakhi.network.CreateHealthIdRequest import org.piramalswasthya.sakhi.network.GenerateOtpHid +import org.piramalswasthya.sakhi.network.LoginGenerateOtpRequest +import org.piramalswasthya.sakhi.network.LoginGenerateOtpResponse +import org.piramalswasthya.sakhi.network.LoginVerifyOtpRequest +import org.piramalswasthya.sakhi.network.LoginVerifyOtpResponse import org.piramalswasthya.sakhi.network.MapHIDtoBeneficiary import org.piramalswasthya.sakhi.network.NetworkResult +import org.piramalswasthya.sakhi.network.SearchAbhaRequest +import org.piramalswasthya.sakhi.network.SearchAbhaResponse import org.piramalswasthya.sakhi.network.StateCodeResponse import org.piramalswasthya.sakhi.network.ValidateOtpHid import retrofit2.Response @@ -39,6 +51,11 @@ import java.io.IOException import java.net.SocketTimeoutException import java.security.KeyFactory import java.security.spec.X509EncodedKeySpec +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale +import java.util.TimeZone +import java.util.UUID import javax.crypto.Cipher import javax.inject.Inject @@ -46,12 +63,24 @@ class AbhaIdRepo @Inject constructor( private val abhaApiService: AbhaApiService, private val amritApiService: AmritApiService, private val userRepo: UserRepo, + private val abhaGenerated: ABHAGenratedRepo, private val prefDao: PreferenceDao ) { suspend fun getAccessToken(): NetworkResult { return withContext(Dispatchers.IO) { try { - val response = abhaApiService.getToken() + + val response = abhaApiService.getToken( + id = if (BuildConfig.FLAVOR.contains("stag", true) || + BuildConfig.FLAVOR.contains("uat", true) + ) { + "sbx" + } else { + "abdm" + }, + requestId = generateUUID(), + timestamp = getCurrentTimestamp() + ) if (response.isSuccessful) { val responseBody = response.body()?.string() val result = Gson().fromJson(responseBody, AbhaTokenResponse::class.java) @@ -75,12 +104,17 @@ class AbhaIdRepo @Inject constructor( suspend fun getAuthCert(): NetworkResult { return withContext(Dispatchers.IO) { try { - val response = abhaApiService.getAuthCert() + val response = abhaApiService.getAuthCert( + requestId = generateUUID(), + timestamp = getCurrentTimestamp() + ) if (response.isSuccessful) { val responseBody = response.body()?.string() - var key = responseBody!! - key = key.replace("-----BEGIN PUBLIC KEY-----\n", "") - key = key.replace("-----END PUBLIC KEY-----", "") + val result = + Gson().fromJson(responseBody, AbhaPublicCertificateResponse::class.java) + var key = result.publicKey +// key = key.replace("-----BEGIN PUBLIC KEY-----\n", "") +// key = key.replace("-----END PUBLIC KEY-----", "") key = key.trim() NetworkResult.Success(key) } else { @@ -126,17 +160,122 @@ class AbhaIdRepo @Inject constructor( } } - suspend fun generateOtpForAadhaarV2(req: AbhaGenerateAadhaarOtpRequest): NetworkResult { + suspend fun generateAadhaarOtpV3(req: AbhaGenerateAadhaarOtpRequest): NetworkResult { return withContext(Dispatchers.IO) { try { - req.aadhaar = encryptData(req.aadhaar) - val response = abhaApiService.generateAadhaarOtpV2(req) + req.loginId = encryptData(req.loginId) + val response = + abhaApiService.generateAadhaarOtpV3(req, generateUUID(), getCurrentTimestamp()) if (response.isSuccessful) { val responseBody = response.body()?.string() val result = Gson().fromJson(responseBody, AbhaGenerateAadhaarOtpResponseV2::class.java) NetworkResult.Success(result) } else { + + val errorString = response.errorBody()?.string() + val (code, message) = parseAbhaErrorString(errorString) ?: Pair("", "") + + if (message.contains("UIDAI Error code : 953")){ + return@withContext NetworkResult.Error(-5, "You have requested multiple OTPs in this transaction. Please try again in 30 minutes.") + } + sendErrorResponse(response) + } + } catch (e: IOException) { + NetworkResult.Error(-1, "Unable to connect to Internet!") + } catch (e: JSONException) { + if (e.message.toString().contains("UIDAI Error code : 953")){ + return@withContext NetworkResult.Error(-5, "You have requested multiple OTPs in this transaction. Please try again in 30 minutes.") + } + NetworkResult.Error(-2, "Invalid response! Please try again!") + } catch (e: SocketTimeoutException) { + NetworkResult.Error(-3, "Request Timed out! Please try again!") + } catch (e: java.lang.Exception) { + e.printStackTrace() + NetworkResult.Error(-4, e.message ?: "Unknown Error") + } + } + } + + suspend fun searchAbha(req: SearchAbhaRequest): NetworkResult { + return withContext(Dispatchers.IO) { + try { + req.mobile = encryptData(req.mobile) + val response = abhaApiService.searchAbha(req, generateUUID(), getCurrentTimestamp()) + if (response.isSuccessful) { + val intermediateResult = JSONArray(response.body()?.string()) + val responseBody = intermediateResult.getJSONObject(0).toString() + val result = + Gson().fromJson(responseBody, SearchAbhaResponse::class.java) + NetworkResult.Success(result) + } else { + + val errorString = response.errorBody()?.string() + val (code, message) = parseAbhaErrorString(errorString) ?: Pair("", "") + + if (message.contains("User not found.") || code.contains("ABDM-1114")){ + return@withContext NetworkResult.Error(-5, "User not found.") + } + + sendErrorResponse(response) + } + } catch (e: IOException) { + NetworkResult.Error(-1, "Unable to connect to Internet!") + } catch (e: JSONException) { + if (e.message.toString().contains("No value for details")){ + return@withContext NetworkResult.Error(-5, "User not found.") + } + NetworkResult.Error(-2, "Invalid response! Please try again!") + } catch (e: SocketTimeoutException) { + NetworkResult.Error(-3, "Request Timed out! Please try again!") + } catch (e: java.lang.Exception) { + e.printStackTrace() + NetworkResult.Error(-4, e.message ?: "Unknown Error") + } + } + } + + suspend fun generateAbhaOtp(req: LoginGenerateOtpRequest): NetworkResult { + return withContext(Dispatchers.IO) { + try { + req.loginId = encryptData(req.loginId) + val response = + abhaApiService.loginGenerateOtp(req, generateUUID(), getCurrentTimestamp()) + if (response.isSuccessful) { + val responseBody = response.body()?.string() + val result = + Gson().fromJson(responseBody, LoginGenerateOtpResponse::class.java) + NetworkResult.Success(result) + } else { + sendErrorResponse(response) + } + } catch (e: IOException) { +// NetworkResult.Error(-1, "Unable to connect to Internet!") + NetworkResult.NetworkError + } catch (e: JSONException) { + NetworkResult.Error(-2, "Invalid response! Please try again!") + } catch (e: SocketTimeoutException) { +// NetworkResult.Error(-3, "Request Timed out! Please try again!") + NetworkResult.NetworkError + } catch (e: java.lang.Exception) { + e.printStackTrace() + NetworkResult.Error(-4, e.message ?: "Unknown Error") + } + } + } + + suspend fun verifyAbhaOtp(req: LoginVerifyOtpRequest): NetworkResult { + return withContext(Dispatchers.IO) { + try { + req.authData.otp.otpValue = encryptData(req.authData.otp.otpValue) + val response = + abhaApiService.loginVerifyOtp(req, generateUUID(), getCurrentTimestamp()) + if (response.isSuccessful) { + val responseBody = response.body()?.string() + val result = + Gson().fromJson(responseBody, LoginVerifyOtpResponse::class.java) + NetworkResult.Success(result) + } else { sendErrorResponse(response) } } catch (e: IOException) { @@ -152,6 +291,17 @@ class AbhaIdRepo @Inject constructor( } } + private fun generateUUID(): String { + return UUID.randomUUID().toString() + } + + private fun getCurrentTimestamp(): String { + val sdf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH).apply { + timeZone = TimeZone.getTimeZone("UTC") + } + return sdf.format(Date()) + } + private fun encryptData(txt: String): String { var encryptedTextBase64 = "" val publicKeyString = prefDao.getPublicKeyForAbha()!! @@ -162,7 +312,8 @@ class AbhaIdRepo @Inject constructor( val publicKeySpec = X509EncodedKeySpec(publicKeyBytes) val publicKey = keyFactory.generatePublic(publicKeySpec) - val cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding") + // Cipher used in ABHA v3 APIs + val cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding") cipher.init(Cipher.ENCRYPT_MODE, publicKey) val encryptedText = cipher.doFinal(txt.toByteArray()) encryptedTextBase64 = @@ -198,22 +349,54 @@ class AbhaIdRepo @Inject constructor( } } + + fun parseAbhaErrorString(errorString: String?): Pair? { + return try { + if (errorString.isNullOrEmpty()) return null + val json = JSONObject(errorString) + val errorObj = json.optJSONObject("error") + val code = errorObj?.optString("code") ?: "" + val message = errorObj?.optString("message") ?: "" + Pair(code, message) + } catch (e: Exception) { + Timber.e("Error parsing error JSON: ${e.message}") + null + } + } + suspend fun verifyOtpForAadhaar(req: AbhaVerifyAadhaarOtpRequest): NetworkResult { + var response: Response? = null return withContext(Dispatchers.IO) { try { - val response = abhaApiService.verifyAadhaarOtp(req) - if (response.isSuccessful) { - val responseBody = response.body()?.string() + // ABHA v3 + req.authData.otp.otpValue = encryptData(req.authData.otp.otpValue) + req.authData.otp.timeStamp = getCurrentTimestamp() + + // ABHA v3 API + response = abhaApiService.verifyAadhaarOtp3(req, generateUUID(), getCurrentTimestamp()) + if (response?.isSuccessful == true) { + val responseBody = response?.body()?.string() val result = Gson().fromJson(responseBody, AbhaVerifyAadhaarOtpResponse::class.java) NetworkResult.Success(result) } else { + val errorString = response?.errorBody()?.string() + val (code, message) = parseAbhaErrorString(errorString) ?: Pair("", "") + + if (message.contains("The mobile number provided by you is already linked to 6 ABHA numbers. Please provide a different mobile number.")){ + return@withContext NetworkResult.Error(-5, "The mobile number provided by you is already linked to 6 ABHA numbers.\nPlease provide a different mobile number.") + } + + if (message.contains("UIDAI Error code : 400 : OTP validation failed")){ + return@withContext NetworkResult.Error(-6, "Incorrect OTP, Please Enter Valid OTP") + } + sendErrorResponse(response) } } catch (e: IOException) { NetworkResult.Error(-1, "Unable to connect to Internet!") } catch (e: JSONException) { - NetworkResult.Error(-2, "Invalid response! Please try again!") + NetworkResult.Error(-2, "SMS Gateway is unavailable! Please try again!") } catch (e: SocketTimeoutException) { NetworkResult.Error(-3, "Request Timed out! Please try again!") } catch (e: java.lang.Exception) { @@ -224,6 +407,31 @@ class AbhaIdRepo @Inject constructor( } + suspend fun printAbhaCard(): NetworkResult { + return withContext(Dispatchers.IO) { + try { + val response = abhaApiService.printAbhaCard(generateUUID(), getCurrentTimestamp()) + val responseBody = response.body() + if (response.isSuccessful) { + NetworkResult.Success( + responseBody!! + ) + } else { + sendErrorResponse(response) + } + } catch (e: IOException) { + NetworkResult.Error(-1, "Unable to connect to Internet!") + } catch (e: JSONException) { + NetworkResult.Error(-2, "Invalid response! Please try again!") + } catch (e: SocketTimeoutException) { + NetworkResult.Error(-3, "Request Timed out! Please try again!") + } catch (e: java.lang.Exception) { + NetworkResult.Error(-4, e.message ?: "Unknown Error") + } + } + + } + suspend fun checkAndGenerateOtpForMobileNumber(req: AbhaGenerateMobileOtpRequest): NetworkResult { return withContext(Dispatchers.IO) { try { @@ -255,7 +463,14 @@ class AbhaIdRepo @Inject constructor( suspend fun verifyOtpForMobileNumber(req: AbhaVerifyMobileOtpRequest): NetworkResult { return withContext(Dispatchers.IO) { try { - val response = abhaApiService.verifyMobileOtp(req) + // ABHA v3 + req.authData.otp.otpValue = encryptData(req.authData.otp.otpValue) + req.authData.otp.timeStamp = getCurrentTimestamp() + // ABHA v1/v2 API +// val response = abhaApiService.verifyMobileOtp(req) + // ABHA v3 API + val response = + abhaApiService.verifyMobileOtp3(req, generateUUID(), getCurrentTimestamp()) if (response.isSuccessful) { val responseBody = response.body()?.string() val result = @@ -304,16 +519,16 @@ class AbhaIdRepo @Inject constructor( return abhaApiService.getPdfCard() } - private fun sendErrorResponse(response: Response): NetworkResult.Error { - return when (response.code()) { + private fun sendErrorResponse(response: Response?): NetworkResult.Error { + return when (response?.code()) { 503 -> NetworkResult.Error(503, "Service Unavailable! Please try later!") else -> { - val errorBody = response.errorBody()?.string() + val errorBody = response?.errorBody()?.string() val errorJson = JSONObject(errorBody.toString()) val detailsArray = errorJson.getJSONArray("details") val detailsObject = detailsArray.getJSONObject(0) val errorMessage = detailsObject.getString("message") - NetworkResult.Error(response.code(), errorMessage) + NetworkResult.Error(response?.code()!!, errorMessage) } } } @@ -376,7 +591,7 @@ class AbhaIdRepo @Inject constructor( NetworkResult.Success(result) } - 5000, 5002 -> { + 401, 5000, 5002 -> { if (JSONObject(responseBody).getString("errorMessage") .contentEquals("Invalid login key or session is expired") ) { @@ -407,7 +622,7 @@ class AbhaIdRepo @Inject constructor( } } - suspend fun mapHealthIDToBeneficiary(mapHIDtoBeneficiary: MapHIDtoBeneficiary): NetworkResult { + suspend fun mapHealthIDToBeneficiary(mapHIDtoBeneficiary: MapHIDtoBeneficiary, ben: BenRegCache?): NetworkResult { return withContext((Dispatchers.IO)) { try { val user = @@ -416,14 +631,80 @@ class AbhaIdRepo @Inject constructor( mapHIDtoBeneficiary.createdBy = user.userName val response = amritApiService.mapHealthIDToBeneficiary(mapHIDtoBeneficiary) val responseBody = response.body()?.string() + when (responseBody?.let { JSONObject(it).getInt("statusCode") }) { + 200 -> { + + val json = JSONObject(responseBody) + val data = json.optJSONObject("data") + Timber.d("Checkbox values : ${data}") + abhaGenerated.deleteAbhaByBenId(mapHIDtoBeneficiary.beneficiaryID!!) + + if (data != null && data.has("benHealthID") && data.has("healthId")) { + NetworkResult.Success(responseBody) + + } + + else if (data != null && data.has("response")) { + val message = data.getString("response") + + NetworkResult.Error(0, message) + } + + else { + NetworkResult.Error(0, "Unknown response format") + } + } + + 401, 5000, 5002 -> { + saveAbhaModelFromRequest(mapHIDtoBeneficiary,ben) + if (JSONObject(responseBody).getString("errorMessage") + .contentEquals("Invalid login key or session is expired") + ) { + userRepo.refreshTokenTmc(user.userName, user.password) + mapHealthIDToBeneficiary(mapHIDtoBeneficiary,ben) + } else { + NetworkResult.Error( + 0, + JSONObject(responseBody).getString("errorMessage") + ) + } + } + + else -> NetworkResult.Error(0, responseBody.toString()) + } + } catch (e: IOException) { + saveAbhaModelFromRequest(mapHIDtoBeneficiary,ben) + NetworkResult.Error(-1, "Unable to connect to Internet!") + } catch (e: JSONException) { + saveAbhaModelFromRequest(mapHIDtoBeneficiary,ben) + NetworkResult.Error(-2, "Invalid response! Please try again!") + } catch (e: SocketTimeoutException) { + saveAbhaModelFromRequest(mapHIDtoBeneficiary,ben) + NetworkResult.Error(-3, "Request Timed out! Please try again!") + } catch (e: java.lang.Exception) { + saveAbhaModelFromRequest(mapHIDtoBeneficiary,ben) + NetworkResult.Error(-4, e.message ?: "Unknown Error") + } + } + } + + suspend fun addHealthIdRecord(addHealthIdRecord: AddHealthIdRecord): NetworkResult { + return withContext((Dispatchers.IO)) { + try { + val user = + prefDao.getLoggedInUser() ?: throw IllegalStateException("No user logged in!!") + addHealthIdRecord.providerServiceMapId = user.serviceMapId + addHealthIdRecord.createdBy = user.userName + val response = amritApiService.addHealthIdRecord(addHealthIdRecord) + val responseBody = response.body()?.string() when (responseBody?.let { JSONObject(it).getInt("statusCode") }) { 200 -> NetworkResult.Success(responseBody) - 5000, 5002 -> { + 401, 5000, 5002 -> { if (JSONObject(responseBody).getString("errorMessage") .contentEquals("Invalid login key or session is expired") ) { userRepo.refreshTokenTmc(user.userName, user.password) - mapHealthIDToBeneficiary(mapHIDtoBeneficiary) + addHealthIdRecord(addHealthIdRecord) } else { NetworkResult.Error( 0, @@ -456,7 +737,7 @@ class AbhaIdRepo @Inject constructor( JSONObject(responseBody).getJSONObject("data").getString("txnId") ) - 5000, 5002 -> { + 401, 5000, 5002 -> { val error = JSONObject(responseBody).getString("errorMessage") if (error.contentEquals("Invalid login key or session is expired")) { val user = prefDao.getLoggedInUser()!! @@ -491,7 +772,7 @@ class AbhaIdRepo @Inject constructor( JSONObject(responseBody).getJSONObject("data").getString("data") ) - 5000, 5002 -> { + 401, 5000, 5002 -> { if (JSONObject(responseBody).getString("errorMessage") .contentEquals("Invalid login key or session is expired") ) { @@ -520,4 +801,26 @@ class AbhaIdRepo @Inject constructor( } } } + + suspend fun saveAbhaModelFromRequest( + mapHIDtoBeneficiary: MapHIDtoBeneficiary, + ben: BenRegCache? + ) { + val abhaProfileJson = Gson().toJson(mapHIDtoBeneficiary.ABHAProfile) + val abha = ABHAModel( + beneficiaryID = mapHIDtoBeneficiary.beneficiaryID!!, + beneficiaryRegID = mapHIDtoBeneficiary.beneficiaryRegID!!, + benName = ben?.firstName.toString(), + benSurname = ben?.lastName, + healthId = mapHIDtoBeneficiary.healthId!!, + txnId = mapHIDtoBeneficiary.txnId!!, + message = mapHIDtoBeneficiary.message!!, + createdBy = "", + providerServiceMapId = prefDao.getLoggedInUser()?.serviceMapId!!, + abhaProfileJson = abhaProfileJson, + healthIdNumber = mapHIDtoBeneficiary.healthIdNumber.toString(), + isNewAbha = mapHIDtoBeneficiary.isNew!! + ) + abhaGenerated.saveAbhaGenrated(abha) + } } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/AdolescentHealthRepo.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/AdolescentHealthRepo.kt new file mode 100644 index 000000000..b9c449c60 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/AdolescentHealthRepo.kt @@ -0,0 +1,243 @@ +package org.piramalswasthya.sakhi.repositories + +import com.google.gson.Gson +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.json.JSONObject +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.database.room.dao.AdolescentHealthDao +import org.piramalswasthya.sakhi.database.room.dao.BenDao +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao +import org.piramalswasthya.sakhi.helpers.Konstants +import org.piramalswasthya.sakhi.model.AdolescentHealthCache +import org.piramalswasthya.sakhi.model.TBScreeningCache +import org.piramalswasthya.sakhi.network.AdolescentHealthRequestDTO +import org.piramalswasthya.sakhi.network.AdolscentHealthDTO +import org.piramalswasthya.sakhi.network.AmritApiService +import org.piramalswasthya.sakhi.network.GetDataPaginatedRequest +import timber.log.Timber +import java.net.SocketTimeoutException +import java.text.SimpleDateFormat +import java.util.Locale +import javax.inject.Inject + +class AdolescentHealthRepo @Inject constructor( + private val adolescentHealthDao: AdolescentHealthDao, + private val benDao: BenDao, + private val preferenceDao: PreferenceDao, + private val userRepo: UserRepo, + private val tmcNetworkApiService: AmritApiService +) { + + suspend fun getAdolescentHealth(benId: Long): AdolescentHealthCache? { + return withContext(Dispatchers.IO) { + adolescentHealthDao.getAdolescentHealth(benId) + } + } + + suspend fun saveAdolescentHealth(adolescentHealthCache : AdolescentHealthCache) { + withContext(Dispatchers.IO) { + adolescentHealthDao.saveAdolescentHealth(adolescentHealthCache) + } + } + + + suspend fun getadolescentHealthCacheFromServer(): Int { + return withContext(Dispatchers.IO) { + val user = + preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") + val lastTimeStamp = preferenceDao.getLastSyncedTimeStamp() + try { + val response = tmcNetworkApiService.getAdolescentHealthData( + GetDataPaginatedRequest( + ashaId = user.userId, + pageNo = 0, + fromDate = BenRepo.getCurrentDate(Konstants.defaultTimeStamp), + toDate = getCurrentDate() + ) + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + + val errorMessage = jsonObj.getString("errorMessage") + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Pull from amrit adolescent screening data : $responseStatusCode") + when (responseStatusCode) { + 200 -> { + try { + val dataObj = jsonObj.getString("data") + saveadolescentHealthCacheFromResponse(dataObj) + } catch (e: Exception) { + Timber.d("Adolescent Screening entries not synced $e") + return@withContext 0 + } + + return@withContext 1 + } + + 5002 -> { + if (userRepo.refreshTokenTmc( + user.userName, user.password + ) + ) throw SocketTimeoutException("Refreshed Token!") + else throw IllegalStateException("User Logged out!!") + } + + 5000 -> { + if (errorMessage == "No record found") return@withContext 0 + } + + else -> { + throw IllegalStateException("$responseStatusCode received, dont know what todo!?") + } + } + } + } + + } catch (e: SocketTimeoutException) { + Timber.e("get_tb error : $e") + return@withContext -2 + + } catch (e: java.lang.IllegalStateException) { + Timber.e("get_tb error : $e") + return@withContext -1 + } + -1 + } + } + + private suspend fun saveadolescentHealthCacheFromResponse(dataObj: String): MutableList { + val tbScreeningList = mutableListOf() + var requestDTO = Gson().fromJson(dataObj, AdolescentHealthRequestDTO::class.java) + requestDTO?.adolescentHealths?.forEach { tbScreeningDTO -> + tbScreeningDTO.visitDate?.let { + var tbScreeningCache: AdolescentHealthCache? = + adolescentHealthDao.getAdolescentHealth( + tbScreeningDTO.benId, + getLongFromDate(tbScreeningDTO.visitDate), + getLongFromDate(tbScreeningDTO.visitDate) - 19_800_000 + ) + if (tbScreeningCache == null) { + benDao.getBen(tbScreeningDTO.benId)?.let { + adolescentHealthDao.saveAdolescentHealth(tbScreeningDTO.toCache()) + } + } + } + } + return tbScreeningList + } + + // RECORD-LEVEL ISOLATION: Coordinator always returns true so the + // WorkManager worker succeeds. Failed records stay UNSYNCED for next cycle. + suspend fun pushUnSyncedRecords(): Boolean { + val screeningResult = pushUnSyncedRecordsAdolescentScreening() + Timber.d("Adolescent Health push result: screening=$screeningResult") + // Worker succeeds — failed records stay UNSYNCED for next cycle + return true + } + + // RECORD-LEVEL ISOLATION: Adolescent Health records are now sent in + // chunks of 20 instead of one giant batch. Previously, if ANY record in + // the batch was malformed, the ENTIRE batch failed and ALL records stayed + // UNSYNCED. Now each chunk is independent — one bad chunk doesn't affect + // the others. Failed chunks' records stay UNSYNCED for the next sync cycle. + private suspend fun pushUnSyncedRecordsAdolescentScreening(): Int { + + return withContext(Dispatchers.IO) { + val user = + preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") + + val tbsnList: List = adolescentHealthDao.getAdolescentHealth(SyncState.UNSYNCED) + + if (tbsnList.isEmpty()) return@withContext 1 + + val CHUNK_SIZE = 20 + val chunks = tbsnList.chunked(CHUNK_SIZE) + var successCount = 0 + var failCount = 0 + + for (chunk in chunks) { + try { + val chunkDtos = chunk.map { it.toDTO() } + + val response = tmcNetworkApiService.saveAdolescentHealthData( + AdolescentHealthRequestDTO( + userId = user.userId, + adolescentHealths = chunkDtos + ) + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Push to Amrit Adolescent Health chunk: $responseStatusCode") + when (responseStatusCode) { + 200 -> { + updateSyncStatusScreening(chunk) + successCount += chunk.size + } + + 401, 5002 -> { + if (userRepo.refreshTokenTmc(user.userName, user.password)) { + Timber.d("Token refreshed, Adolescent Health chunk will retry next cycle") + } + failCount += chunk.size + } + + else -> { + Timber.e("Adolescent Health chunk failed with statusCode: $responseStatusCode") + failCount += chunk.size + } + } + } + } else { + Timber.e("Adolescent Health chunk HTTP error: $statusCode") + failCount += chunk.size + } + } catch (e: Exception) { + Timber.e(e, "Adolescent Health chunk push failed: ${chunk.size} records") + failCount += chunk.size + } + } + + Timber.d("Adolescent Health push complete: $successCount succeeded, $failCount failed out of ${tbsnList.size}") + return@withContext 1 + } + } + + + + private suspend fun updateSyncStatusScreening(tbsnList: List) { + tbsnList.forEach { + it.syncState = SyncState.SYNCED + adolescentHealthDao.saveAdolescentHealth(it) + } + } + + + companion object { + private val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) + private val timeFormat = SimpleDateFormat("HH:mm:ss", Locale.ENGLISH) + private fun getCurrentDate(millis: Long = System.currentTimeMillis()): String { + val dateString = dateFormat.format(millis) + val timeString = timeFormat.format(millis) + return "${dateString}T${timeString}.000Z" + } + + private fun getLongFromDate(dateString: String): Long { + //Jul 22, 2023 8:17:23 AM" + val f = SimpleDateFormat("MMM d, yyyy h:mm:ss a", Locale.ENGLISH) + val date = f.parse(dateString) + return date?.time ?: throw IllegalStateException("Invalid date for dateReg") + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/AshaProfileRepo.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/AshaProfileRepo.kt new file mode 100644 index 000000000..e82bf36e6 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/AshaProfileRepo.kt @@ -0,0 +1,164 @@ +package org.piramalswasthya.sakhi.repositories + +import com.google.gson.Gson +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.json.JSONException +import org.json.JSONObject +import org.piramalswasthya.sakhi.database.room.dao.ProfileDao +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao +import org.piramalswasthya.sakhi.model.ProfileActivityCache +import org.piramalswasthya.sakhi.model.User +import org.piramalswasthya.sakhi.network.AmritApiService +import timber.log.Timber +import java.net.SocketTimeoutException +import javax.inject.Inject + +class AshaProfileRepo @Inject constructor( + private val amritApiService: AmritApiService, + private val profileDao: ProfileDao, + private val preferenceDao: PreferenceDao, + private val userRepo: UserRepo +) { + + + suspend fun postDataToAmritServer( + benNetworkPostSet: ProfileActivityCache, + retryCount: Int = 3 + ): Boolean { + try { + val response = amritApiService.submitAshaProfileData(benNetworkPostSet) + val statusCode = response.code() + + if (statusCode == 200) { + + val responseString: String? = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + val responseStatusCode = jsonObj.getInt("statusCode") + if (responseStatusCode == 200) { + Timber.d("response : $jsonObj") + try { + val dataObj = jsonObj.getString("data") + saveProfileData(dataObj) + } catch (e: Exception) { + Timber.d("profile data not synced $e") + return false + } + return true + } else if (responseStatusCode == 5002) { + val user = preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("User not logged in according to db") + if (userRepo.refreshTokenTmc( + user.userName, user.password + ) + ) throw SocketTimeoutException("Refreshed Token!") + else throw IllegalStateException("User seems to be logged out and refresh token not working!!!!") + } + } + } + Timber.w("Bad Response from server, need to check $response ") + return false + } catch (e: SocketTimeoutException) { + Timber.e("Caught exception $e here") + if (retryCount > 0) return postDataToAmritServer( + benNetworkPostSet, retryCount - 1 + ) + Timber.e("postDataToAmritServer: max retries exhausted") + return false + } catch (e: JSONException) { + Timber.e("Caught exception $e here") + return false + } catch (e: java.lang.Exception) { + Timber.e("Caught exception $e here") + return false + } + } + + + suspend fun pullAndSaveAshaProfile(user: User, retryCount: Int = 3): Boolean { + return withContext(Dispatchers.IO) { + try { + val response = amritApiService.getAshaProfileData(user.userId) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Pull from amrit asha profile data : $responseStatusCode") + when (responseStatusCode) { + 200 -> { + try { + val dataObj = jsonObj.getString("data") + saveProfileData(dataObj) + } catch (e: Exception) { + Timber.d("profile data not synced $e") + return@withContext false + } + return@withContext true + } + + 5002 -> { + if (userRepo.refreshTokenTmc( + user.userName, user.password + ) + ) throw SocketTimeoutException("Refreshed Token!") + else throw IllegalStateException("User Logged out!!") + } + + 5000 -> { + return@withContext true + } + + else -> { + throw IllegalStateException("$responseStatusCode received, don't know what todo!?") + } + } + } + } + + } catch (e: SocketTimeoutException) { + Timber.e("profile error : $e") + if (retryCount > 0) return@withContext pullAndSaveAshaProfile(user, retryCount - 1) + Timber.e("pullAndSaveAshaProfile: max retries exhausted") + return@withContext false + } catch (e: Exception) { + Timber.d("Caught $e at incentives!") + return@withContext false + } + true + } + } + + + suspend fun getSavedRecord(id: Long): ProfileActivityCache? { + return withContext(Dispatchers.IO) { + profileDao.getProfileActivityById(id) + } + } + + suspend fun saveRecord(profileActivityCache: ProfileActivityCache) { + withContext(Dispatchers.IO) { + profileDao.insert(profileActivityCache) + } + } + private suspend fun saveProfileData(dataObj: String) { + + val activitiesCache = + Gson().fromJson(dataObj, ProfileActivityCache::class.java) as ProfileActivityCache + if (activitiesCache != null) { + // Preserve local profileImage — server stores the URI string which is + // only meaningful on this device, so always prefer the local file. + val existingRecord = profileDao.getProfileActivityById(activitiesCache.employeeId.toLong()) + if (existingRecord != null && existingRecord.profileImage.isNotEmpty()) { + activitiesCache.profileImage = existingRecord.profileImage + } + profileDao.insert(activitiesCache) + } + + + } + + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/AshaRepo.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/AshaRepo.kt new file mode 100644 index 000000000..d553f4885 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/AshaRepo.kt @@ -0,0 +1,104 @@ +package org.piramalswasthya.sakhi.repositories + +import com.google.gson.Gson +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.json.JSONObject +import org.piramalswasthya.sakhi.database.room.dao.IncentiveDao +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao +import org.piramalswasthya.sakhi.helpers.setToEndOfTheDay +import org.piramalswasthya.sakhi.model.IncentiveActivityListRequest +import org.piramalswasthya.sakhi.model.IncentiveActivityNetwork +import org.piramalswasthya.sakhi.model.IncentiveRecordListRequest +import org.piramalswasthya.sakhi.model.IncentiveRecordNetwork +import org.piramalswasthya.sakhi.model.User +import org.piramalswasthya.sakhi.model.getDateTimeStringFromLong +import org.piramalswasthya.sakhi.network.AmritApiService +import timber.log.Timber +import java.net.SocketTimeoutException +import java.util.Calendar +import javax.inject.Inject + +class AshaRepo @Inject constructor( + private val amritApiService: AmritApiService, + private val incentiveDao: IncentiveDao, + private val preferenceDao: PreferenceDao, + private val userRepo: UserRepo + +) { + + val list = incentiveDao.getAllRecords() + + + suspend fun pullAndSaveAllAshaActivities(user: User): Boolean { + return withContext(Dispatchers.IO) { + try { + val response = amritApiService.getAshaProfileData(user.userId) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + + val errorMessage = jsonObj.getString("errorMessage") + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Pull from amrit incentives data : $responseStatusCode") + when (responseStatusCode) { + 200 -> { + try { + val dataObj = jsonObj.getString("data") + saveAshaMasterData(dataObj) + } catch (e: Exception) { + Timber.d("Incentive master data not synced $e") + return@withContext false + } + + return@withContext true + } + + 5002 -> { + if (userRepo.refreshTokenTmc( + user.userName, user.password + ) + ) throw SocketTimeoutException("Refreshed Token!") + else throw IllegalStateException("User Logged out!!") + } + + 5000 -> { + if (errorMessage == "No record found") return@withContext true + } + + else -> { + throw IllegalStateException("$responseStatusCode received, don't know what todo!?") + } + } + } + } + + } catch (e: SocketTimeoutException) { + Timber.e("incentives error : $e") + pullAndSaveAllAshaActivities(user) + return@withContext true + } catch (e: Exception) { + Timber.d("Caught $e at incentives!") + return@withContext false + } + true + } + } + + private suspend fun saveAshaMasterData(dataObj: String) { + + val activities = + Gson().fromJson(dataObj, Array::class.java).toList() + + val activityList = activities.map { it.asCacheModel() } + activityList.forEach { activity -> + val activityCache = incentiveDao.getActivityById(activity.id) + if (activityCache == null) { + incentiveDao.insert(activity) + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/BenRepo.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/BenRepo.kt index 60f402f61..3e83b286f 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/repositories/BenRepo.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/BenRepo.kt @@ -1,25 +1,36 @@ package org.piramalswasthya.sakhi.repositories import android.app.Application +import android.net.Uri +import android.widget.Toast import com.google.gson.Gson +import com.google.gson.reflect.TypeToken import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext +import org.json.JSONArray import org.json.JSONException import org.json.JSONObject -import org.piramalswasthya.sakhi.configuration.BenGenRegFormDataset import org.piramalswasthya.sakhi.database.room.BeneficiaryIdsAvail import org.piramalswasthya.sakhi.database.room.SyncState import org.piramalswasthya.sakhi.database.room.dao.BenDao import org.piramalswasthya.sakhi.database.room.dao.BeneficiaryIdsAvailDao +import org.piramalswasthya.sakhi.database.room.dao.GeneralOpdDao import org.piramalswasthya.sakhi.database.room.dao.HouseholdDao +import org.piramalswasthya.sakhi.database.room.dao.dynamicSchemaDao.CUFYFormResponseJsonDao +import org.piramalswasthya.sakhi.database.room.dao.dynamicSchemaDao.FormResponseJsonDao import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao import org.piramalswasthya.sakhi.helpers.ImageUtils import org.piramalswasthya.sakhi.helpers.Konstants import org.piramalswasthya.sakhi.model.* import org.piramalswasthya.sakhi.network.* +import org.piramalswasthya.sakhi.ui.home_activity.all_ben.new_ben_registration.ben_form.NewBenRegViewModel +import org.piramalswasthya.sakhi.work.WorkerUtils import timber.log.Timber +import java.io.File import java.lang.Long.min import java.net.SocketTimeoutException import java.text.SimpleDateFormat @@ -34,9 +45,14 @@ class BenRepo @Inject constructor( private val infantRegRepo: InfantRegRepo, private val preferenceDao: PreferenceDao, private val userRepo: UserRepo, - private val tmcNetworkApiService: AmritApiService + private val generalOpdDao: GeneralOpdDao, + private val tmcNetworkApiService: AmritApiService, + private val formResponseJsonDao: FormResponseJsonDao, + private val provideCUFYFormResponseJsonDao: CUFYFormResponseJsonDao ) { + private val processNewBenMutex = Mutex() + companion object { private val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) private val timeFormat = SimpleDateFormat("HH:mm:ss", Locale.ENGLISH) @@ -53,6 +69,128 @@ class BenRepo @Inject constructor( } } + suspend fun updateBenToSync(householdId: Long, unsynced: SyncState) { + withContext(Dispatchers.IO) { + benDao.updateBenToSync(householdId = householdId,unsynced,"U",2) + } + } + + suspend fun updateHousehold(householdId: Long, unsynced: SyncState) { + withContext(Dispatchers.IO) { + benDao.updateHofSpouseAdded(householdId = householdId,unsynced,"U",2) + } + } + suspend fun updateBeneficiarySpouseAdded(householdId: Long,benID: Long,unsynced: SyncState) { + withContext(Dispatchers.IO) { + benDao.updateBeneficiarySpouseAdded(householdId = householdId, benId = benID,unsynced,"U",2) + } + } + + suspend fun updateFatherInChildren(benName: String, householdId: Long, parentName: String, unsynced: SyncState) { + withContext(Dispatchers.IO) { + benDao.updateFatherInChildren(benName = benName, householdId = householdId, parentName = parentName, unsynced, "U", 2) + } + } + + suspend fun updateMotherInChildren(benName: String, householdId: Long, parentName: String, unsynced: SyncState) { + withContext(Dispatchers.IO) { + benDao.updateMotherInChildren(benName = benName, householdId = householdId, parentName = parentName, unsynced, "U", 2) + } + } + + suspend fun updateSpouseOfHoF(benName: String, householdId: Long, spouseName: String, unsynced: SyncState) { + withContext(Dispatchers.IO) { + benDao.updateSpouseOfHoF(benName = benName, householdId = householdId, spouseName = spouseName, unsynced, "U", 2) + } + } + + suspend fun updateFather(benName: String, householdId: Long, parentName: String, unsynced: SyncState) { + withContext(Dispatchers.IO) { + benDao.updateFather(benName = benName, householdId = householdId, parentName = parentName, unsynced, "U", 2) + } + } + + suspend fun updateMother(benName: String, householdId: Long, parentName: String, unsynced: SyncState) { + withContext(Dispatchers.IO) { + benDao.updateMother(benName = benName, householdId = householdId, parentName = parentName, unsynced, "U", 2) + } + } + + suspend fun updateMarriageAgeOfWife(marriageDate: Long, ageAtMarriage: Int, householdId: Long, spouseName: String, unsynced: SyncState) { + withContext(Dispatchers.IO) { + benDao.updateMarriageAgeOfWife(marriageDate = marriageDate, ageAtMarriage = ageAtMarriage, householdId = householdId, spouseName = spouseName, unsynced, "U", 2) + } + } + + suspend fun updateMarriageAgeOfHusband(marriageDate: Long, ageAtMarriage: Int, householdId: Long, spouseName: String, unsynced: SyncState) { + withContext(Dispatchers.IO) { + benDao.updateMarriageAgeOfHusband(marriageDate = marriageDate, ageAtMarriage = ageAtMarriage, householdId = householdId, spouseName = spouseName, unsynced, "U", 2) + } + } + + suspend fun updateBabyName(babyName: String, householdId: Long, parentName: String, unsynced: SyncState) { + withContext(Dispatchers.IO) { + benDao.updateBabyName(babyName = babyName, householdId = householdId, parentName = parentName, unsynced, "U", 2) + } + } + + suspend fun updateSpouse(benName: String, householdId: Long, spouseName: String, unsynced: SyncState) { + withContext(Dispatchers.IO) { + benDao.updateSpouse(benName = benName, householdId = householdId, spouseName = spouseName, unsynced, "U", 2) + } + } + + suspend fun updateChildrenLastName(lastName: String, householdId: Long, parentName: String, unsynced: SyncState) { + withContext(Dispatchers.IO) { + benDao.updateChildrenLastName(lastName = lastName, householdId = householdId, parentName = parentName, unsynced, "U", 2) + } + } + + suspend fun updateSpouseLastName(lastName: String, householdId: Long, spouseName: String, unsynced: SyncState) { + withContext(Dispatchers.IO) { + benDao.updateSpouseLastName(lastName = lastName, householdId = householdId, spouseName = spouseName, unsynced, "U", 2) + } + } + + suspend fun updateBeneficiaryChildrenAdded( + householdId: Long, + benID: Long, + unsynced: SyncState + + ) { + withContext(Dispatchers.IO) { + benDao.updateBeneficiaryChildrenAdded(householdId = householdId, benId = benID,unsynced,"U",2) + } + } + // 1. Pregnancy death check + suspend fun hasPregnancyDeath(benId: Long): Boolean { + return benDao.checkPregnancyDeath(benId) + } + + // 2. Abortion death check + suspend fun hasAbortionDeath(benId: Long): Boolean { + return benDao.checkAbortionDeath(benId) + } + + // 3. Delivery death check + suspend fun hasDeliveryDeath(benId: Long): Boolean { + return benDao.checkDeliveryDeath(benId) + } + + // 4. PNC death check + suspend fun hasPncDeath(benId: Long): Boolean { + return benDao.checkPncDeath(benId) + } + + suspend fun isPncCauseOfDeathAccident(benId: Long,cause: String): Boolean{ + return benDao.isDeathByCause(benId,cause) + } + + suspend fun isAncCauseOfDeathAccident(benId: Long, cause:String): Boolean{ + return benDao.isDeathByCauseAnc(benId,cause) + } + + fun getBenBasicListFromHousehold(hhId: Long): Flow> { return benDao.getAllBasicBenForHousehold(hhId).map { it.map { it.asBasicDomainModel() } } @@ -63,12 +201,48 @@ class BenRepo @Inject constructor( } + suspend fun getChildCountForBen(benId: Long): Int { + return benDao.getChildCountForBen(benId) + } + + suspend fun getChildBenListFromHousehold( + hhId: Long, + selectedbenIdFromArgs: Long, + firstName: String? + ): List { + return benDao.getChildBenForHousehold(hhId,selectedbenIdFromArgs,firstName) + + } + + suspend fun getChildBelow15( + hhId: Long, + selectedbenIdFromArgs: Long, + firstName: String? + ): Int { + return benDao.getBelow15Count(hhId,selectedbenIdFromArgs,firstName) + + } + + suspend fun getChildAbove15( + hhId: Long, + selectedbenIdFromArgs: Long, + firstName: String? + ): Int { + return benDao.get15aboveCount(hhId,selectedbenIdFromArgs,firstName) + + } + + suspend fun isBenDead(benId: Long): Boolean { + return benDao.isBenDead(benId) + } + suspend fun getBenFromId(benId: Long): BenRegCache? { return withContext(Dispatchers.IO) { benDao.getBen(benId) } } + suspend fun getBenWithHRPT(benId: Long): BenWithHRPTrackingCache { return withContext(Dispatchers.IO) { benDao.getHRPTrackingPregForBen(benId) @@ -83,13 +257,49 @@ class BenRepo @Inject constructor( suspend fun persistRecord(ben: BenRegCache) { withContext(Dispatchers.IO) { - ben.userImage = ben.userImage?.let { - ImageUtils.saveBenImageFromCameraToStorage(context, it, ben.beneficiaryId) + + val originalImagePath = ben.userImage + val finalImagePath = originalImagePath?.let { imagePath -> + val uri = Uri.parse(imagePath) + + // Check if image is already in permanent storage (filesDir) + val isAlreadyPermanent = uri.scheme == "file" && uri.path?.let { + File(it).absolutePath.startsWith(context.filesDir.absolutePath) + } == true + + if (isAlreadyPermanent) { + imagePath + } else { + // Save to permanent storage (handles both content:// and file:// URIs) + val savedPath = ImageUtils.saveBenImageFromCameraToStorage( + context = context, + uriString = imagePath, + benId = ben.beneficiaryId + ) + + if (savedPath.isNullOrBlank()) { + Timber.e("Image compression/save failed for beneficiaryId=${ben.beneficiaryId}") + + // Cleanup orphaned cache file if it's a file URI + if (uri.scheme == "file") { + uri.path?.let { File(it) }?.let { file -> + runCatching { file.delete() } + .onFailure { Timber.w(it, "Failed to delete orphaned cache image") } + } + } + + throw IllegalStateException("Failed to save beneficiary image") + } + + savedPath + } } + ben.userImage = finalImagePath benDao.upsert(ben) } } + suspend fun updateRecord(ben: BenRegCache) { withContext(Dispatchers.IO) { benDao.updateBen(ben) @@ -155,18 +365,22 @@ class BenRepo @Inject constructor( ): Boolean { val sendingData = ben.asNetworkSendingModel(user, locationRecord, context) + Timber.d("Amrit push beneficiary registration: benId=${ben.beneficiaryId}, hhId=${ben.householdId}") try { val response = tmcNetworkApiService.getBenIdFromBeneficiarySending(sendingData) + val statusCode = response.code() val responseString = response.body()?.string() + Timber.d("Amrit push beneficiary registration response: httpStatus=$statusCode, benId=${ben.beneficiaryId}") if (responseString != null) { val jsonObj = JSONObject(responseString) - val errorMessage = jsonObj.getString("errorMessage") + val errorMessage = jsonObj.optString("errorMessage", "") val responseStatusCode: Int = jsonObj.getInt("statusCode") if (responseStatusCode == 200) { val jsonObjectData: JSONObject = jsonObj.getJSONObject("data") - val resBenId = jsonObjectData.getString("response") - val benNumber = resBenId.substring(resBenId.length - 12) - val newBenId = java.lang.Long.valueOf(benNumber) + val response = jsonObjectData.getString("response") + val newBenId = jsonObjectData.getString("benGenId").toLong() + val newBenRegId = jsonObjectData.getString("benRegId").toLong() + Timber.d("Amrit push beneficiary registration success: oldBenId=${ben.beneficiaryId}, newBenId=$newBenId, benRegId=$newBenRegId") //FIX TO UPDATE IMAGE-NAME WITH NEW BEN-ID val infantReg = infantRegRepo.getInfantRegFromChildBenId(ben.beneficiaryId) infantReg?.let { @@ -177,11 +391,18 @@ class BenRepo @Inject constructor( } infantReg?.let { infantRegRepo.update(it) } val photoUri = ImageUtils.renameImage(context, ben.beneficiaryId, newBenId) - benDao.updateToFinalBenId( - hhId = ben.householdId, - oldId = ben.beneficiaryId, - newId = newBenId, - imageUri = photoUri + if (newBenRegId != null) { + benDao.updateToFinalBenId( + hhId = ben.householdId, + oldId = ben.beneficiaryId, + newBenRegId = newBenRegId, + newId = newBenId, + imageUri = photoUri + ) + } + formResponseJsonDao.updateVisitBenId( + oldBenId = ben.beneficiaryId, + newBenId = newBenId ) //FIX TO MAP UPDATED BEN-ID FOR HOF householdDao.getHousehold(ben.householdId) @@ -194,29 +415,32 @@ class BenRepo @Inject constructor( return true } - if (responseStatusCode == 5002) { + Timber.e("Amrit push beneficiary registration failed: statusCode=$responseStatusCode, error=$errorMessage, benId=${ben.beneficiaryId}") + if (responseStatusCode == 5002 || responseStatusCode ==401) { if (userRepo.refreshTokenTmc( user.userName, user.password ) ) throw SocketTimeoutException("Refreshed Token") } + } else { + Timber.e("Amrit push beneficiary registration failed: response body is null, httpStatus=$statusCode, benId=${ben.beneficiaryId}") } throw IllegalStateException("Response undesired!") } catch (se: SocketTimeoutException) { if (se.message == "Refreshed Token") { return createBenIdAtServerByBeneficiarySending(ben, user, locationRecord) } + Timber.e("Amrit push beneficiary registration timeout: benId=${ben.beneficiaryId}, error=$se") return false } catch (e: java.lang.Exception) { benDao.setSyncState(ben.householdId, ben.beneficiaryId, SyncState.UNSYNCED) - Timber.d("Caugnt error $e") + Timber.e("Amrit push beneficiary registration error: benId=${ben.beneficiaryId}, error=$e") return false } - } - suspend fun processNewBen(): Boolean { - return withContext(Dispatchers.IO) { + suspend fun processNewBen(): Boolean = processNewBenMutex.withLock { + withContext(Dispatchers.IO) { val user = preferenceDao.getLoggedInUser() ?: throw IllegalStateException("No user logged in!!") @@ -230,7 +454,6 @@ class BenRepo @Inject constructor( // val cbacPostList = mutableSetOf() benList.forEach { -// val isSuccess = createBenIdAtServerByBeneficiarySending(it, user, it.locationRecord) Timber.d("YTR 429 $it") } @@ -240,17 +463,24 @@ class BenRepo @Inject constructor( benDao.setSyncState(it.householdId, it.beneficiaryId, SyncState.SYNCING) benNetworkPostList.add(it.asNetworkPostModel(context, user)) householdNetworkPostList.add( - householdDao.getHousehold(it.householdId)!!.asNetworkModel() + householdDao.getHousehold(it.householdId)!!.asNetworkModel(user) ) try { - if (it.ageUnitId != 3 || it.age < 15) kidNetworkPostList.add(it.asKidNetworkModel()) + if (it.ageUnitId != 3 || it.age < 15) kidNetworkPostList.add( + it.asKidNetworkModel( + user + ) + ) } catch (e: java.lang.Exception) { - Timber.d("caught error in adding kidDetails : $e") + Timber.e("caught error in adding kidDetails : $e") } - - } + // RECORD-LEVEL ISOLATION: BenRepo previously returned true + // regardless of upload success (Pattern C — silent success). + // Now failures are explicitly logged so they're visible in Timber + // logs. The worker still returns true (failed records are already + // marked via benSyncWithServerFailed and retry on next cycle). val uploadDone = postDataToAmritServer( benNetworkPostList, householdNetworkPostList, kidNetworkPostList, ) @@ -258,19 +488,24 @@ class BenRepo @Inject constructor( benNetworkPostList.takeIf { it.isNotEmpty() }?.map { it.benId }?.let { benDao.benSyncWithServerFailed(*it.toLongArray()) } + Timber.e("Beneficiary batch push FAILED: ${benNetworkPostList.size} ben records, ${householdNetworkPostList.size} household records") + } else { + Timber.d("Beneficiary batch push succeeded: ${benNetworkPostList.size} ben records, ${householdNetworkPostList.size} household records") } return@withContext true } - } private suspend fun postDataToAmritServer( benNetworkPostSet: MutableSet, householdNetworkPostSet: MutableSet, kidNetworkPostSet: MutableSet, -// cbacPostList: MutableSet + retryCount: Int = 3, ): Boolean { if (benNetworkPostSet.isEmpty() && householdNetworkPostSet.isEmpty() && kidNetworkPostSet.isEmpty()) return true + val benIds = benNetworkPostSet.map { it.benId } + val hhIds = householdNetworkPostSet.map { it.householdId } + Timber.d("Amrit push syncDataToAmrit: sending ${benNetworkPostSet.size} ben(s) $benIds, ${householdNetworkPostSet.size} hh(s) $hhIds, ${kidNetworkPostSet.size} kid(s)") val rmnchData = SendingRMNCHData( householdNetworkPostSet.toList(), benNetworkPostSet.toList(), @@ -280,6 +515,7 @@ class BenRepo @Inject constructor( try { val response = tmcNetworkApiService.submitRmnchDataAmrit(rmnchData) val statusCode = response.code() + Timber.d("Amrit push syncDataToAmrit response: httpStatus=$statusCode") if (statusCode == 200) { @@ -287,18 +523,85 @@ class BenRepo @Inject constructor( if (responseString != null) { val jsonObj = JSONObject(responseString) val responseStatusCode = jsonObj.getInt("statusCode") - val errorMessage = jsonObj.getString("errorMessage") + val errorMessage = jsonObj.optString("errorMessage", "") if (responseStatusCode == 200) { - Timber.d("response : $jsonObj") + Timber.d("Amrit push syncDataToAmrit success: $jsonObj") val benToUpdateList = benNetworkPostSet.takeIf { it.isNotEmpty() }?.map { it.benId } ?.toTypedArray()?.toLongArray() val hhToUpdateList = householdNetworkPostSet.takeIf { it.isNotEmpty() } ?.map { it.householdId.toLong() }?.toTypedArray()?.toLongArray() - Timber.d("ben : ${benNetworkPostSet.size}, hh: ${householdNetworkPostSet.size}") - benToUpdateList?.let { benDao.benSyncedWithServer(*it) } + Timber.d("Amrit push syncDataToAmrit marking synced: benIds=${benToUpdateList?.toList()}, hhIds=${hhToUpdateList?.toList()}") + benToUpdateList?.let { + benDao.benSyncedWithServer(*it) + Timber.d("Amrit push syncDataToAmrit DB updated: benIds=${it.toList()}") + } hhToUpdateList?.let { householdDao.householdSyncedWithServer(*it) } return true + } else if (responseStatusCode == 5002 || responseStatusCode ==401) { + val user = preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("User not logged in according to db") + if (userRepo.refreshTokenTmc( + user.userName, user.password + ) + ) throw SocketTimeoutException("Refreshed Token!") + else throw IllegalStateException("User seems to be logged out and refresh token not working!!!!") + } + Timber.e("Amrit push syncDataToAmrit failed: statusCode=$responseStatusCode, error=$errorMessage") + } else { + Timber.e("Amrit push syncDataToAmrit failed: response body is null, httpStatus=$statusCode") + } + } + Timber.w("Amrit push syncDataToAmrit bad response: httpStatus=$statusCode, benIds=$benIds") + return false + } catch (e: SocketTimeoutException) { + Timber.e("Amrit push syncDataToAmrit timeout: benIds=$benIds, error=$e") + if (retryCount > 0) return postDataToAmritServer( + benNetworkPostSet, householdNetworkPostSet, kidNetworkPostSet, retryCount - 1 + ) + Timber.e("Amrit push syncDataToAmrit: max retries exhausted") + return false + } catch (e: JSONException) { + Timber.e("Amrit push syncDataToAmrit JSON error: benIds=$benIds, error=$e") + return false + } catch (e: java.lang.Exception) { + Timber.e("Amrit push syncDataToAmrit error: benIds=$benIds, error=$e") + return false + } + } + + + suspend fun deactivateHouseHold( + benNetworkPostSet: List, + householdNetworkPostSet: HouseholdNetwork, + retryCount: Int = 3, + ): Boolean { + val user = preferenceDao.getLoggedInUser() ?: throw IllegalStateException("No user logged in!!") + val benNetworkPostList: List = + benNetworkPostSet.map { + it.asNetworkPostModel(context, user) + } + + + val rmnchData = SendingRMNCHData( + listOf(householdNetworkPostSet), + benNetworkPostList + ) + try { + val response = tmcNetworkApiService.submitRmnchDataAmrit(rmnchData) + val statusCode = response.code() + + if (statusCode == 200) { + + val responseString: String? = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + val responseStatusCode = jsonObj.getInt("statusCode") + val errorMessage = jsonObj.optString("errorMessage", "") + if (responseStatusCode == 200) { + Timber.d("response : $jsonObj") + WorkerUtils.triggerAmritPullWorker(context) + return true } else if (responseStatusCode == 5002) { val user = preferenceDao.getLoggedInUser() ?: throw IllegalStateException("User not logged in according to db") @@ -310,23 +613,85 @@ class BenRepo @Inject constructor( } } } - Timber.w("Bad Response from server, need to check $householdNetworkPostSet\n$benNetworkPostSet\n$kidNetworkPostSet $response ") + Timber.w("Bad Response from server, need to check $householdNetworkPostSet") return false } catch (e: SocketTimeoutException) { - Timber.d("Caught exception $e here") - return postDataToAmritServer( - benNetworkPostSet, householdNetworkPostSet, kidNetworkPostSet + Timber.e("Caught exception $e here") + if (retryCount > 0) return deactivateHouseHold( + benNetworkPostSet, householdNetworkPostSet, retryCount - 1 ) + Timber.e("deactivateHouseHold: max retries exhausted") + return false } catch (e: JSONException) { - Timber.d("Caught exception $e here") + Timber.e("Caught exception $e here") return false } catch (e: java.lang.Exception) { - Timber.d("Caught exception $e here") + Timber.e("Caught exception $e here") + return false + } + } + + suspend fun deactivateBeneficiary( + benNetworkPostSet: List, + retryCount: Int = 3, + ): Boolean { + val user = preferenceDao.getLoggedInUser() ?: throw IllegalStateException("No user logged in!!") + val benNetworkPostList: List = + benNetworkPostSet.map { + it.asNetworkPostModel(context, user) + } + + + val rmnchData = SendingRMNCHData( + // listOf(householdNetworkPostSet), + benficieryRegistrationData= benNetworkPostList + ) + try { + val response = tmcNetworkApiService.submitRmnchDataAmrit(rmnchData) + val statusCode = response.code() + + if (statusCode == 200) { + + val responseString: String? = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + val responseStatusCode = jsonObj.getInt("statusCode") + val errorMessage = jsonObj.optString("errorMessage", "") + if (responseStatusCode == 200) { + Timber.d("response : $jsonObj") + WorkerUtils.triggerAmritPullWorker(context) + return true + } else if (responseStatusCode == 5002) { + val user = preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("User not logged in according to db") + if (userRepo.refreshTokenTmc( + user.userName, user.password + ) + ) throw SocketTimeoutException("Refreshed Token!") + else throw IllegalStateException("User seems to be logged out and refresh token not working!!!!") + } + } + } + Timber.w("Bad Response from server, need to check $benNetworkPostList") + return false + } catch (e: SocketTimeoutException) { + Timber.e("Caught exception $e here") + if (retryCount > 0) return deactivateBeneficiary( + benNetworkPostSet, retryCount - 1 + ) + Timber.e("deactivateBeneficiary: max retries exhausted") + return false + } catch (e: JSONException) { + Timber.e("Caught exception $e here") + return false + } catch (e: java.lang.Exception) { + Timber.e("Caught exception $e here") return false } } suspend fun getBeneficiariesFromServerForWorker(pageNumber: Int): Int { + Timber.d("=====1234:getBeneficiariesFromServerForWorker : $pageNumber") return withContext(Dispatchers.IO) { val user = preferenceDao.getLoggedInUser() @@ -347,7 +712,7 @@ class BenRepo @Inject constructor( if (responseString != null) { val jsonObj = JSONObject(responseString) - val errorMessage = jsonObj.getString("errorMessage") + val errorMessage = jsonObj.optString("errorMessage", "") val responseStatusCode = jsonObj.getInt("statusCode") Timber.d("Pull from amrit page $pageNumber response status : $responseStatusCode") when (responseStatusCode) { @@ -356,6 +721,10 @@ class BenRepo @Inject constructor( val dataObj = jsonObj.getJSONObject("data") val pageSize = dataObj.getInt("totalPage") +// HelperUtil.allPagesContent.append("Page $pageNumber:\n") +// HelperUtil.allPagesContent.append(responseString) +// HelperUtil.allPagesContent.append("\n") + try { householdDao.upsert( *getHouseholdCacheFromServerResponse( @@ -367,6 +736,7 @@ class BenRepo @Inject constructor( return@withContext 0 } val benCacheList = getBenCacheFromServerResponse(responseString) + benDao.upsert(*benCacheList.toTypedArray()) // val cbacCacheList = getCbacCacheFromServerResponse(responseString) // cbacDao.upsert(*cbacCacheList.toTypedArray()) @@ -375,7 +745,7 @@ class BenRepo @Inject constructor( return@withContext pageSize } - 5002 -> { + 401,5002 -> { if (pageNumber == 0 && userRepo.refreshTokenTmc( user.userName, user.password ) @@ -384,6 +754,8 @@ class BenRepo @Inject constructor( } 5000 -> { + // HelperUtil.saveApiResponseToDownloads(context, "9864880049_getBeneficiaryData_response.txt", HelperUtil.allPagesContent.toString()) + if (errorMessage == "No record found") return@withContext 0 } @@ -395,11 +767,11 @@ class BenRepo @Inject constructor( } } catch (e: SocketTimeoutException) { - Timber.d("get_ben error : $e") + Timber.e("get_ben error : $e") return@withContext -2 } catch (e: java.lang.IllegalStateException) { - Timber.d("get_ben error : $e") + Timber.e("get_ben error : $e") return@withContext -1 } -1 @@ -428,7 +800,7 @@ class BenRepo @Inject constructor( if (responseString != null) { val jsonObj = JSONObject(responseString) - val errorMessage = jsonObj.getString("errorMessage") + val errorMessage = jsonObj.optString("errorMessage", "") val responseStatusCode = jsonObj.getInt("statusCode") if (responseStatusCode == 200) { val dataObj = jsonObj.getJSONObject("data") @@ -455,6 +827,56 @@ class BenRepo @Inject constructor( BenBasicDomain( benId = jsonObject.getLong("benficieryid"), hhId = jsonObject.getLong("houseoldId"), + +// isDeath = if (jsonObject.has("isDeath")) jsonObject.optBoolean("isDeath") else false, +// isDeathValue = jsonObject.optString("isDeath", null), +// dateOfDeath = jsonObject.optString("dateOfDeath", null), +// timeOfDeath = jsonObject.optString("timeOfDeath", null), +// reasonOfDeath = jsonObject.optString("reasonOfDeath", null), +// reasonOfDeathId = if (jsonObject.has("reasonOfDeathId")) jsonObject.optInt("reasonOfDeathId") else -1, +// placeOfDeath = jsonObject.optString("placeOfDeath", null), +// placeOfDeathId = if (jsonObject.has("placeOfDeathId")) jsonObject.optInt("placeOfDeathId") else -1, +// otherPlaceOfDeath = jsonObject.optString("otherPlaceOfDeath", null), + + isDeath = if (jsonObject.has("isDeath")) jsonObject.optBoolean( + "isDeath" + ) else false, + + isDeathValue = jsonObject.optString("isDeath", null) + .takeIf { !it.isNullOrEmpty() }, + + dateOfDeath = jsonObject.optString("dateOfDeath", null) + .takeIf { !it.isNullOrEmpty() }, + + timeOfDeath = jsonObject.optString("timeOfDeath", null) + .takeIf { !it.isNullOrEmpty() }, + + reasonOfDeath = jsonObject.optString( + "reasonOfDeath", + null + ).takeIf { !it.isNullOrEmpty() }, + + reasonOfDeathId = if (jsonObject.has("reasonOfDeathId")) { + jsonObject.optInt("reasonOfDeathId") + .takeIf { it != 0 } ?: -1 + } else -1, + + placeOfDeath = jsonObject.optString( + "placeOfDeath", + null + ).takeIf { !it.isNullOrEmpty() }, + + placeOfDeathId = if (jsonObject.has("placeOfDeathId")) { + jsonObject.optInt("placeOfDeathId") + .takeIf { it != 0 } ?: -1 + } else -1, + + otherPlaceOfDeath = jsonObject.optString( + "otherPlaceOfDeath", + null + ).takeIf { !it.isNullOrEmpty() }, + + regDate = benDataObj.getString("registrationDate"), benName = benDataObj.getString("firstName"), benSurname = benDataObj.getString("lastName"), @@ -468,7 +890,12 @@ class BenRepo @Inject constructor( // typeOfList = benDataObj.getString("registrationType"), syncState = if (benExists) SyncState.SYNCED else SyncState.SYNCING, dob = 0L, - relToHeadId = 0 + relToHeadId = 0, + isConsent = false, + isSpouseAdded = false, + isChildrenAdded = false, + isMarried = false, + reproductiveStatusId = benDataObj.getInt("reproductiveStatusId"), ) ) } @@ -490,27 +917,120 @@ class BenRepo @Inject constructor( } throw IllegalStateException("Response code !-100") } else { - Timber.d("getBenData() returned error message : $errorMessage") + Timber.e("getBenData() returned error message : $errorMessage") throw IllegalStateException("Response code !-100") } } } } catch (e: Exception) { - Timber.d("get_ben error : $e") + Timber.e("get_ben error : $e") } Timber.d("get_ben data : $benDataList") Pair(0, benDataList) } } + suspend fun getGeneralOPDBeneficiariesFromServertoWorker(pageNumber: Int): Int { + Timber.d("=====1234:getBeneficiariesFromServerForWorker : $pageNumber") + return withContext(Dispatchers.IO) { + val user = + preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") + val lastTimeStamp = preferenceDao.getLastSyncedTimeStamp() + try { + val response = tmcNetworkApiService.getgeneralOPDBeneficiaries( + GetDataPaginatedRequestForGeneralOPD( + user.userId, + user.villages[0].id, + user.userName, + user.userId, + pageNumber, + getCurrentDate(lastTimeStamp), + getCurrentDate(), + + ) + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + + val errorMessage = jsonObj.optString("errorMessage", "") + if (!jsonObj.has("statusCode")) { + Timber.e("GeneralOPD response missing statusCode. Raw response: $responseString") + return@withContext -1 + } + val responseStatusCode = jsonObj.optInt("statusCode", -1) + Timber.d("Pull from amrit page $pageNumber response status : $responseStatusCode") + when (responseStatusCode) { + 200 -> { + try { + val dataObj = jsonObj.getJSONObject("data") + val entriesArray = dataObj.getJSONArray("entries") + saveGeneralOPDData(entriesArray) + } catch (e: Exception) { + Timber.d("GeneralOPD data not synced $e") + return@withContext 0 + } + + return@withContext 1 + } + + 5002 -> { + if (pageNumber == 0 && userRepo.refreshTokenTmc( + user.userName, user.password + ) + ) throw SocketTimeoutException("Refreshed Token!") + else throw IllegalStateException("User Logged out!!") + } + + 5000 -> { + return@withContext 0 + } + + else -> { + Timber.e("GeneralOPD unexpected statusCode: $responseStatusCode, response: $responseString") + throw IllegalStateException("$responseStatusCode received, dont know what todo!?") + } + } + } + } + + } catch (e: SocketTimeoutException) { + Timber.e("get_ben error : $e") + return@withContext -2 + } catch (e: JSONException) { + Timber.e("JSON parsing error for GeneralOPD data: $e") + return@withContext -1 + } catch (e: java.lang.IllegalStateException) { + Timber.e("get_ben error : $e") + return@withContext -1 + } catch (e: Exception) { + Timber.e("get_general_opd unexpected error : $e") + return@withContext -1 + } + -1 + } + } + + private suspend fun saveGeneralOPDData(dataObj: JSONArray) { + val gson = Gson() + val type = object : TypeToken>() {}.type + val networkList: List = gson.fromJson(dataObj.toString(), type) + val entityList = networkList.map { it.asGeneralCacheModel() } + generalOpdDao.insertAll(entityList) + + } + private fun getLongFromDate(date: String): Long { val formatter = SimpleDateFormat("MMM dd, yyyy HH:mm:ss a", Locale.ENGLISH) val localDateTime = formatter.parse(date) return localDateTime?.time ?: 0 } - + var count = 0 private suspend fun getBenCacheFromServerResponse(response: String): MutableList { val jsonObj = JSONObject(response) val result = mutableListOf() @@ -524,6 +1044,8 @@ class BenRepo @Inject constructor( for (i in 0 until jsonArray.length()) { val jsonObject = jsonArray.getJSONObject(i) val benDataObj = jsonObject.getJSONObject("beneficiaryDetails") + val abhaHealthDetailsObj = jsonObject.getJSONObject("abhaHealthDetails") + // val houseDataObj = jsonObject.getJSONObject("householdDetails") // val cbacDataObj = jsonObject.getJSONObject("cbacDetails") val childDataObj = jsonObject.getJSONObject("bornbirthDeatils") @@ -531,6 +1053,11 @@ class BenRepo @Inject constructor( if (jsonObject.has("benficieryid")) jsonObject.getLong("benficieryid") else -1L val hhId = if (jsonObject.has("houseoldId")) jsonObject.getLong("houseoldId") else -1L + + if(benId == 700623622919L){ + Timber.d("====5224::BenPull benId=$benId | benExists=${benDao.getBen(hhId, benId) != null} | has doYouHavechildren=${jsonObject.has("doYouHavechildren")} val=${jsonObject.optBoolean("doYouHavechildren")} | has isMarried=${jsonObject.has("isMarried")} val=${jsonObject.optBoolean("isMarried")} | has isSpouseAdded=${jsonObject.has("isSpouseAdded")} val=${jsonObject.optBoolean("isSpouseAdded")} | has isChildrenAdded=${jsonObject.has("isChildrenAdded")} val=${jsonObject.optBoolean("isChildrenAdded")} | has noOfchildren=${jsonObject.has("noOfchildren")} val=${jsonObject.optInt("noOfchildren")}") + } + if (benId == -1L || hhId == -1L) continue val benExists = benDao.getBen(hhId, benId) != null @@ -548,9 +1075,37 @@ class BenRepo @Inject constructor( BenRegCache( householdId = jsonObject.getLong("houseoldId"), beneficiaryId = jsonObject.getLong("benficieryid"), + isDeath = if (jsonObject.has("isDeath")) jsonObject.optBoolean("isDeath") else false, + isDeathValue = jsonObject.optString("isDeath", null) + .takeIf { !it.isNullOrEmpty() }, + dateOfDeath = jsonObject.optString("dateOfDeath", null) + .takeIf { !it.isNullOrEmpty() }, + timeOfDeath = jsonObject.optString("timeOfDeath", null) + .takeIf { !it.isNullOrEmpty() }, + reasonOfDeath = jsonObject.optString("reasonOfDeath", null) + .takeIf { !it.isNullOrEmpty() }, + reasonOfDeathId = if (jsonObject.has("reasonOfDeathId")) { + jsonObject.optInt("reasonOfDeathId").takeIf { it != 0 } ?: -1 + } else -1, + + placeOfDeath = jsonObject.optString("placeOfDeath", null) + .takeIf { !it.isNullOrEmpty() }, + + placeOfDeathId = if (jsonObject.has("placeOfDeathId")) { + jsonObject.optInt("placeOfDeathId").takeIf { it != 0 } ?: -1 + } else -1, + + otherPlaceOfDeath = jsonObject.optString("otherPlaceOfDeath", null) + .takeIf { !it.isNullOrEmpty() }, + + ashaId = jsonObject.getInt("ashaId"), benRegId = jsonObject.getLong("BenRegId"), + isNewAbha = if (abhaHealthDetailsObj.has("isNewAbha")) abhaHealthDetailsObj.getBoolean( + "isNewAbha" + ) else false, age = benDataObj.getInt("age"), + isDeactivate = if (benDataObj.has("isDeactivate")) benDataObj.getBoolean("isDeactivate") else false, ageUnit = if (benDataObj.has("gender")) { when (benDataObj.getString("age_unit")) { "Years" -> AgeUnit.YEARS @@ -885,6 +1440,12 @@ class BenRepo @Inject constructor( birthOPV = if (childDataObj.has("birthOPV")) childDataObj.getBoolean( "birthOPV" ) else false, + birthCertificateFileBackView = if (childDataObj.has("birthOPV")) childDataObj.getString( + "birthOPV" + ) else "", + birthCertificateFileFrontView = if (childDataObj.has("birthOPV")) childDataObj.getString( + "birthOPV" + ) else "" ), genDetails = if (childDataObj.length() != 0) null else BenRegGen( maritalStatus = if (benDataObj.has("maritalstatus")) benDataObj.getString( @@ -959,12 +1520,25 @@ class BenRepo @Inject constructor( // ) else null, // noOfDaysForDelivery = noOfDaysForDelivery, ), - healthIdDetails = if (jsonObject.has("healthId")) BenHealthIdDetails( - jsonObject.getString("healthId"), - jsonObject.getString("healthIdNumber") - ) else null, + healthIdDetails = if (abhaHealthDetailsObj != null && abhaHealthDetailsObj.length() > 0) { + BenHealthIdDetails( + healthIdNumber = abhaHealthDetailsObj.getString("HealthIdNumber"), + isNewAbha = if (abhaHealthDetailsObj.has("isNewAbha")) abhaHealthDetailsObj.getBoolean( + "isNewAbha" + ) else false, + healthId = abhaHealthDetailsObj.getString("HealthID") + ) + + } else null, syncState = SyncState.SYNCED, - isDraft = false + isDraft = false, + isConsent = false, + isSpouseAdded = if (jsonObject.has("isSpouseAdded")) jsonObject.optBoolean("isSpouseAdded") else false, + isChildrenAdded = if (jsonObject.has("isChildrenAdded")) jsonObject.optBoolean("isChildrenAdded") else false, + isMarried = if (jsonObject.has("isMarried")) jsonObject.optBoolean("isMarried") else false, + doYouHavechildren = if (jsonObject.has("doYouHavechildren")) jsonObject.optBoolean("doYouHavechildren") else false, + noOfAliveChildren = if (jsonObject.has("noofAlivechildren")) jsonObject.optInt("noofAlivechildren") else 0, + noOfChildren = if (jsonObject.has("noOfchildren")) jsonObject.optInt("noOfchildren") else 0, ) ) @@ -1003,6 +1577,11 @@ class BenRepo @Inject constructor( ) }, ${benDataObj.getString("reproductiveStatus")}" )*/ + +// if (benDataObj.has("benficieryid")){ +// count++ +// Timber.d("====050224,::$count") +// } } catch (e: JSONException) { Timber.e("Beneficiary skipped: ${jsonObject.getLong("benficieryid")} with error $e") } catch (e: NumberFormatException) { @@ -1139,6 +1718,9 @@ class BenRepo @Inject constructor( // updatedTimeStamp = houseDataObj.getString("other_houseType"), processed = "P", isDraft = false, + isDeactivate = if (houseDataObj.has("isDeactivate")) houseDataObj.getBoolean( + "isDeactivate" + ) else false ) ) } catch (e: JSONException) { @@ -1171,7 +1753,7 @@ class BenRepo @Inject constructor( } } - 5000, 5002 -> { + 401,5000, 5002 -> { if (JSONObject(responseBody).getString("errorMessage") .contentEquals("Invalid login key or session is expired") ) { @@ -1196,6 +1778,117 @@ class BenRepo @Inject constructor( return null } + + suspend fun sendOtp(mobileNo: String): SendOtpResponse? { + try { + var sendOtp = sendOtpRequest(mobileNo) + val response = tmcNetworkApiService.sendOtp(sendOtp) + if (response.isSuccessful) { + val responseBody = response.body()?.string() + when (responseBody?.let { JSONObject(it).getInt("statusCode") }) { + 200 -> { + val jsonObj = JSONObject(responseBody) + val data = jsonObj.getJSONObject("data").toString() + val response = Gson().fromJson(data, SendOtpResponse::class.java) + Toast.makeText(context,"Otp sent successfully",Toast.LENGTH_SHORT).show() + return response + } + + 5000, 5002 -> { + if (JSONObject(responseBody).getString("errorMessage") + .contentEquals("Invalid login key or session is expired") + ) { + val user = preferenceDao.getLoggedInUser()!! + userRepo.refreshTokenTmc(user.userName, user.password) + + } else { + NetworkResult.Error( + 0, + JSONObject(responseBody).getString("errorMessage") + ) + } + } + + else -> { + NetworkResult.Error(0, responseBody.toString()) + } + } + } + } catch (_: java.lang.Exception) { + } + return null + } + suspend fun resendOtp(mobileNo: String): SendOtpResponse? { + try { + var sendOtp = sendOtpRequest(mobileNo) + val response = tmcNetworkApiService.resendOtp(sendOtp) + if (response.isSuccessful) { + val responseBody = response.body()?.string() + when (responseBody?.let { JSONObject(it).getInt("statusCode") }) { + 200 -> { + val jsonObj = JSONObject(responseBody) + val data = jsonObj.getJSONObject("data").toString() + val response = Gson().fromJson(data, SendOtpResponse::class.java) + Toast.makeText(context,"Otp sent successfully",Toast.LENGTH_SHORT).show() + return response + } + + 5000, 5002 -> { + if (JSONObject(responseBody).getString("errorMessage") + .contentEquals("Invalid login key or session is expired") + ) { + val user = preferenceDao.getLoggedInUser()!! + userRepo.refreshTokenTmc(user.userName, user.password) + + } else { + NetworkResult.Error( + 0, + JSONObject(responseBody).getString("errorMessage") + ) + } + } + + else -> { + NetworkResult.Error(0, responseBody.toString()) + } + } + } + } catch (_: java.lang.Exception) { + } + return null + } + + suspend fun verifyOtp(mobileNo: String,otp:Int): ValidateOtpResponse? { + + var validateOtp = ValidateOtpRequest(otp,mobileNo) + val response = tmcNetworkApiService.validateOtp(validateOtp) + if (response.isSuccessful) { + val responseBody = response.body()?.string() + when (responseBody?.let { JSONObject(it).getInt("statusCode") }) { + 200 -> { + val jsonObj = JSONObject(responseBody) + val data = jsonObj.getJSONObject("data").toString() + val myresponse = Gson().fromJson(responseBody, ValidateOtpResponse::class.java) + NewBenRegViewModel.isOtpVerified = true + return myresponse + } + + 5000, 5002 -> { + Toast.makeText(context,"Please enter valid OTP.",Toast.LENGTH_SHORT).show() + + } + + else -> { + NetworkResult.Error(0, responseBody.toString()) + } + } + } + + return null + } + + + suspend fun getMinBenId(): Long { return withContext(Dispatchers.IO) { benDao.getMinBenId() ?: 0L diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/CbacRepo.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/CbacRepo.kt index 97430417d..914d36585 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/repositories/CbacRepo.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/CbacRepo.kt @@ -2,30 +2,36 @@ package org.piramalswasthya.sakhi.repositories import android.content.Context import android.content.res.Resources +import com.google.gson.Gson import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import org.json.JSONException import org.json.JSONObject import org.piramalswasthya.sakhi.database.room.InAppDb +import org.piramalswasthya.sakhi.database.room.NcdReferalDao import org.piramalswasthya.sakhi.database.room.SyncState import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao -import org.piramalswasthya.sakhi.helpers.Konstants import org.piramalswasthya.sakhi.model.BenRegCache import org.piramalswasthya.sakhi.model.CbacCache +import org.piramalswasthya.sakhi.model.CbacRequest +import org.piramalswasthya.sakhi.model.CbacResponseDto +import org.piramalswasthya.sakhi.model.CbacVisitDetails +import org.piramalswasthya.sakhi.model.VisitDetailsWrapper +import org.piramalswasthya.sakhi.model.toEntity import org.piramalswasthya.sakhi.network.AmritApiService -import org.piramalswasthya.sakhi.network.GetDataPaginatedRequest -import org.piramalswasthya.sakhi.network.getLongFromDate +import org.piramalswasthya.sakhi.network.GetCBACRequest +import org.piramalswasthya.sakhi.work.WorkerUtils import timber.log.Timber -import java.util.concurrent.TimeUnit import javax.inject.Inject class CbacRepo @Inject constructor( - @ApplicationContext context: Context, + @ApplicationContext var context: Context, private val database: InAppDb, private val userRepo: UserRepo, private val amritApiService: AmritApiService, - private val prefDao: PreferenceDao + private val prefDao: PreferenceDao, + private val referalDao: NcdReferalDao, + ) { private val resources: Resources @@ -34,6 +40,11 @@ class CbacRepo @Inject constructor( resources = context.resources } + suspend fun updateReferStatus(benId: Long,status: Boolean) { + database.cbacDao.updateReferralStatus(benId,status) + + } + suspend fun saveCbacData(cbacCache: CbacCache, ben: BenRegCache): Boolean { return withContext(Dispatchers.IO) { @@ -70,7 +81,7 @@ class CbacRepo @Inject constructor( database.benDao.updateBen(ben) true } catch (e: java.lang.Exception) { - Timber.d("Error : $e raised at saveCbacData") + Timber.e("Error : $e raised at saveCbacData") false } } @@ -85,33 +96,41 @@ class CbacRepo @Inject constructor( } + + suspend fun pullAndPersistCbacRecord(page: Int = 0): Int { - val userId = prefDao.getLoggedInUser()?.userId!! - val response = amritApiService.getCbacs( - userDetail = - GetDataPaginatedRequest( - ashaId = userId, - pageNo = page, - fromDate = BenRepo.getCurrentDate(Konstants.defaultTimeStamp), - toDate = BenRepo.getCurrentDate(System.currentTimeMillis()) - ) + val userName = prefDao.getLoggedInUser()?.userName!! + val cbacRequest = GetCBACRequest(userName) + + val response = amritApiService.getCbacData( + cbacRequest ) val body = response.body()?.string()?.let { JSONObject(it) } - body?.getInt("statusCode")?.takeIf { it == 5002 }?.let { + body?.getInt("statusCode")?.takeIf { it == 5002 ||it ==401 }?.let { val user = prefDao.getLoggedInUser()!! userRepo.refreshTokenTmc(user.userName, user.password) pullAndPersistCbacRecord(page) } - val cbacList = body?.let { - getCbacCacheFromServerResponse(it) - } - cbacList?.filter { - !database.cbacDao.sameCreateDateExists(it.createdDate, TimeUnit.SECONDS.toMillis(1)) - && database.benDao.getBen(it.benId) != null - }?.let { - database.cbacDao.upsert(*it.toTypedArray()) + val dataArray = body?.optJSONArray("data") + + if (dataArray != null && dataArray.length() > 0) { + val gson = Gson() + val cbacEntities = mutableListOf() + + for (i in 0 until dataArray.length()) { + val item = dataArray.getJSONObject(i) + val dto = gson.fromJson(item.toString(), CbacResponseDto::class.java) + cbacEntities.add(dto.toEntity()) + } + val existingBenIds = database.benDao.getExistingBenIds(cbacEntities.map { it.benId }) + val validEntities = cbacEntities.filter { it.benId in existingBenIds } + if (validEntities.isNotEmpty()) { + database.cbacDao.insertAll(validEntities) + } + + } - return if (page == 0) body?.let { getNumPages(it) } ?: 0 else 0 + return 0 } private fun getNumPages(body: JSONObject): Int { @@ -120,34 +139,102 @@ class CbacRepo @Inject constructor( suspend fun pushAndUpdateCbacRecord() { val unProcessedList = database.cbacDao.getAllUnprocessedCbac() - val list = unProcessedList - .map { it.cbac.asPostModel(it.hhId, it.benGender, resources = resources) } - val response = list.takeIf { it.isNotEmpty() }?.let { - amritApiService.postCbacs(list = list) + + val cbacDataList = unProcessedList.mapNotNull { record -> + val cbacPostModel = record.cbac.asPostModel(record.hhId, record.benGender, resources) + record.cbac.benId?.let { benId -> + Pair(benId, cbacPostModel) + } } - response?.body()?.string()?.let { body -> - val jsonBody = JSONObject(body) - val array = jsonBody.getJSONArray("data") - for (i in 0 until array.length()) { - val item = array.getJSONObject(i) - val isSuccess = item.getString("status") == "Success" - if (isSuccess) { - val benId = item.getLong("benId") - val createdDate = getLongFromDate(item.getString("createdDate")) - unProcessedList.firstOrNull { - compareLocalWithServer( - it.cbac.createdDate, - createdDate - ) && it.cbac.benId == benId - } - ?.let { - it.cbac.Processed = "P" - it.cbac.syncState = SyncState.SYNCED - database.cbacDao.update(it.cbac) + + if (cbacDataList.isEmpty()) return + + // RECORD-LEVEL ISOLATION: Each CBAC record is processed + // independently with try/catch. If one record fails, remaining + // records continue. Failed records stay UNSYNCED for next cycle. + var successCount = 0 + var failCount = 0 + + for ((benId, cbac) in cbacDataList) { + try { + val request = CbacRequest( + visitDetails = VisitDetailsWrapper( + visitDetails = CbacVisitDetails( + beneficiaryRegID = benId, + providerServiceMapID = prefDao.getLoggedInUser()!!.serviceMapId, + visitNo = null, + visitReason = "New Chief Complaint", + visitCategory = "NCD screening", + IdrsOrCbac = "CBAC", + createdBy = prefDao.getLoggedInUser()?.userName.toString(), + vanID = prefDao.getLoggedInUser()?.vanId!!, + parkingPlaceID = prefDao.getLoggedInUser()?.serviceMapId!!, + subVisitCategory = null, + pregnancyStatus = null, + followUpForFpMethod = null, + sideEffects = null, + otherSideEffects = null, + fileIDs = null, + reportFilePath = null, + otherFollowUpForFpMethod = null, + rCHID = null, + healthFacilityType = null, + healthFacilityLocation = null + ) + ), + cbac = cbac, + benFlowID = benId, + beneficiaryID = benId, + sessionID = 3, + parkingPlaceID = prefDao.getLoggedInUser()?.serviceMapId, + createdBy = prefDao.getLoggedInUser()?.userName.toString(), + vanID = prefDao.getLoggedInUser()?.vanId, + beneficiaryRegID = benId, + benVisitID = null, + providerServiceMapID = prefDao.getLoggedInUser()?.serviceMapId, + isFlw = true + ) + + val response = amritApiService.postCbacs(request) + + response?.body()?.string()?.let { body -> + val jsonBody = JSONObject(body) + + val isSuccess = jsonBody.getString("status") == "Success" + + if (isSuccess) { + val matchingRecord = unProcessedList.firstOrNull { it.cbac.benId == benId } + matchingRecord?.let { record -> + updateSyncStatusCbac(record.cbac) + } + val dataObject = jsonBody.getJSONObject("data") + val visitCode = dataObject.getString("visitCode") + val benVisitID = dataObject.getString("benVisitID") + val referRecord = referalDao.getReferalFromBenId(benId) + referRecord?.let { refer -> + refer.visitCode = visitCode.toLongOrNull() + refer.benVisitID = benVisitID.toLongOrNull() + referalDao.update(refer) } + successCount++ + } else { + failCount++ + } } + } catch (e: Exception) { + Timber.e(e, "CBAC push failed for benId: $benId") + failCount++ } } + + Timber.d("CBAC push complete: $successCount succeeded, $failCount failed out of ${cbacDataList.size}") + } + + + private suspend fun updateSyncStatusCbac(cbac: CbacCache) { + cbac.syncState = SyncState.SYNCED + cbac.Processed = "P" + database.cbacDao.upsert(cbac) } private fun compareLocalWithServer(local: Long, server: Long): Boolean { @@ -156,156 +243,158 @@ class CbacRepo @Inject constructor( return localRounded == serverRemovingTimezoneOffset } - private fun getCbacCacheFromServerResponse(body: JSONObject): MutableList { - val jsonObj = body.getJSONObject("data") - val result = mutableListOf() - - val responseStatusCode = body.getInt("statusCode") - if (responseStatusCode == 200) { - val jsonArray = jsonObj.getJSONArray("data") - - if (jsonArray.length() != 0) { - for (i in 0 until jsonArray.length()) { - val cbacDataObj = jsonArray.getJSONObject(i) -// val cbacDataObj = jsonObject.getJSONObject("cbacDetails") - val benId = - if (cbacDataObj.has("beneficiaryId")) cbacDataObj.getLong("beneficiaryId") else 0L -// val hhId = -// if (jsonObject.has("houseoldId")) jsonObject.getLong("houseoldId") else -1L -// if (benId == -1L || hhId == -1L) continue - if (benId == 0L || jsonArray.length() == 0) continue - - - try { - result.add( - CbacCache( - benId = cbacDataObj.getLong("beneficiaryId"), -// hhId = ben.householdId, - ashaId = prefDao.getLoggedInUser()?.userId - ?: throw IllegalStateException("logged in user not found!"), -// gender = ben.gender!!, - fillDate = getLongFromDate( - if (cbacDataObj.has("filledDate")) cbacDataObj.getString( - "filledDate" - ) else cbacDataObj.getString("createdDate") - ), - cbac_age_posi = cbacDataObj.getInt("cbacAgePosi"), - cbac_smoke_posi = cbacDataObj.getInt("cbacSmokePosi"), - cbac_alcohol_posi = cbacDataObj.getInt("cbacAlcoholPosi"), - cbac_waist_posi = cbacDataObj.getInt("cbacWaistPosi"), - cbac_pa_posi = cbacDataObj.getInt("cbacPaPosi"), - cbac_familyhistory_posi = cbacDataObj.getInt("cbacFamilyhistoryPosi"), - total_score = cbacDataObj.getInt("totalScore"), - cbac_sufferingtb_pos = cbacDataObj.getInt("cbacSufferingtbPos"), - cbac_antitbdrugs_pos = cbacDataObj.getInt("cbacAntitbdrugsPos"), - cbac_tbhistory_pos = cbacDataObj.getInt("cbacTbhistoryPos"), - cbac_sortnesofbirth_pos = cbacDataObj.getInt("cbacSortnesofbirthPos"), - cbac_coughing_pos = cbacDataObj.getInt("cbacCoughingPos"), - cbac_bloodsputum_pos = cbacDataObj.getInt("cbacBloodsputumPos"), - cbac_fivermore_pos = cbacDataObj.getInt("cbacFivermorePos"), - cbac_loseofweight_pos = cbacDataObj.getInt("cbacLoseofweightPos"), - cbac_nightsweats_pos = cbacDataObj.getInt("cbacNightsweatsPos"), - cbac_historyoffits_pos = cbacDataObj.getInt("cbacHistoryoffitsPos"), - cbac_difficultyinmouth_pos = cbacDataObj.getInt("cbacDifficultyinmouthPos"), - cbac_uicers_pos = cbacDataObj.getInt("cbacUicersPos"), - cbac_toneofvoice_pos = cbacDataObj.getInt("cbacToneofvoicePos"), - cbac_lumpinbreast_pos = cbacDataObj.getInt("cbacLumpinbreastPos"), - cbac_blooddischage_pos = cbacDataObj.getInt("cbacBlooddischagePos"), - cbac_changeinbreast_pos = cbacDataObj.getInt("cbacChangeinbreastPos"), - cbac_bleedingbtwnperiods_pos = cbacDataObj.getInt("cbacBleedingbtwnperiodsPos"), - cbac_bleedingaftermenopause_pos = cbacDataObj.getInt("cbacBleedingaftermenopausePos"), - cbac_bleedingafterintercourse_pos = cbacDataObj.getInt("cbacBleedingafterintercoursePos"), - cbac_foulveginaldischarge_pos = cbacDataObj.getInt("cbacFoulveginaldischargePos"), - cbac_growth_in_mouth_posi = cbacDataObj.getInt("cbacGrowthInMouthPosi"), - cbac_Pain_while_chewing_posi = cbacDataObj.getInt("cbacPainWhileChewingPosi"), - cbac_hyper_pigmented_patch_posi = cbacDataObj.getInt("cbacHyperPigmentedPatchPosi"), - cbac_any_thickend_skin_posi = cbacDataObj.getInt("cbacAnyThickendSkinPosi"), - cbac_nodules_on_skin_posi = cbacDataObj.getInt("cbacNodulesOnSkinPosi"), - cbac_numbness_on_palm_posi = cbacDataObj.getInt("cbacNumbnessOnPalmPosi"), - cbac_clawing_of_fingers_posi = cbacDataObj.getInt("cbacClawingOfFingersPosi"), - cbac_tingling_or_numbness_posi = cbacDataObj.getInt("cbacTinglingOrNumbnessPosi"), - cbac_inability_close_eyelid_posi = cbacDataObj.getInt("cbacInabilityCloseEyelidPosi"), - cbac_diff_holding_obj_posi = cbacDataObj.getInt("cbacDiffHoldingObjPosi"), - cbac_weekness_in_feet_posi = cbacDataObj.getInt("cbacWeeknessInFeetPosi"), - cbac_fuel_used_posi = cbacDataObj.getInt("cbacFuelUsedPosi"), - cbac_occupational_exposure_posi = cbacDataObj.getInt("cbacOccupationalExposurePosi"), - cbac_little_interest_posi = cbacDataObj.getInt("cbacLittleInterestPosi"), - cbac_feeling_down_posi = cbacDataObj.getInt("cbacFeelingDownPosi"), - cbac_little_interest_score = cbacDataObj.getInt("cbacLittleInterestScore"), - cbac_feeling_down_score = cbacDataObj.getInt("cbacFeelingDownScore"), -//START TODO() - cbac_tingling_palm_posi = if (cbacDataObj.has("cbacTinglingPalmPosi")) cbacDataObj.getInt( - "cbacTinglingPalmPosi" - ) else 0, - cbac_cloudy_posi = if (cbacDataObj.has("cbacCloudyPosi")) cbacDataObj.getInt( - "cbacCloudyPosi" - ) else 0, - cbac_white_or_red_patch_posi = if (cbacDataObj.has("cbacWhiteOrRedPatchPosi")) cbacDataObj.getInt( - "cbacWhiteOrRedPatchPosi" - ) else 0, - cbac_diffreading_posi = if (cbacDataObj.has("cbacDiffreadingPosi")) cbacDataObj.getInt( - "cbacDiffreadingPosi" - ) else 0, - cbac_pain_ineyes_posi = if (cbacDataObj.has("cbacPainIneyesPosi")) cbacDataObj.getInt( - "cbacPainIneyesPosi" - ) else 0, - cbac_redness_ineyes_posi = if (cbacDataObj.has("cbacRednessIneyesPosi")) cbacDataObj.getInt( - "cbacRednessIneyesPosi" - ) else 0, - cbac_diff_inhearing_posi = if (cbacDataObj.has("cbacDiffInhearingPosi")) cbacDataObj.getInt( - "cbacDiffInhearingPosi" - ) else 0, - cbac_feeling_unsteady_posi = if (cbacDataObj.has("cbacFeelingUnsteadyPosi")) cbacDataObj.getInt( - "cbacFeelingUnsteadyPosi" - ) else 0, - cbac_suffer_physical_disability_posi = if (cbacDataObj.has("cbacSufferPhysicalDisabilityPosi")) cbacDataObj.getInt( - "cbacSufferPhysicalDisabilityPosi" - ) else 0, - cbac_needing_help_posi = if (cbacDataObj.has("cbacNeedingHelpPosi")) cbacDataObj.getInt( - "cbacNeedingHelpPosi" - ) else 0, - cbac_forgetting_names_posi = if (cbacDataObj.has("cbacForgettingNamesPosi")) cbacDataObj.getInt( - "cbacForgettingNamesPosi" - ) else 0, -//END TODO() - cbac_referpatient_mo = cbacDataObj.getInt("cbacReferpatientMo") - .toString(), - cbac_tracing_all_fm = cbacDataObj.getInt("cbacTracingAllFm") - .toString(), - cbac_sputemcollection = cbacDataObj.getInt("cbacSputemcollection") - .toString(), - serverUpdatedStatus = cbacDataObj.getInt("serverUpdatedStatus"), - createdBy = cbacDataObj.getString("createdBy"), - createdDate = getLongFromDate(cbacDataObj.getString("createdDate")), - ProviderServiceMapID = cbacDataObj.getInt("providerServiceMapId"), -// VanID = if (cbacDataObj.has("vanID")) cbacDataObj.getInt("vanID") else user.vanId, - Countyid = cbacDataObj.getInt("countryid"), - stateid = cbacDataObj.getInt("stateid"), - districtid = cbacDataObj.getInt("districtid"), - villageid = cbacDataObj.getInt("villageid"), - cbac_reg_id = if (cbacDataObj.has("BenRegId")) cbacDataObj.getLong("BenRegId") else 1L, -// suspected_hrp = cbacDataObj.getString("suspectedHrp"), -// confirmed_hrp = cbacDataObj.getString("confirmedHrp"), -// suspected_ncd = cbacDataObj.getString("suspectedNcd"), -// confirmed_ncd = cbacDataObj.getString("confirmedNcd"), -// suspected_tb = cbacDataObj.getString("suspectedTb"), -// confirmed_tb = cbacDataObj.getString("confirmedTb"), -// suspected_ncd_diseases = cbacDataObj.getString("suspectedNcdDiseases"), -// diagnosis_status = cbacDataObj.getString("confirmedTb"), - Processed = "P",//cbacDataObj.getString("Processed"), - syncState = SyncState.SYNCED, - ) - ) - } catch (e: JSONException) { - Timber.i("Cbac skipped: ${cbacDataObj.getLong("beneficiaryId")} with error $e") - } catch (e: Exception) { - Timber.i("Cbac skipped: ${cbacDataObj.getLong("beneficiaryId")} with error $e") - } - } - } - } - return result - } +// private fun getCbacCacheFromServerResponse(body: JSONObject): MutableList { +// val jsonObj = body.getJSONObject("data") +// val result = mutableListOf() +// +// val responseStatusCode = body.getInt("statusCode") +// if (responseStatusCode == 200) { +// val jsonArray = jsonObj.getJSONArray("data") +// +// if (jsonArray.length() != 0) { +// for (i in 0 until jsonArray.length()) { +// val cbacDataObj = jsonArray.getJSONObject(i) +//// val cbacDataObj = jsonObject.getJSONObject("cbacDetails") +// val benId = +// if (cbacDataObj.has("beneficiaryId")) cbacDataObj.getLong("beneficiaryId") else 0L +//// val hhId = +//// if (jsonObject.has("houseoldId")) jsonObject.getLong("houseoldId") else -1L +//// if (benId == -1L || hhId == -1L) continue +// if (benId == 0L || jsonArray.length() == 0) continue +// val dto = Gson().fromJson(cbacDataObj.toString(), CbacResponseDto::class.java) +// +// +// try { +// +// result.add( +// dto.toEntity() +// CbacCache( +//// hhId = ben.householdId, +// ashaId = prefDao.getLoggedInUser()?.userId +// ?: throw IllegalStateException("logged in user not found!"), +//// gender = ben.gender!!, +// fillDate = getLongFromDate( +// if (cbacDataObj.has("filledDate")) cbacDataObj.getString( +// "filledDate" +// ) else cbacDataObj.getString("createdDate") +// ), +// cbac_age_posi = cbacDataObj.getInt("cbacAgeScore"), +// cbac_smoke_posi = cbacDataObj.getInt("cbacConsumeGutkaScore"), +// cbac_alcohol_posi = cbacDataObj.getInt("cbacAlcoholScore"), +// cbac_waist_posi = cbacDataObj.getInt("cbacWaistMaleScore"), +// cbac_pa_posi = cbacDataObj.getInt("cbacPhysicalActivityScore"), +// cbac_familyhistory_posi = cbacDataObj.getInt("cbacFamilyHistoryBpdiabetesScore"), +// total_score = cbacDataObj.getInt("totalScore"), +// cbac_sufferingtb_pos =if (cbacDataObj.getString("cbacTb").equals("yes", true)) 1 else 0,, +// cbac_antitbdrugs_pos = cbacDataObj.getInt("cbacAntitbdrugsPos"), +// cbac_tbhistory_pos = cbacDataObj.getInt("cbacTbhistoryPos"), +// cbac_sortnesofbirth_pos = cbacDataObj.getInt("cbacSortnesofbirthPos"), +// cbac_coughing_pos = cbacDataObj.getInt("cbacCoughingPos"), +// cbac_bloodsputum_pos = cbacDataObj.getInt("cbacBloodsputumPos"), +// cbac_fivermore_pos = cbacDataObj.getInt("cbacFivermorePos"), +// cbac_loseofweight_pos = cbacDataObj.getInt("cbacLoseofweightPos"), +// cbac_nightsweats_pos = cbacDataObj.getInt("cbacNightsweatsPos"), +// cbac_historyoffits_pos = cbacDataObj.getInt("cbacHistoryoffitsPos"), +// cbac_difficultyinmouth_pos = cbacDataObj.getInt("cbacDifficultyinmouthPos"), +// cbac_uicers_pos = cbacDataObj.getInt("cbacUicersPos"), +// cbac_toneofvoice_pos = cbacDataObj.getInt("cbacToneofvoicePos"), +// cbac_lumpinbreast_pos = cbacDataObj.getInt("cbacLumpinbreastPos"), +// cbac_blooddischage_pos = cbacDataObj.getInt("cbacBlooddischagePos"), +// cbac_changeinbreast_pos = cbacDataObj.getInt("cbacChangeinbreastPos"), +// cbac_bleedingbtwnperiods_pos = cbacDataObj.getInt("cbacBleedingbtwnperiodsPos"), +// cbac_bleedingaftermenopause_pos = cbacDataObj.getInt("cbacBleedingaftermenopausePos"), +// cbac_bleedingafterintercourse_pos = cbacDataObj.getInt("cbacBleedingafterintercoursePos"), +// cbac_foulveginaldischarge_pos = cbacDataObj.getInt("cbacFoulveginaldischargePos"), +// cbac_growth_in_mouth_posi = cbacDataObj.getInt("cbacGrowthInMouthPosi"), +// cbac_Pain_while_chewing_posi = cbacDataObj.getInt("cbacPainWhileChewingPosi"), +// cbac_hyper_pigmented_patch_posi = cbacDataObj.getInt("cbacHyperPigmentedPatchPosi"), +// cbac_any_thickend_skin_posi = cbacDataObj.getInt("cbacAnyThickendSkinPosi"), +// cbac_nodules_on_skin_posi = cbacDataObj.getInt("cbacNodulesOnSkinPosi"), +// cbac_numbness_on_palm_posi = cbacDataObj.getInt("cbacNumbnessOnPalmPosi"), +// cbac_clawing_of_fingers_posi = cbacDataObj.getInt("cbacClawingOfFingersPosi"), +// cbac_tingling_or_numbness_posi = cbacDataObj.getInt("cbacTinglingOrNumbnessPosi"), +// cbac_inability_close_eyelid_posi = cbacDataObj.getInt("cbacInabilityCloseEyelidPosi"), +// cbac_diff_holding_obj_posi = cbacDataObj.getInt("cbacDiffHoldingObjPosi"), +// cbac_weekness_in_feet_posi = cbacDataObj.getInt("cbacWeeknessInFeetPosi"), +// cbac_fuel_used_posi = cbacDataObj.getInt("cbacFuelUsedPosi"), +// cbac_occupational_exposure_posi = cbacDataObj.getInt("cbacOccupationalExposurePosi"), +// cbac_little_interest_posi = cbacDataObj.getInt("cbacLittleInterestPosi"), +// cbac_feeling_down_posi = cbacDataObj.getInt("cbacFeelingDownPosi"), +// cbac_little_interest_score = cbacDataObj.getInt("cbacLittleInterestScore"), +// cbac_feeling_down_score = cbacDataObj.getInt("cbacFeelingDownScore"), +////START TODO() +// cbac_tingling_palm_posi = if (cbacDataObj.has("cbacTinglingPalmPosi")) cbacDataObj.getInt( +// "cbacTinglingPalmPosi" +// ) else 0, +// cbac_cloudy_posi = if (cbacDataObj.has("cbacCloudyPosi")) cbacDataObj.getInt( +// "cbacCloudyPosi" +// ) else 0, +// cbac_white_or_red_patch_posi = if (cbacDataObj.has("cbacWhiteOrRedPatchPosi")) cbacDataObj.getInt( +// "cbacWhiteOrRedPatchPosi" +// ) else 0, +// cbac_diffreading_posi = if (cbacDataObj.has("cbacDiffreadingPosi")) cbacDataObj.getInt( +// "cbacDiffreadingPosi" +// ) else 0, +// cbac_pain_ineyes_posi = if (cbacDataObj.has("cbacPainIneyesPosi")) cbacDataObj.getInt( +// "cbacPainIneyesPosi" +// ) else 0, +// cbac_redness_ineyes_posi = if (cbacDataObj.has("cbacRednessIneyesPosi")) cbacDataObj.getInt( +// "cbacRednessIneyesPosi" +// ) else 0, +// cbac_diff_inhearing_posi = if (cbacDataObj.has("cbacDiffInhearingPosi")) cbacDataObj.getInt( +// "cbacDiffInhearingPosi" +// ) else 0, +// cbac_feeling_unsteady_posi = if (cbacDataObj.has("cbacFeelingUnsteadyPosi")) cbacDataObj.getInt( +// "cbacFeelingUnsteadyPosi" +// ) else 0, +// cbac_suffer_physical_disability_posi = if (cbacDataObj.has("cbacSufferPhysicalDisabilityPosi")) cbacDataObj.getInt( +// "cbacSufferPhysicalDisabilityPosi" +// ) else 0, +// cbac_needing_help_posi = if (cbacDataObj.has("cbacNeedingHelpPosi")) cbacDataObj.getInt( +// "cbacNeedingHelpPosi" +// ) else 0, +// cbac_forgetting_names_posi = if (cbacDataObj.has("cbacForgettingNamesPosi")) cbacDataObj.getInt( +// "cbacForgettingNamesPosi" +// ) else 0, +////END TODO() +// cbac_referpatient_mo = cbacDataObj.getInt("cbacReferpatientMo") +// .toString(), +// cbac_tracing_all_fm = cbacDataObj.getInt("cbacTracingAllFm") +// .toString(), +// cbac_sputemcollection = cbacDataObj.getInt("cbacSputemcollection") +// .toString(), +// serverUpdatedStatus = cbacDataObj.getInt("serverUpdatedStatus"), +// createdBy = cbacDataObj.getString("createdBy"), +// createdDate = getLongFromDate(cbacDataObj.getString("createdDate")), +// ProviderServiceMapID = cbacDataObj.getInt("providerServiceMapId"), +//// VanID = if (cbacDataObj.has("vanID")) cbacDataObj.getInt("vanID") else user.vanId, +// Countyid = cbacDataObj.getInt("countryid"), +// stateid = cbacDataObj.getInt("stateid"), +// districtid = cbacDataObj.getInt("districtid"), +// villageid = cbacDataObj.getInt("villageid"), +// cbac_reg_id = if (cbacDataObj.has("BenRegId")) cbacDataObj.getLong("BenRegId") else 1L, +//// suspected_hrp = cbacDataObj.getString("suspectedHrp"), +//// confirmed_hrp = cbacDataObj.getString("confirmedHrp"), +//// suspected_ncd = cbacDataObj.getString("suspectedNcd"), +//// confirmed_ncd = cbacDataObj.getString("confirmedNcd"), +//// suspected_tb = cbacDataObj.getString("suspectedTb"), +//// confirmed_tb = cbacDataObj.getString("confirmedTb"), +//// suspected_ncd_diseases = cbacDataObj.getString("suspectedNcdDiseases"), +//// diagnosis_status = cbacDataObj.getString("confirmedTb"), +// Processed = "P",//cbacDataObj.getString("Processed"), +// syncState = SyncState.SYNCED, +//// ) +// ) +// } catch (e: JSONException) { +// Timber.i("Cbac skipped: ${cbacDataObj.getLong("beneficiaryId")} with error $e") +// } catch (e: Exception) { +// Timber.i("Cbac skipped: ${cbacDataObj.getLong("beneficiaryId")} with error $e") +// } +// } +// } +// } +// return result +// } suspend fun getLastFilledCbac(benId: Long): CbacCache? { return withContext(Dispatchers.IO) { @@ -315,4 +404,5 @@ class CbacRepo @Inject constructor( } + } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/CdrRepo.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/CdrRepo.kt index f73fd2d98..32088ad6e 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/repositories/CdrRepo.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/CdrRepo.kt @@ -31,7 +31,7 @@ class CdrRepo @Inject constructor( cdrDao.upsert(cdrCache) true } catch (e: Exception) { - Timber.d("Error : $e raised at saveCdrData") + Timber.e("Error : $e raised at saveCdrData") false } } @@ -43,25 +43,44 @@ class CdrRepo @Inject constructor( val cdrPostList = mutableSetOf() + // RECORD-LEVEL ISOLATION: Each CDR record is processed + // independently with try/catch. Previously this method silently + // returned true even when records failed to upload (Pattern C). + // Now failures are logged and tracked while still allowing the + // worker to succeed (failed records retry on next sync cycle). + var successCount = 0 + var failCount = 0 + cdrList.forEach { - cdrPostList.clear() - cdrPostList.add(it.asPostModel()) - it.syncState = SyncState.SYNCING - cdrDao.update(it) - val uploadDone = postCdrForm(cdrPostList.toList()) - if (uploadDone) { - it.processed = "P" - it.syncState = SyncState.SYNCED - } else { + try { + cdrPostList.clear() + cdrPostList.add(it.asPostModel()) + it.syncState = SyncState.SYNCING + cdrDao.update(it) + val uploadDone = postCdrForm(cdrPostList.toList()) + if (uploadDone) { + it.processed = "P" + it.syncState = SyncState.SYNCED + successCount++ + } else { + it.syncState = SyncState.UNSYNCED + failCount++ + } + cdrDao.update(it) + } catch (e: Exception) { + Timber.e(e, "CDR push failed for record") it.syncState = SyncState.UNSYNCED + cdrDao.update(it) + failCount++ } - cdrDao.update(it) } + + Timber.d("CDR push complete: $successCount succeeded, $failCount failed out of ${cdrList.size}") return@withContext true } } - private suspend fun postCdrForm(cdrPostList: List): Boolean { + private suspend fun postCdrForm(cdrPostList: List, retryCount: Int = 3): Boolean { if (cdrPostList.isEmpty()) return false val user = preferenceDao.getLoggedInUser() @@ -78,7 +97,7 @@ class CdrRepo @Inject constructor( val errormessage = jsonObj.getString("errorMessage") if (jsonObj.isNull("statusCode")) throw IllegalStateException("Amrit server not responding properly, Contact Service Administrator!!") - val responsestatuscode = jsonObj.getInt("responseStatusCode") + val responsestatuscode = jsonObj.getInt("statusCode") when (responsestatuscode) { 200 -> { @@ -86,7 +105,7 @@ class CdrRepo @Inject constructor( return true } - 5002 -> { + 401,5002 -> { if (userRepo.refreshTokenTmc( user.userName, user.password @@ -106,14 +125,17 @@ class CdrRepo @Inject constructor( } } else { //server_resp5(); + Timber.w("Bad Response from server $statusCode $response ") } Timber.w("Bad Response from server, need to check $cdrPostList $response ") return false } catch (e: SocketTimeoutException) { - Timber.d("Caught exception $e here") - return postCdrForm(cdrPostList) + Timber.e("Caught exception $e here") + if (retryCount > 0) return postCdrForm(cdrPostList, retryCount - 1) + Timber.e("postCdrForm: max retries exhausted") + return false } catch (e: JSONException) { - Timber.d("Caught exception $e here") + Timber.e("Caught exception $e here") return false } @@ -156,7 +178,7 @@ class CdrRepo @Inject constructor( return@withContext 1 } - 5002 -> { + 401, 5002 -> { if (userRepo.refreshTokenTmc( user.userName, user.password ) @@ -176,11 +198,11 @@ class CdrRepo @Inject constructor( } } catch (e: SocketTimeoutException) { - Timber.d("get_pnc error : $e") + Timber.e("get_pnc error : $e") return@withContext -2 } catch (e: java.lang.IllegalStateException) { - Timber.d("get_pnc error : $e") + Timber.e("get_pnc error : $e") return@withContext -1 } -1 diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/DeliveryOutcomeRepo.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/DeliveryOutcomeRepo.kt index 186e4f460..4fa3ab94b 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/repositories/DeliveryOutcomeRepo.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/DeliveryOutcomeRepo.kt @@ -1,5 +1,7 @@ package org.piramalswasthya.sakhi.repositories +import android.app.Application +import android.util.Log import com.google.gson.Gson import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -25,6 +27,7 @@ import java.util.concurrent.TimeUnit import javax.inject.Inject class DeliveryOutcomeRepo @Inject constructor( + private val context: Application, private val preferenceDao: PreferenceDao, private val amritApiService: AmritApiService, private val userRepo: UserRepo, @@ -53,25 +56,39 @@ class DeliveryOutcomeRepo @Inject constructor( val deliveryOutcomePostList = mutableSetOf() + // RECORD-LEVEL ISOLATION: Each delivery outcome record is + // processed independently. If one record fails, remaining records continue. + // Failed records stay UNSYNCED and retry on next sync cycle. + var successCount = 0 + var failCount = 0 + deliveryOutcomeList.forEach { - deliveryOutcomePostList.clear() - val ben = benDao.getBen(it.benId) - ?: throw IllegalStateException("No beneficiary exists for benId: ${it.benId}!!") - deliveryOutcomePostList.add(it.asPostModel()) - it.syncState = SyncState.SYNCING - deliveryOutcomeDao.updateDeliveryOutcome(it) - val uploadDone = postDataToAmritServer(deliveryOutcomePostList) - if (uploadDone) { - it.processed = "P" - it.syncState = SyncState.SYNCED - } else { + try { + deliveryOutcomePostList.clear() + val ben = benDao.getBen(it.benId) + ?: throw IllegalStateException("No beneficiary exists for benId: ${it.benId}!!") + deliveryOutcomePostList.add(it.asPostModel()) + it.syncState = SyncState.SYNCING + deliveryOutcomeDao.updateDeliveryOutcome(it) + val uploadDone = postDataToAmritServer(deliveryOutcomePostList) + if (uploadDone) { + it.processed = "P" + it.syncState = SyncState.SYNCED + successCount++ + } else { + it.syncState = SyncState.UNSYNCED + failCount++ + } + deliveryOutcomeDao.updateDeliveryOutcome(it) + } catch (e: Exception) { + Timber.e(e, "Delivery outcome push failed for benId: ${it.benId}") it.syncState = SyncState.UNSYNCED + deliveryOutcomeDao.updateDeliveryOutcome(it) + failCount++ } - deliveryOutcomeDao.updateDeliveryOutcome(it) - if (!uploadDone) - return@withContext false } + Timber.d("Delivery outcome push complete: $successCount succeeded, $failCount failed out of ${deliveryOutcomeList.size}") return@withContext true } } @@ -83,6 +100,8 @@ class DeliveryOutcomeRepo @Inject constructor( ?: throw IllegalStateException("No user logged in!!") try { +// HelperUtil.deliveryOutcomeRepo.append("API getCalled with request ${deliveryOutcomePostList.toList()}\n") +// HelperUtil.deliveryOutcomeRepo.append("\n") val response = amritApiService.postDeliveryOutcomeForm(deliveryOutcomePostList.toList()) val statusCode = response.code() @@ -100,10 +119,18 @@ class DeliveryOutcomeRepo @Inject constructor( when (responsestatuscode) { 200 -> { Timber.d("Saved Successfully to server") +// HelperUtil.deliveryOutcomeRepo.append("Throwing 200:$responseString\n") +// HelperUtil.deliveryOutcomeRepo.append("\n") +// HelperUtil.deliveryOutcomeRepoMethod(context, "deliveryOutcomeRepoMethod.txt", HelperUtil.deliveryOutcomeRepo.toString()) + return true } - 5002 -> { + 401, 5002 -> { +// HelperUtil.deliveryOutcomeRepo.append("Throwing 5002\n") +// HelperUtil.deliveryOutcomeRepo.append("\n") +// HelperUtil.deliveryOutcomeRepoMethod(context, "deliveryOutcomeRepoMethod.txt", HelperUtil.deliveryOutcomeRepo.toString()) + if (userRepo.refreshTokenTmc( user.userName, user.password @@ -112,28 +139,56 @@ class DeliveryOutcomeRepo @Inject constructor( } else -> { +// HelperUtil.deliveryOutcomeRepo.append("Throwing away IO eXcEpTiOn\n") +// HelperUtil.deliveryOutcomeRepo.append("\n") +// HelperUtil.deliveryOutcomeRepoMethod(context, "deliveryOutcomeRepoMethod.txt", HelperUtil.deliveryOutcomeRepo.toString()) + throw IOException("Throwing away IO eXcEpTiOn") } } } } catch (e: SocketTimeoutException) { - Timber.d("Caught exception $e here") + Timber.e("Caught exception $e here") +// HelperUtil.deliveryOutcomeRepo.append("Caught exception:SocketTimeoutException $e \n") +// HelperUtil.deliveryOutcomeRepo.append("\n") +// HelperUtil.deliveryOutcomeRepoMethod(context, "deliveryOutcomeRepoMethod.txt", HelperUtil.deliveryOutcomeRepo.toString()) + return postDataToAmritServer(deliveryOutcomePostList) } catch (e: IOException) { +// HelperUtil.deliveryOutcomeRepo.append("Caught exception:IOException $e \n") +// HelperUtil.deliveryOutcomeRepo.append("\n") +// HelperUtil.deliveryOutcomeRepoMethod(context, "deliveryOutcomeRepoMethod.txt", HelperUtil.deliveryOutcomeRepo.toString()) + e.printStackTrace() } catch (e: Exception) { +// HelperUtil.deliveryOutcomeRepo.append("Caught exception:Exception $e \n") +// HelperUtil.deliveryOutcomeRepo.append("\n") +// HelperUtil.deliveryOutcomeRepoMethod(context, "deliveryOutcomeRepoMethod.txt", HelperUtil.deliveryOutcomeRepo.toString()) + e.printStackTrace() } } else { //server_resp5(); } +// HelperUtil.deliveryOutcomeRepo.append("Bad Response from server, need to check $deliveryOutcomePostList $response\n") +// HelperUtil.deliveryOutcomeRepo.append("\n") +// HelperUtil.deliveryOutcomeRepoMethod(context, "deliveryOutcomeRepoMethod.txt", HelperUtil.deliveryOutcomeRepo.toString()) + Timber.w("Bad Response from server, need to check $deliveryOutcomePostList $response ") return false } catch (e: SocketTimeoutException) { - Timber.d("Caught exception $e here") + Timber.e("Caught exception $e here") +// HelperUtil.deliveryOutcomeRepo.append("Caught exception SocketTimeOut $e \n") +// HelperUtil.deliveryOutcomeRepo.append("\n") +// HelperUtil.deliveryOutcomeRepoMethod(context, "deliveryOutcomeRepoMethod.txt", HelperUtil.deliveryOutcomeRepo.toString()) + return postDataToAmritServer(deliveryOutcomePostList) } catch (e: JSONException) { - Timber.d("Caught exception $e here") + Timber.e("Caught exception $e here") +// HelperUtil.deliveryOutcomeRepo.append("Caught exception JSONException $e \n") +// HelperUtil.deliveryOutcomeRepo.append("\n") +// HelperUtil.deliveryOutcomeRepoMethod(context, "deliveryOutcomeRepoMethod.txt", HelperUtil.deliveryOutcomeRepo.toString()) + return false } } @@ -175,7 +230,7 @@ class DeliveryOutcomeRepo @Inject constructor( return@withContext 1 } - 5002 -> { + 401,5002 -> { if (userRepo.refreshTokenTmc( user.userName, user.password ) @@ -195,11 +250,11 @@ class DeliveryOutcomeRepo @Inject constructor( } } catch (e: SocketTimeoutException) { - Timber.d("get_delivery_outcome error : $e") + Timber.e("get_delivery_outcome error : $e") return@withContext -2 } catch (e: java.lang.IllegalStateException) { - Timber.d("get_delivery_outcome error : $e") + Timber.e("get_delivery_outcome error : $e") return@withContext -1 } -1 @@ -207,14 +262,27 @@ class DeliveryOutcomeRepo @Inject constructor( } private suspend fun saveDeliveryOutcomeCacheFromResponse(dataObj: String): List { - var deliveryOutcomeList = + val deliveryOutcomeList = Gson().fromJson(dataObj, Array::class.java).toList() deliveryOutcomeList.forEach { deliveryOutcome -> deliveryOutcome.createdDate?.let { - var deliveryOutcomeCache: DeliveryOutcomeCache? = - deliveryOutcomeDao.getDeliveryOutcome(deliveryOutcome.benId) - if (deliveryOutcomeCache == null) { - deliveryOutcomeDao.saveDeliveryOutcome(deliveryOutcome.toDeliveryCache()) + try { + if (deliveryOutcome.benId <= 0) { + Timber.w("Skipping delivery outcome with invalid benId: ${deliveryOutcome.benId}") + return@let + } + val ben = benDao.getBen(deliveryOutcome.benId) + if (ben == null) { + Timber.w("Skipping delivery outcome for benId ${deliveryOutcome.benId} - beneficiary not found locally") + return@let + } + val deliveryOutcomeCache = + deliveryOutcomeDao.getDeliveryOutcome(deliveryOutcome.benId) + if (deliveryOutcomeCache == null) { + deliveryOutcomeDao.saveDeliveryOutcome(deliveryOutcome.toDeliveryCache()) + } + } catch (e: Exception) { + Timber.e(e, "Failed to save delivery outcome for benId: ${deliveryOutcome.benId}") } } } @@ -252,4 +320,7 @@ class DeliveryOutcomeRepo @Inject constructor( return "${dateString}T${timeString}.000Z" } } -} \ No newline at end of file +} + + + diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/EcrRepo.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/EcrRepo.kt index e627f6656..1f4517443 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/repositories/EcrRepo.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/EcrRepo.kt @@ -1,5 +1,6 @@ package org.piramalswasthya.sakhi.repositories +import android.app.Application import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.json.JSONArray @@ -29,8 +30,10 @@ class EcrRepo @Inject constructor( private val userRepo: UserRepo, private val database: InAppDb, private val preferenceDao: PreferenceDao, - private val tmcNetworkApiService: AmritApiService -) { + private val tmcNetworkApiService: AmritApiService, + private val context: Application, + + ) { suspend fun persistRecord(ecrForm: EligibleCoupleRegCache) { withContext(Dispatchers.IO) { @@ -49,6 +52,11 @@ class EcrRepo @Inject constructor( database.ecrDao.getSavedECR(benId) } } + suspend fun getNoOfChildren(benId: Long): Int? { + return withContext(Dispatchers.IO) { + database.ecrDao.getNoOfChildren(benId) + } + } suspend fun getEct(benId: Long, createdDate: Long): EligibleCoupleTrackingCache? { return withContext(Dispatchers.IO) { @@ -67,41 +75,55 @@ class EcrRepo @Inject constructor( val ecrList = database.ecrDao.getAllUnprocessedECR() val ecrPostList = mutableSetOf() + // RECORD-LEVEL ISOLATION: Each ECR record is processed + // independently. If one record fails, remaining records continue. + // Failed records stay UNSYNCED and retry on next sync cycle. + var successCount = 0 + var failCount = 0 + ecrList.forEach { - ecrPostList.clear() - val ben = database.benDao.getBen(it.benId) - ?: throw IllegalStateException("No beneficiary exists for benId: ${it.benId}!!") - - val ecrPost = it.asPostModel() - val cache = database.hrpDao.getNonPregnantAssess(ben.beneficiaryId) - cache?.let { - ecrPost.misCarriage = cache.misCarriage - ecrPost.homeDelivery = cache.homeDelivery - ecrPost.medicalIssues = cache.medicalIssues - ecrPost.pastCSection = cache.pastCSection - ecrPost.isHighRisk = cache.isHighRisk - } + try { + ecrPostList.clear() + val ben = database.benDao.getBen(it.benId) + ?: throw IllegalStateException("No beneficiary exists for benId: ${it.benId}!!") + + val ecrPost = it.asPostModel(context) + val cache = database.hrpDao.getNonPregnantAssess(ben.beneficiaryId) + cache?.let { + ecrPost.misCarriage = cache.misCarriage + ecrPost.homeDelivery = cache.homeDelivery + ecrPost.medicalIssues = cache.medicalIssues + ecrPost.pastCSection = cache.pastCSection + ecrPost.isHighRisk = cache.isHighRisk + } - ecrPostList.add(ecrPost) - it.syncState = SyncState.SYNCING - database.ecrDao.update(it) - val uploadDone = postECRDataToAmritServer(ecrPostList) - if (uploadDone) { - it.processed = "P" - it.syncState = SyncState.SYNCED - } else { + ecrPostList.add(ecrPost) + it.syncState = SyncState.SYNCING + database.ecrDao.update(it) + val uploadDone = postECRDataToAmritServer(ecrPostList) + if (uploadDone) { + it.processed = "P" + it.syncState = SyncState.SYNCED + successCount++ + } else { + it.syncState = SyncState.UNSYNCED + failCount++ + } + database.ecrDao.update(it) + } catch (e: Exception) { + Timber.e(e, "ECR push failed for benId: ${it.benId}") it.syncState = SyncState.UNSYNCED + database.ecrDao.update(it) + failCount++ } - database.ecrDao.update(it) - if (!uploadDone) - return@withContext false } + Timber.d("ECR push complete: $successCount succeeded, $failCount failed out of ${ecrList.size}") return@withContext true } } - suspend fun postECRDataToAmritServer(ecrPostList: MutableSet): Boolean { + suspend fun postECRDataToAmritServer(ecrPostList: MutableSet, retryCount: Int = 3): Boolean { if (ecrPostList.isEmpty()) return false val user = @@ -128,7 +150,7 @@ class EcrRepo @Inject constructor( return true } - 5002 -> { + 401, 5002 -> { if (userRepo.refreshTokenTmc( user.userName, user.password ) @@ -151,10 +173,12 @@ class EcrRepo @Inject constructor( Timber.w("Bad Response from server, need to check $ecrPostList $response ") return false } catch (e: SocketTimeoutException) { - Timber.d("Caught exception $e here") - return postECRDataToAmritServer(ecrPostList) + Timber.e("Caught exception $e here") + if (retryCount > 0) return postECRDataToAmritServer(ecrPostList, retryCount - 1) + Timber.e("postECRDataToAmritServer: max retries exhausted") + return false } catch (e: JSONException) { - Timber.d("Caught exception $e here") + Timber.e("Caught exception $e here") return false } } @@ -168,28 +192,42 @@ class EcrRepo @Inject constructor( val ectPostList = mutableSetOf() + // RECORD-LEVEL ISOLATION: Each ECT record is processed + // independently. If one record fails, remaining records continue. + // Failed records stay UNSYNCED and retry on next sync cycle. + var successCount = 0 + var failCount = 0 + ectList.forEach { - ectPostList.clear() - ectPostList.add(it) - it.syncState = SyncState.SYNCING - database.ecrDao.updateEligibleCoupleTracking(it) - val uploadDone = postECTDataToAmritServer(ectPostList) - if (uploadDone) { - it.processed = "P" - it.syncState = SyncState.SYNCED - } else { + try { + ectPostList.clear() + ectPostList.add(it) + it.syncState = SyncState.SYNCING + database.ecrDao.updateEligibleCoupleTracking(it) + val uploadDone = postECTDataToAmritServer(ectPostList) + if (uploadDone) { + it.processed = "P" + it.syncState = SyncState.SYNCED + successCount++ + } else { + it.syncState = SyncState.UNSYNCED + failCount++ + } + database.ecrDao.updateEligibleCoupleTracking(it) + } catch (e: Exception) { + Timber.e(e, "ECT push failed for record") it.syncState = SyncState.UNSYNCED + database.ecrDao.updateEligibleCoupleTracking(it) + failCount++ } - database.ecrDao.updateEligibleCoupleTracking(it) - if (!uploadDone) - return@withContext false } + Timber.d("ECT push complete: $successCount succeeded, $failCount failed out of ${ectList.size}") return@withContext true } } - private suspend fun postECTDataToAmritServer(ectPostList: MutableSet): Boolean { + private suspend fun postECTDataToAmritServer(ectPostList: MutableSet, retryCount: Int = 3): Boolean { if (ectPostList.isEmpty()) return false val user = @@ -216,7 +254,7 @@ class EcrRepo @Inject constructor( return true } - 5002 -> { + 401, 5002 -> { if (userRepo.refreshTokenTmc( user.userName, user.password ) @@ -239,15 +277,17 @@ class EcrRepo @Inject constructor( Timber.w("Bad Response from server, need to check $ectPostList $response ") return false } catch (e: SocketTimeoutException) { - Timber.d("Caught exception $e here") - return postECTDataToAmritServer(ectPostList) + Timber.e("Caught exception $e here") + if (retryCount > 0) return postECTDataToAmritServer(ectPostList, retryCount - 1) + Timber.e("postECTDataToAmritServer: max retries exhausted") + return false } catch (e: JSONException) { - Timber.d("Caught exception $e here") + Timber.e("Caught exception $e here") return false } } - suspend fun pullAndPersistEcrRecord(): Int { + suspend fun pullAndPersistEcrRecord(retryCount: Int = 3): Int { return withContext(Dispatchers.IO) { val user = preferenceDao.getLoggedInUser() ?: throw IllegalStateException("No user logged in!!") @@ -276,7 +316,7 @@ class EcrRepo @Inject constructor( ecrList.forEach { ecr -> database.benDao.getBen(ecr.benId)?.let { - if (database.ecrDao.getSavedECR(ecr.benId) == null) { + if (database.ecrDao.getSavedECR(ecr.benId) == null) { database.ecrDao.upsert(ecr) } } @@ -312,7 +352,7 @@ class EcrRepo @Inject constructor( return@withContext 1 } - 5002 -> { + 401,5002 -> { if (userRepo.refreshTokenTmc( user.userName, user.password ) @@ -332,11 +372,12 @@ class EcrRepo @Inject constructor( } } catch (e: SocketTimeoutException) { - Timber.d("get_ect error : $e") - pullAndPersistEcrRecord() - + Timber.e("get_ect error : $e") + if (retryCount > 0) return@withContext pullAndPersistEcrRecord(retryCount - 1) + Timber.e("pullAndPersistEcrRecord: max retries exhausted") + return@withContext -1 } catch (e: java.lang.IllegalStateException) { - Timber.d("get_ect error : $e") + Timber.e("get_ect error : $e") return@withContext -1 } -1 @@ -344,7 +385,7 @@ class EcrRepo @Inject constructor( } - suspend fun pullAndPersistEctRecord(): Int { + suspend fun pullAndPersistEctRecord(retryCount: Int = 3): Int { return withContext(Dispatchers.IO) { val user = preferenceDao.getLoggedInUser() ?: throw IllegalStateException("No user logged in!!") @@ -384,7 +425,7 @@ class EcrRepo @Inject constructor( return@withContext 1 } - 5002 -> { + 401, 5002 -> { if (userRepo.refreshTokenTmc( user.userName, user.password ) @@ -404,11 +445,12 @@ class EcrRepo @Inject constructor( } } catch (e: SocketTimeoutException) { - Timber.d("get_ect error : $e") - pullAndPersistEctRecord() - + Timber.e("get_ect error : $e") + if (retryCount > 0) return@withContext pullAndPersistEctRecord(retryCount - 1) + Timber.e("pullAndPersistEctRecord: max retries exhausted") + return@withContext -1 } catch (e: java.lang.IllegalStateException) { - Timber.d("get_ect error : $e") + Timber.e("get_ect error : $e") return@withContext -1 } -1 @@ -433,6 +475,12 @@ class EcrRepo @Inject constructor( ) else getLongFromDate( ecrJson.getString("createdDate") ), + lmpDate = + if (ecrJson.has("lmpDate")) { + getLongFromLmpDateOrNull(ecrJson.getString("lmpDate")) ?: 0L + } else { + 0L + }, bankAccount = if (ecrJson.has("bankAccountNumber")) ecrJson.getLong("bankAccountNumber") else null, bankName = if (ecrJson.has("bankName")) ecrJson.getString("bankName") else null, branchName = if (ecrJson.has("branchName")) ecrJson.getString("branchName") else null, @@ -441,7 +489,7 @@ class EcrRepo @Inject constructor( // noOfLiveChildren = if (ecrJson.has("noOfLiveChildren")) ecrJson.getInt("noOfLiveChildren") else 0, // noOfMaleChildren = if (ecrJson.has("noOfMaleChildren")) ecrJson.getInt("noOfMaleChildren") else 0, // noOfFemaleChildren = if (ecrJson.has("noOfFemaleChildren")) ecrJson.getInt("noOfFemaleChildren") else 0, - dob1 = if (ecrJson.has("dob1")) getLongFromDate(ecrJson.getString("dob1")) else null, + dob1 = if (ecrJson.has("dob1")) getLongFromDateOrNull(ecrJson.getString("dob1")) else null, age1 = if (ecrJson.has("age1")) ecrJson.getInt("age1") else null, gender1 = if (ecrJson.has("gender1")) ecrJson.getString("gender1") .uppercase() @@ -452,7 +500,7 @@ class EcrRepo @Inject constructor( marriageFirstChildGap = if (ecrJson.has("marriageFirstChildGap")) ecrJson.getInt( "marriageFirstChildGap" ) else null, - dob2 = if (ecrJson.has("dob2")) getLongFromDate(ecrJson.getString("dob2")) else null, + dob2 = if (ecrJson.has("dob2")) getLongFromDateOrNull(ecrJson.getString("dob2")) else null, age2 = if (ecrJson.has("age2")) ecrJson.getInt("age2") else null, gender2 = if (ecrJson.has("gender2")) ecrJson.getString("gender2") .uppercase() @@ -463,7 +511,7 @@ class EcrRepo @Inject constructor( firstAndSecondChildGap = if (ecrJson.has("firstAndSecondChildGap")) ecrJson.getInt( "firstAndSecondChildGap" ) else null, - dob3 = if (ecrJson.has("dob3")) getLongFromDate(ecrJson.getString("dob3")) else null, + dob3 = if (ecrJson.has("dob3")) getLongFromDateOrNull(ecrJson.getString("dob3")) else null, age3 = if (ecrJson.has("age3")) ecrJson.getInt("age3") else null, gender3 = if (ecrJson.has("gender3")) ecrJson.getString("gender3") .uppercase() @@ -474,7 +522,7 @@ class EcrRepo @Inject constructor( secondAndThirdChildGap = if (ecrJson.has("secondAndThirdChildGap")) ecrJson.getInt( "secondAndThirdChildGap" ) else null, - dob4 = if (ecrJson.has("dob4")) getLongFromDate(ecrJson.getString("dob4")) else null, + dob4 = if (ecrJson.has("dob4")) getLongFromDateOrNull(ecrJson.getString("dob4")) else null, age4 = if (ecrJson.has("age4")) ecrJson.getInt("age4") else null, gender4 = if (ecrJson.has("gender4")) ecrJson.getString("gender4") .uppercase() @@ -485,7 +533,7 @@ class EcrRepo @Inject constructor( thirdAndFourthChildGap = if (ecrJson.has("thirdAndFourthChildGap")) ecrJson.getInt( "thirdAndFourthChildGap" ) else null, - dob5 = if (ecrJson.has("dob5")) getLongFromDate(ecrJson.getString("dob5")) else null, + dob5 = if (ecrJson.has("dob5")) getLongFromDateOrNull(ecrJson.getString("dob5")) else null, age5 = if (ecrJson.has("age5")) ecrJson.getInt("age5") else null, gender5 = if (ecrJson.has("gender5")) ecrJson.getString("gender5") .uppercase() @@ -496,7 +544,7 @@ class EcrRepo @Inject constructor( fourthAndFifthChildGap = if (ecrJson.has("fourthAndFifthChildGap")) ecrJson.getInt( "fourthAndFifthChildGap" ) else null, - dob6 = if (ecrJson.has("dob6")) getLongFromDate(ecrJson.getString("dob6")) else null, + dob6 = if (ecrJson.has("dob6")) getLongFromDateOrNull(ecrJson.getString("dob6")) else null, age6 = if (ecrJson.has("age6")) ecrJson.getInt("age6") else null, gender6 = if (ecrJson.has("gender6")) ecrJson.getString("gender6") .uppercase() @@ -507,7 +555,7 @@ class EcrRepo @Inject constructor( fifthANdSixthChildGap = if (ecrJson.has("fifthANdSixthChildGap")) ecrJson.getInt( "fifthANdSixthChildGap" ) else null, - dob7 = if (ecrJson.has("dob7")) getLongFromDate(ecrJson.getString("dob7")) else null, + dob7 = if (ecrJson.has("dob7")) getLongFromDateOrNull(ecrJson.getString("dob7")) else null, age7 = if (ecrJson.has("age7")) ecrJson.getInt("age7") else null, gender7 = if (ecrJson.has("gender7")) ecrJson.getString("gender7") .uppercase() @@ -518,7 +566,7 @@ class EcrRepo @Inject constructor( sixthAndSeventhChildGap = if (ecrJson.has("sixthAndSeventhChildGap")) ecrJson.getInt( "sixthAndSeventhChildGap" ) else null, - dob8 = if (ecrJson.has("dob8")) getLongFromDate(ecrJson.getString("dob8")) else null, + dob8 = if (ecrJson.has("dob8")) getLongFromDateOrNull(ecrJson.getString("dob8")) else null, age8 = if (ecrJson.has("age8")) ecrJson.getInt("age8") else null, gender8 = if (ecrJson.has("gender8")) ecrJson.getString("gender8") .uppercase() @@ -529,7 +577,7 @@ class EcrRepo @Inject constructor( seventhAndEighthChildGap = if (ecrJson.has("seventhAndEighthChildGap")) ecrJson.getInt( "seventhAndEighthChildGap" ) else null, - dob9 = if (ecrJson.has("dob9")) getLongFromDate(ecrJson.getString("dob9")) else null, + dob9 = if (ecrJson.has("dob9")) getLongFromDateOrNull(ecrJson.getString("dob9")) else null, age9 = if (ecrJson.has("age9")) ecrJson.getInt("age9") else null, gender9 = if (ecrJson.has("gender9")) ecrJson.getString("gender9") .uppercase() @@ -557,7 +605,19 @@ class EcrRepo @Inject constructor( "updatedDate" ) else ecrJson.getString("createdDate") ), - syncState = SyncState.SYNCED + syncState = SyncState.SYNCED, + isKitHandedOver = ecrJson.optBoolean("isKitHandedOver",false), + kitHandedOverDate = getLongFromDate( + if (ecrJson.has("updatedDate")) ecrJson.getString("updatedDate") else ecrJson.getString("createdDate") ), + kitPhoto1 = ecrJson.optString("kitPhoto1",""), + kitPhoto2 = ecrJson.optString("kitPhoto2",""), + lmp_date = getLongFromDate( + if (ecrJson.has("updatedDate")) ecrJson.getString( + "updatedDate" + ) else ecrJson.getString("createdDate"), + + + ) ) if (ecr.isRegistered) list.add(ecr) } catch (e: Exception) { @@ -574,9 +634,27 @@ class EcrRepo @Inject constructor( val list = mutableListOf() for (i in 0 until dataObj.length()) { val ecrJson = dataObj.getJSONObject(i) + val methodOfContraceptionRaw = ecrJson.optString("methodOfContraception", null) + val ecr = EligibleCoupleTrackingCache( benId = ecrJson.getLong("benId"), + lmpDate = + if (ecrJson.has("lmpDate")) { + getLongFromLmpDateOrNull(ecrJson.getString("lmpDate")) ?: 0L + } else { + 0L + }, visitDate = getLongFromDate(ecrJson.getString("visitDate")), + dateOfAntraInjection = getStringToDate(ecrJson.optString("dateOfAntraInjection",null)), +// antraDose = ecrJson.optString("antraDose",null), + antraDose = methodOfContraceptionRaw?.substringAfter("/", "") + ?.takeIf { it.isNotEmpty() } + ?: ecrJson.optString("antraDose", null), + dueDateOfAntraInjection = ecrJson.optString("dueDateOfAntraInjection",null), + mpaFile = ecrJson.optString("mpaFile",null), + dischargeSummary1 = ecrJson.optString("dischargeSummary1",null), + dischargeSummary2 = ecrJson.optString("dischargeSummary2",null), + isPregnancyTestDone = if (ecrJson.has("isPregnancyTestDone")) ecrJson.getString("isPregnancyTestDone") else null, isActive = if (ecrJson.has("isActive")) ecrJson.getBoolean("isActive") else false, pregnancyTestResult = if (ecrJson.has("pregnancyTestResult")) ecrJson.getString("pregnancyTestResult") else null, @@ -598,7 +676,8 @@ class EcrRepo @Inject constructor( ) else ecrJson.getString("createdDate") ), processed = "P", - syncState = SyncState.SYNCED + syncState = SyncState.SYNCED, + lmp_date = ecrJson.getLong("benId") ) list.add(ecr) @@ -611,6 +690,13 @@ class EcrRepo @Inject constructor( database.ecrDao.getLatestEct(benId) } } + suspend fun getAllAntraDoses(benId: Long): List? { + return withContext(Dispatchers.IO) { + database.ecrDao.getAllAntraDoses(benId) + } + } + + private fun getHighRiskAssess(dataObj: JSONArray): List { // TODO() @@ -652,10 +738,51 @@ class EcrRepo @Inject constructor( } private fun getLongFromDate(dateString: String): Long { - //Jul 22, 2023 8:17:23 AM" val f = SimpleDateFormat("MMM d, yyyy h:mm:ss a", Locale.ENGLISH) val date = f.parse(dateString) return date?.time ?: throw IllegalStateException("Invalid date for dateReg") } + + private fun getLongFromDateOrNull(dateString: String?): Long? { + if (dateString.isNullOrBlank()) return null + return try { + val f = SimpleDateFormat("MMM d, yyyy h:mm:ss a", Locale.ENGLISH) + f.parse(dateString)?.time + } catch (e: Exception) { + null + } + } + + private fun getLongFromLmpDate(dateString: String): Long { + val f = SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) + val date = f.parse(dateString) + return date?.time ?: throw IllegalStateException("Invalid date for dateReg") + } + + private fun getLongFromLmpDateOrNull(dateString: String?): Long? { + if (dateString.isNullOrBlank()) return null + return try { + val f = SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) + f.parse(dateString)?.time + } catch (e: Exception) { + null + } + } + + private fun getStringToDate(dateString: String?): String? { + if (dateString.isNullOrBlank()) return null + + return try { + val inputFormat = SimpleDateFormat("MMM d, yyyy h:mm:ss a", Locale.ENGLISH) + val outputFormat = SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH) + + val date = inputFormat.parse(dateString) + date?.let { outputFormat.format(it) } + } catch (e: Exception) { + null + } + } + + } } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/FilariaRepo.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/FilariaRepo.kt new file mode 100644 index 000000000..7c2877366 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/FilariaRepo.kt @@ -0,0 +1,419 @@ +package org.piramalswasthya.sakhi.repositories + +import com.google.gson.Gson +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.json.JSONObject +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.database.room.dao.BenDao +import org.piramalswasthya.sakhi.database.room.dao.FilariaDao +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao +import org.piramalswasthya.sakhi.helpers.Konstants +import org.piramalswasthya.sakhi.model.FilariaScreeningCache +import org.piramalswasthya.sakhi.network.AmritApiService +import org.piramalswasthya.sakhi.network.FilariaScreeningDTO +import org.piramalswasthya.sakhi.network.FilariaScreeningRequestDTO +import org.piramalswasthya.sakhi.network.GetDataPaginatedRequestForDisease +import timber.log.Timber +import java.net.SocketTimeoutException +import java.text.SimpleDateFormat +import java.util.Locale +import javax.inject.Inject + +class FilariaRepo @Inject constructor( + private val filariaDao: FilariaDao, + private val benDao: BenDao, + private val preferenceDao: PreferenceDao, + private val userRepo: UserRepo, + private val tmcNetworkApiService: AmritApiService +) { + + suspend fun getFilariaScreening(benId: Long): FilariaScreeningCache? { + return withContext(Dispatchers.IO) { + filariaDao.getFilariaScreening(benId) + } + } + + suspend fun saveFilariaScreening(filariaScreeningCache: FilariaScreeningCache) { + withContext(Dispatchers.IO) { + filariaDao.saveFilariaScreening(filariaScreeningCache) + } + } + + /* suspend fun getTBSuspected(benId: Long): TBSuspectedCache? { + return withContext(Dispatchers.IO) { + malariaDao.getTbSuspected(benId) + } + } + + suspend fun saveTBSuspected(tbSuspectedCache: TBSuspectedCache) { + withContext(Dispatchers.IO) { + malariaDao.saveTbSuspected(tbSuspectedCache) + } + }*/ + + suspend fun getFilariaScreeningDetailsFromServer(): Int { + return withContext(Dispatchers.IO) { + val user = + preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") + val lastTimeStamp = preferenceDao.getLastSyncedTimeStamp() + try { + val response = tmcNetworkApiService.getMalariaScreeningData( + GetDataPaginatedRequestForDisease( + ashaId = user.userId, + pageNo = 0, + fromDate = BenRepo.getCurrentDate(Konstants.defaultTimeStamp), + toDate = getCurrentDate(), + diseaseTypeID = 4 + ) + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + + val errorMessage = jsonObj.optString("errorMessage", "") + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Pull from amrit Filaria screening data : $responseStatusCode") + when (responseStatusCode) { + 200 -> { + try { + val dataObj = jsonObj.getString("data") + saveAESScreeningCacheFromResponse(dataObj) + } catch (e: Exception) { + Timber.d("Filaria Screening entries not synced $e") + return@withContext 0 + } + + return@withContext 1 + } + + 5002 -> { + if (userRepo.refreshTokenTmc( + user.userName, user.password + ) + ) throw SocketTimeoutException("Refreshed Token!") + else throw IllegalStateException("User Logged out!!") + } + + 5000 -> { + if (errorMessage == "No record found") return@withContext 0 + } + + else -> { + throw IllegalStateException("$responseStatusCode received, dont know what todo!?") + } + } + } + } + + } catch (e: SocketTimeoutException) { + Timber.e("get_tb error : $e") + return@withContext -2 + + } catch (e: java.lang.IllegalStateException) { + Timber.e("get_tb error : $e") + return@withContext -1 + } + -1 + } + } + + private suspend fun saveAESScreeningCacheFromResponse(dataObj: String): MutableList { + val filariaScreeningList = mutableListOf() + var requestDTO = Gson().fromJson(dataObj, FilariaScreeningRequestDTO::class.java) + requestDTO?.filariaLists?.forEach { filariaScreeningDTO -> + filariaScreeningDTO.mdaHomeVisitDate?.let { + var aesScreeningCache: FilariaScreeningCache? = + filariaDao.getFilariaScreening( + filariaScreeningDTO.benId, + getLongFromDate(filariaScreeningDTO.mdaHomeVisitDate), + getLongFromDate(filariaScreeningDTO.mdaHomeVisitDate) - 19_800_000 + ) + if (aesScreeningCache == null) { + benDao.getBen(filariaScreeningDTO.benId)?.let { + filariaDao.saveFilariaScreening(filariaScreeningDTO.toCache()) + } + } + } + } + return filariaScreeningList + } + + /* suspend fun getTbSuspectedDetailsFromServer(): Int { + return withContext(Dispatchers.IO) { + val user = + preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") + val lastTimeStamp = preferenceDao.getLastSyncedTimeStamp() + try { + val response = tmcNetworkApiService.getTBSuspectedData( + GetDataPaginatedRequest( + ashaId = user.userId, + pageNo = 0, + fromDate = BenRepo.getCurrentDate(Konstants.defaultTimeStamp), + toDate = getCurrentDate() + ) + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + + val errorMessage = jsonObj.optString("errorMessage", "") + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Pull from amrit tb suspected data : $responseStatusCode") + when (responseStatusCode) { + 200 -> { + try { + val dataObj = jsonObj.getString("data") + saveTBSuspectedCacheFromResponse(dataObj) + } catch (e: Exception) { + Timber.d("TB Suspected entries not synced $e") + return@withContext 0 + } + + return@withContext 1 + } + + 5002 -> { + if (userRepo.refreshTokenTmc( + user.userName, user.password + ) + ) throw SocketTimeoutException("Refreshed Token!") + else throw IllegalStateException("User Logged out!!") + } + + 5000 -> { + if (errorMessage == "No record found") return@withContext 0 + } + + else -> { + throw IllegalStateException("$responseStatusCode received, don't know what todo!?") + } + } + } + } + + } catch (e: SocketTimeoutException) { + Timber.e("get_tb error : $e") + return@withContext -2 + + } catch (e: java.lang.IllegalStateException) { + Timber.e("get_tb error : $e") + return@withContext -1 + } + -1 + } + }*/ + + /* private suspend fun saveTBSuspectedCacheFromResponse(dataObj: String): MutableList { + val tbSuspectedList = mutableListOf() + val requestDTO = Gson().fromJson(dataObj, TBSuspectedRequestDTO::class.java) + requestDTO?.tbSuspectedList?.forEach { tbSuspectedDTO -> + tbSuspectedDTO.visitDate?.let { + val tbSuspectedCache: TBSuspectedCache? = + malariaDao.getTbSuspected( + tbSuspectedDTO.benId, + getLongFromDate(tbSuspectedDTO.visitDate), + getLongFromDate(tbSuspectedDTO.visitDate) - 19_800_000 + ) + if (tbSuspectedCache == null) { + benDao.getBen(tbSuspectedDTO.benId)?.let { + malariaDao.saveTbSuspected(tbSuspectedDTO.toCache()) + } + } + } + } + return tbSuspectedList + }*/ + + // RECORD-LEVEL ISOLATION: Coordinator always returns true so the + // WorkManager worker succeeds. Failed records stay UNSYNCED for next cycle. + suspend fun pushUnSyncedRecords(): Boolean { + val screeningResult = pushUnSyncedRecordsFilariaScreening() + Timber.d("Filaria push result: screening=$screeningResult") + // Worker succeeds — failed records stay UNSYNCED for next cycle + return true + } + + // RECORD-LEVEL ISOLATION: Filaria Screening records are now sent in + // chunks of 20 instead of one giant batch. Previously, if ANY record in + // the batch was malformed, the ENTIRE batch failed and ALL records stayed + // UNSYNCED. Now each chunk is independent — one bad chunk doesn't affect + // the others. Failed chunks' records stay UNSYNCED for the next sync cycle. + private suspend fun pushUnSyncedRecordsFilariaScreening(): Int { + + return withContext(Dispatchers.IO) { + val user = + preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") + + val filariaSnList: List = filariaDao.getFilariaScreening(SyncState.UNSYNCED) + + if (filariaSnList.isEmpty()) return@withContext 1 + + val CHUNK_SIZE = 20 + val chunks = filariaSnList.chunked(CHUNK_SIZE) + var successCount = 0 + var failCount = 0 + + for (chunk in chunks) { + try { + val chunkDtos = chunk.map { it.toDTO() } + + val response = tmcNetworkApiService.saveFilariaScreeningData( + FilariaScreeningRequestDTO( + userId = user.userId, + filariaLists = chunkDtos + ) + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Push to Amrit Filaria Screening chunk: $responseStatusCode") + when (responseStatusCode) { + 200 -> { + updateSyncStatusScreening(chunk) + successCount += chunk.size + } + + 401, 5002 -> { + if (userRepo.refreshTokenTmc(user.userName, user.password)) { + Timber.d("Token refreshed, Filaria chunk will retry next cycle") + } + failCount += chunk.size + } + + else -> { + Timber.e("Filaria Screening chunk failed with statusCode: $responseStatusCode") + failCount += chunk.size + } + } + } + } else { + Timber.e("Filaria Screening chunk HTTP error: $statusCode") + failCount += chunk.size + } + } catch (e: Exception) { + Timber.e(e, "Filaria Screening chunk push failed: ${chunk.size} records") + failCount += chunk.size + } + } + + Timber.d("Filaria Screening push complete: $successCount succeeded, $failCount failed out of ${filariaSnList.size}") + return@withContext 1 + } + } + + /* private suspend fun pushUnSyncedRecordsTBSuspected(): Int { + return withContext(Dispatchers.IO) { + val user = + preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") + + val tbspList: List = tbDao.getTbSuspected(SyncState.UNSYNCED) + + val tbspDtos = mutableListOf() + tbspList.forEach { cache -> + tbspDtos.add(cache.toDTO()) + } + if (tbspDtos.isEmpty()) return@withContext 1 + try { + val response = tmcNetworkApiService.saveTBSuspectedData( + TBSuspectedRequestDTO( + userId = user.userId, + tbSuspectedList = tbspDtos + ) + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + + val errorMessage = jsonObj.optString("errorMessage", "") + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Push to amrit tb screening data : $responseStatusCode") + when (responseStatusCode) { + 200 -> { + try { + updateSyncStatusSuspected(tbspList) + return@withContext 1 + } catch (e: Exception) { + Timber.d("TB Screening entries not synced $e") + } + + } + + 5002 -> { + if (userRepo.refreshTokenTmc( + user.userName, user.password + ) + ) throw SocketTimeoutException("Refreshed Token!") + else throw IllegalStateException("User Logged out!!") + } + + 5000 -> { + if (errorMessage == "No record found") return@withContext 0 + } + + else -> { + throw IllegalStateException("$responseStatusCode received, dont know what todo!?") + } + } + } + } + + } catch (e: SocketTimeoutException) { + Timber.e("get_tb error : $e") + return@withContext -2 + + } catch (e: java.lang.IllegalStateException) { + Timber.e("get_tb error : $e") + return@withContext -1 + } + -1 + } + }*/ + + + private suspend fun updateSyncStatusScreening(aesAsList: List) { + aesAsList.forEach { + it.syncState = SyncState.SYNCED + filariaDao.saveFilariaScreening(it) + } + } + + /* private suspend fun updateSyncStatusSuspected(tbspList: List) { + tbspList.forEach { + it.syncState = SyncState.SYNCED + malariaDao.saveTbSuspected(it) + } + }*/ + + companion object { + private val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) + private val timeFormat = SimpleDateFormat("HH:mm:ss", Locale.ENGLISH) + private fun getCurrentDate(millis: Long = System.currentTimeMillis()): String { + val dateString = dateFormat.format(millis) + val timeString = timeFormat.format(millis) + return "${dateString}T${timeString}.000Z" + } + + private fun getLongFromDate(dateString: String): Long { + //Jul 22, 2023 8:17:23 AM" + val f = SimpleDateFormat("MMM d, yyyy h:mm:ss a", Locale.ENGLISH) + val date = f.parse(dateString) + return date?.time ?: throw IllegalStateException("Invalid date for dateReg") + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/FpotRepo.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/FpotRepo.kt index 1c011e7a7..2bf1dad4c 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/repositories/FpotRepo.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/FpotRepo.kt @@ -29,7 +29,7 @@ class FpotRepo @Inject constructor( true } catch (e: Exception) { - Timber.d("Error : $e raised at saveCdrData") + Timber.e("Error : $e raised at saveCdrData") false } } diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/HRPRepo.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/HRPRepo.kt index faa9f97d3..937dea8e6 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/repositories/HRPRepo.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/HRPRepo.kt @@ -1,5 +1,7 @@ package org.piramalswasthya.sakhi.repositories +import android.util.Log +import android.app.Application import com.google.gson.Gson import com.google.gson.JsonObject import kotlinx.coroutines.Dispatchers @@ -21,6 +23,7 @@ import org.piramalswasthya.sakhi.model.PregnantWomanRegistrationCache import org.piramalswasthya.sakhi.model.PwrPost import org.piramalswasthya.sakhi.network.AmritApiService import org.piramalswasthya.sakhi.network.GetDataPaginatedRequest +import org.piramalswasthya.sakhi.network.HRPMicroBirthPlanDTO import org.piramalswasthya.sakhi.network.HRPNonPregnantAssessDTO import org.piramalswasthya.sakhi.network.HRPNonPregnantTrackDTO import org.piramalswasthya.sakhi.network.HRPPregnantAssessDTO @@ -39,9 +42,11 @@ class HRPRepo @Inject constructor( private val maternalHealthRepo: MaternalHealthRepo, private val ecrRepo: EcrRepo, private val preferenceDao: PreferenceDao, - private val tmcNetworkApiService: AmritApiService + private val tmcNetworkApiService: AmritApiService, + private val context: Application, -) { + + ) { suspend fun getPregnantAssess(benId: Long): HRPPregnantAssessCache? { return withContext(Dispatchers.IO) { database.hrpDao.getPregnantAssess(benId) @@ -156,7 +161,8 @@ class HRPRepo @Inject constructor( } } - suspend fun getHighRiskAssessDetailsFromServer(): Int { + + suspend fun getHighRiskAssessDetailsFromServer(retryCount: Int = 3): Int { return withContext(Dispatchers.IO) { val user = preferenceDao.getLoggedInUser() @@ -176,9 +182,9 @@ class HRPRepo @Inject constructor( if (responseString != null) { val jsonObj = JSONObject(responseString) - val errorMessage = jsonObj.getString("errorMessage") + val errorMessage = jsonObj.optString("errorMessage", "") val responseStatusCode = jsonObj.getInt("statusCode") - Timber.d("Pull from amrit assess data : $responseStatusCode") + Timber.d("Pull from amrit micro birth plan data: $responseStatusCode") when (responseStatusCode) { 200 -> { try { @@ -192,7 +198,7 @@ class HRPRepo @Inject constructor( return@withContext 1 } - 5002 -> { + 401,5002 -> { if (userRepo.refreshTokenTmc( user.userName, user.password ) @@ -212,11 +218,80 @@ class HRPRepo @Inject constructor( } } catch (e: SocketTimeoutException) { - Timber.d("get data error : $e") - return@withContext getHighRiskAssessDetailsFromServer() + Timber.e("get data error : $e") + if (retryCount > 0) return@withContext getHighRiskAssessDetailsFromServer(retryCount - 1) + Timber.e("getHighRiskAssessDetailsFromServer: max retries exhausted") + return@withContext -1 } catch (e: java.lang.IllegalStateException) { - Timber.d("get data error : $e") + Timber.e("get data error : $e") + return@withContext -1 + } + -1 + } + } + + + suspend fun getHighRiskAssessMicroBirthPlanDetailsFromServer(retryCount: Int = 3): Int { + return withContext(Dispatchers.IO) { + val user = + preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") + try { + + + val response = tmcNetworkApiService.getMicroBirthPlanAssessData( + user.userId + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + + val errorMessage = jsonObj.optString("errorMessage", "") + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Pull from amrit assess data : $responseStatusCode") + when (responseStatusCode) { + 200 -> { + try { + val dataObj = jsonObj.getString("data") + saveHighRiskAssessMicroBirthPlan(dataObj) + } catch (e: Exception) { + Timber.d("Assess entries not synced $e") + return@withContext 0 + } + + return@withContext 1 + } + + 5002 -> { + if (userRepo.refreshTokenTmc( + user.userName, user.password + ) + ) throw SocketTimeoutException("Refreshed Token!") + else throw IllegalStateException("User Logged out!!") + } + + 5000 -> { + if (errorMessage == "No record found") return@withContext 0 + } + + else -> { + throw IllegalStateException("Unexpected response code: $responseStatusCode") + } + } + } + } + + } catch (e: SocketTimeoutException) { + Timber.e("get data error : $e") + if (retryCount > 0) return@withContext getHighRiskAssessMicroBirthPlanDetailsFromServer(retryCount - 1) + Timber.e("getHighRiskAssessMicroBirthPlanDetailsFromServer: max retries exhausted") + return@withContext -1 + + } catch (e: java.lang.IllegalStateException) { + Timber.e("get data error : $e") return@withContext -1 } -1 @@ -229,6 +304,10 @@ class HRPRepo @Inject constructor( for (dto in entries) { try { val entry = Gson().fromJson(dto.toString(), HighRiskAssessDTO::class.java) + if (entry.benId <= 0) { + Timber.d("Skipping HRP assess entry with invalid benId=${entry.benId}") + continue + } entry.createdDate?.let { val hrpPregnantAssessCache = database .hrpDao.getPregnantAssess(entry.benId) @@ -271,6 +350,51 @@ class HRPRepo @Inject constructor( } } + private fun saveHighRiskAssessMicroBirthPlan(dataObj: String) { + val requestDTO = Gson().fromJson(dataObj, JsonObject::class.java) + val entries = requestDTO.getAsJsonArray("entries") + for (dto in entries) { + try { + val entry = Gson().fromJson(dto.toString(), HRPMicroBirthPlanDTO::class.java) + if (entry.benId <= 0) { + Timber.d("Skipping HRP micro birth plan entry with invalid benId=${entry.benId}") + continue + } + entry.nearestSc?.let { + val hrpMicroBirthPlanCache = database.hrpDao.getMicroBirthPlan(entry.benId) + if (hrpMicroBirthPlanCache == null) { + database.hrpDao.saveRecord(entry.toCache()) + } else { + + hrpMicroBirthPlanCache.apply { + nearestSc = nearestSc ?: entry.nearestSc + bloodGroup = bloodGroup ?: entry.bloodGroup + contactNumber1 = contactNumber1 ?: entry.contactNumber1 + contactNumber2 = contactNumber2 ?: entry.contactNumber2 + scHosp = scHosp ?: entry.scHosp + usg = usg ?: entry.usg + block = block ?: entry.block + nearestPhc = nearestPhc ?: entry.nearestPhc + nearestFru = nearestFru ?: entry.nearestFru + bloodDonors1 = bloodDonors1 ?: entry.bloodDonors1 + bloodDonors2 = bloodDonors2 ?: entry.bloodDonors2 + birthCompanion = birthCompanion ?: entry.birthCompanion + careTaker = careTaker ?: entry.careTaker + communityMember = communityMember ?: entry.communityMember + communityMemberContact = communityMemberContact ?: entry.communityMemberContact + modeOfTransportation = modeOfTransportation ?: entry.modeOfTransportation + } + + + database.hrpDao.saveRecord(hrpMicroBirthPlanCache) + } + } + } catch (e: java.lang.Exception) { + Timber.d("cannot save entry $dto due to : $e") + } + } + } + private fun isHighRisk(assess: HRPNonPregnantAssessCache) { assess.isHighRisk = assess.noOfDeliveries == "Yes" || @@ -295,7 +419,7 @@ class HRPRepo @Inject constructor( assess.multiplePregnancy == "Yes" } - suspend fun getHRPAssessDetailsFromServer(): Int { + suspend fun getHRPAssessDetailsFromServer(retryCount: Int = 3): Int { return withContext(Dispatchers.IO) { val user = preferenceDao.getLoggedInUser() @@ -315,7 +439,7 @@ class HRPRepo @Inject constructor( if (responseString != null) { val jsonObj = JSONObject(responseString) - val errorMessage = jsonObj.getString("errorMessage") + val errorMessage = jsonObj.optString("errorMessage", "") val responseStatusCode = jsonObj.getInt("statusCode") Timber.d("Pull from amrit hrp assess data : $responseStatusCode") when (responseStatusCode) { @@ -331,7 +455,7 @@ class HRPRepo @Inject constructor( return@withContext 1 } - 5002 -> { + 401,5002 -> { if (userRepo.refreshTokenTmc( user.userName, user.password ) @@ -351,11 +475,13 @@ class HRPRepo @Inject constructor( } } catch (e: SocketTimeoutException) { - Timber.d("get data error : $e") - return@withContext getHRPAssessDetailsFromServer() + Timber.e("get data error : $e") + if (retryCount > 0) return@withContext getHRPAssessDetailsFromServer(retryCount - 1) + Timber.e("getHRPAssessDetailsFromServer: max retries exhausted") + return@withContext -1 } catch (e: java.lang.IllegalStateException) { - Timber.d("get data error : $e") + Timber.e("get data error : $e") return@withContext -1 } -1 @@ -368,6 +494,10 @@ class HRPRepo @Inject constructor( for (dto in entries) { try { val entry = Gson().fromJson(dto.toString(), HRPPregnantAssessDTO::class.java) + if (entry.benId <= 0) { + Timber.d("Skipping HRP pregnant assess entry with invalid benId=${entry.benId}") + continue + } entry.visitDate?.let { val hrpPregnantAssessCache = database .hrpDao.getPregnantAssess(entry.benId) @@ -381,7 +511,8 @@ class HRPRepo @Inject constructor( } } - suspend fun getHRPTrackDetailsFromServer(): Int { + + suspend fun getHRPTrackDetailsFromServer(retryCount: Int = 3): Int { return withContext(Dispatchers.IO) { val user = preferenceDao.getLoggedInUser() @@ -401,7 +532,7 @@ class HRPRepo @Inject constructor( if (responseString != null) { val jsonObj = JSONObject(responseString) - val errorMessage = jsonObj.getString("errorMessage") + val errorMessage = jsonObj.optString("errorMessage", "") val responseStatusCode = jsonObj.getInt("statusCode") Timber.d("Pull from amrit hrp track data : $responseStatusCode") when (responseStatusCode) { @@ -417,7 +548,7 @@ class HRPRepo @Inject constructor( return@withContext 1 } - 5002 -> { + 401, 5002 -> { if (userRepo.refreshTokenTmc( user.userName, user.password ) @@ -437,11 +568,13 @@ class HRPRepo @Inject constructor( } } catch (e: SocketTimeoutException) { - Timber.d("get data error : $e") - return@withContext getHRPTrackDetailsFromServer() + Timber.e("get data error : $e") + if (retryCount > 0) return@withContext getHRPTrackDetailsFromServer(retryCount - 1) + Timber.e("getHRPTrackDetailsFromServer: max retries exhausted") + return@withContext -1 } catch (e: java.lang.IllegalStateException) { - Timber.d("get data error : $e") + Timber.e("get data error : $e") return@withContext -1 } -1 @@ -475,7 +608,7 @@ class HRPRepo @Inject constructor( } } - suspend fun getHRNonPAssessDetailsFromServer(): Int { + suspend fun getHRNonPAssessDetailsFromServer(retryCount: Int = 3): Int { return withContext(Dispatchers.IO) { val user = preferenceDao.getLoggedInUser() @@ -495,7 +628,7 @@ class HRPRepo @Inject constructor( if (responseString != null) { val jsonObj = JSONObject(responseString) - val errorMessage = jsonObj.getString("errorMessage") + val errorMessage = jsonObj.optString("errorMessage", "") val responseStatusCode = jsonObj.getInt("statusCode") Timber.d("Pull from Amrit hrp non pregnant Assess data : $responseStatusCode") when (responseStatusCode) { @@ -511,7 +644,7 @@ class HRPRepo @Inject constructor( return@withContext 1 } - 5002 -> { + 401,5002 -> { if (userRepo.refreshTokenTmc( user.userName, user.password ) @@ -531,11 +664,13 @@ class HRPRepo @Inject constructor( } } catch (e: SocketTimeoutException) { - Timber.d("get data error : $e") - return@withContext getHRNonPAssessDetailsFromServer() + Timber.e("get data error : $e") + if (retryCount > 0) return@withContext getHRNonPAssessDetailsFromServer(retryCount - 1) + Timber.e("getHRNonPAssessDetailsFromServer: max retries exhausted") + return@withContext -1 } catch (e: java.lang.IllegalStateException) { - Timber.d("get data error : $e") + Timber.e("get data error : $e") return@withContext -1 } -1 @@ -561,7 +696,7 @@ class HRPRepo @Inject constructor( } } - suspend fun getHRNonPTrackDetailsFromServer(): Int { + suspend fun getHRNonPTrackDetailsFromServer(retryCount: Int = 3): Int { return withContext(Dispatchers.IO) { val user = preferenceDao.getLoggedInUser() @@ -581,7 +716,7 @@ class HRPRepo @Inject constructor( if (responseString != null) { val jsonObj = JSONObject(responseString) - val errorMessage = jsonObj.getString("errorMessage") + val errorMessage = jsonObj.optString("errorMessage", "") val responseStatusCode = jsonObj.getInt("statusCode") Timber.d("Pull from amrit non hrp track data : $responseStatusCode") when (responseStatusCode) { @@ -597,7 +732,7 @@ class HRPRepo @Inject constructor( return@withContext 1 } - 5002 -> { + 401,5002 -> { if (userRepo.refreshTokenTmc( user.userName, user.password ) @@ -617,11 +752,13 @@ class HRPRepo @Inject constructor( } } catch (e: SocketTimeoutException) { - Timber.d("get data error : $e") - return@withContext getHRNonPTrackDetailsFromServer() + Timber.e("get data error : $e") + if (retryCount > 0) return@withContext getHRNonPTrackDetailsFromServer(retryCount - 1) + Timber.e("getHRNonPTrackDetailsFromServer: max retries exhausted") + return@withContext -1 } catch (e: java.lang.IllegalStateException) { - Timber.d("get data error : $e") + Timber.e("get data error : $e") return@withContext -1 } -1 @@ -652,35 +789,37 @@ class HRPRepo @Inject constructor( } } + // RECORD-LEVEL ISOLATION: Coordinator always returns true so the + // WorkManager worker succeeds. Each sub-method handles its own failures + // independently — failed records stay UNSYNCED and retry on next sync cycle. suspend fun pushUnSyncedRecords(): Boolean { + val hrpaMBPResult = pushUnSyncedRecordsHRPMicroBirthPlan() val hrpaResult = pushUnSyncedRecordsHRPAssess() val hrptResult = pushUnSyncedRecordsHRPTrack() val hrnpaResult = pushUnSyncedRecordsHRNonPAssess() val hrnptResult = pushUnSyncedRecordsHRNonPTrack() - return (hrpaResult == 1) && (hrptResult == 1) && (hrnpaResult == 1) && (hrnptResult == 1) + Timber.d("HRP push results: MBP=$hrpaMBPResult, assess=$hrpaResult, track=$hrptResult, nonPAssess=$hrnpaResult, nonPTrack=$hrnptResult") + // Worker succeeds — failed records stay UNSYNCED for next cycle + return true } + // RECORD-LEVEL ISOLATION: Records now sent in chunks of 20. + // Previously all-or-nothing batch. One bad chunk doesn't affect others. + // Also removed dangerous recursive retry on SocketTimeoutException. private suspend fun pushUnSyncedRecordsHRPAssess(): Int { - return withContext(Dispatchers.IO) { val user = preferenceDao.getLoggedInUser() ?: throw IllegalStateException("No user logged in!!") val entities = database.hrpDao.getHRPAssess(SyncState.UNSYNCED) + if (entities.isNullOrEmpty()) return@withContext 1 - val assessDtos = mutableListOf() - + // PWR side-effect: push PWR data before chunking HRP Assess val pwrDtos = mutableSetOf() - - entities?.let { - it.forEach { cache -> - assessDtos.add(cache.toHighRiskAssessDTO()) - pwrDtos.add(mapPWR(cache)) - cache.syncState = SyncState.SYNCED - } + entities.forEach { cache -> + pwrDtos.add(mapPWR(cache)) } - if (maternalHealthRepo.postPwrToAmritServer(pwrDtos)) { pwrDtos.forEach { pwr -> maternalHealthRepo.getSavedRegistrationRecord(pwr.benId)?.let { pwrCache -> @@ -691,160 +830,214 @@ class HRPRepo @Inject constructor( } } - if (assessDtos.isEmpty()) return@withContext 1 - try { - val response = tmcNetworkApiService.saveHighRiskAssessData( - UserDataDTO( - userId = user.userId, - entries = assessDtos - ) - ) - val statusCode = response.code() - if (statusCode == 200) { - val responseString = response.body()?.string() - if (responseString != null) { - val jsonObj = JSONObject(responseString) + val CHUNK_SIZE = 20 + val chunks = entities.chunked(CHUNK_SIZE) + var successCount = 0 + var failCount = 0 - val responseStatusCode = jsonObj.getInt("statusCode") - Timber.d("Push to amrit hrp assess data : $responseStatusCode") - when (responseStatusCode) { - 200 -> { - try { - updateSyncStatusHrpa(entities) - return@withContext 1 - } catch (e: Exception) { - Timber.d("HRP Assess entries not synced $e") - } + for (chunk in chunks) { + try { + val chunkDtos = chunk.map { cache -> + cache.toHighRiskAssessDTO() + } + val response = tmcNetworkApiService.saveHighRiskAssessData( + UserDataDTO(userId = user.userId, entries = chunkDtos) + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Push to Amrit HRP Assess chunk: $responseStatusCode") + when (responseStatusCode) { + 200 -> { + chunk.forEach { it.syncState = SyncState.SYNCED } + updateSyncStatusHrpa(chunk) + successCount += chunk.size + } + 401, 5002 -> { + if (userRepo.refreshTokenTmc(user.userName, user.password)) { + Timber.d("Token refreshed, HRP Assess chunk will retry next cycle") + } + failCount += chunk.size + } + else -> { + Timber.e("HRP Assess chunk failed with statusCode: $responseStatusCode") + failCount += chunk.size + } } + } + } else { + Timber.e("HRP Assess chunk HTTP error: $statusCode") + failCount += chunk.size + } + } catch (e: Exception) { + Timber.e(e, "HRP Assess chunk push failed: ${chunk.size} records") + failCount += chunk.size + } + } - 5002 -> { - if (userRepo.refreshTokenTmc( - user.userName, user.password - ) - ) throw SocketTimeoutException("Refreshed Token!") - else throw IllegalStateException("User Logged out!!") - } + Timber.d("HRP Assess push complete: $successCount succeeded, $failCount failed out of ${entities.size}") + return@withContext 1 + } + } + // RECORD-LEVEL ISOLATION: Records now sent in chunks of 20. + // Previously all-or-nothing batch. One bad chunk doesn't affect others. + // Also removed dangerous recursive retry on SocketTimeoutException. + private suspend fun pushUnSyncedRecordsHRPMicroBirthPlan(): Int { + return withContext(Dispatchers.IO) { + val user = + preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") - 5000 -> { - val errorMessage = jsonObj.getString("errorMessage") - if (errorMessage == "No record found") return@withContext 0 - } + val entities = database.hrpDao.getMicroBirthPlan(SyncState.UNSYNCED) + if (entities.isNullOrEmpty()) return@withContext 1 - else -> { - throw IllegalStateException("$responseStatusCode received, dont know what todo!?") + val CHUNK_SIZE = 20 + val chunks = entities.chunked(CHUNK_SIZE) + var successCount = 0 + var failCount = 0 + + for (chunk in chunks) { + try { + val chunkDtos = chunk.map { cache -> + cache.toDTO() + } + + val response = tmcNetworkApiService.saveMicroBirthPlanAssessData( + UserDataDTO(userId = user.userId, entries = chunkDtos) + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Push to Amrit HRP MicroBirthPlan chunk: $responseStatusCode") + when (responseStatusCode) { + 200 -> { + chunkDtos.forEach { dto -> + database.hrpDao.getSavedRecord(dto.benId)?.let { record -> + record.processed = "P" + record.syncState = SyncState.SYNCED + updateSyncStatusHrpMBP(record) + } + } + successCount += chunk.size + } + 401, 5002 -> { + if (userRepo.refreshTokenTmc(user.userName, user.password)) { + Timber.d("Token refreshed, HRP MicroBirthPlan chunk will retry next cycle") + } + failCount += chunk.size + } + else -> { + Timber.e("HRP MicroBirthPlan chunk failed with statusCode: $responseStatusCode") + failCount += chunk.size + } } } + } else { + Timber.e("HRP MicroBirthPlan chunk HTTP error: $statusCode") + failCount += chunk.size } + } catch (e: Exception) { + Timber.e(e, "HRP MicroBirthPlan chunk push failed: ${chunk.size} records") + failCount += chunk.size } - - } catch (e: SocketTimeoutException) { - Timber.d("get_tb error : $e") - return@withContext pushUnSyncedRecordsHRPAssess() - - } catch (e: java.lang.IllegalStateException) { - Timber.d("get_tb error : $e") - return@withContext -1 } - -1 + + Timber.d("HRP MicroBirthPlan push complete: $successCount succeeded, $failCount failed out of ${entities.size}") + return@withContext 1 } } + // RECORD-LEVEL ISOLATION: Records now sent in chunks of 20. + // Previously all-or-nothing batch. One bad chunk doesn't affect others. + // Also removed dangerous recursive retry on SocketTimeoutException. private suspend fun pushUnSyncedRecordsHRPTrack(): Int { - return withContext(Dispatchers.IO) { val user = preferenceDao.getLoggedInUser() ?: throw IllegalStateException("No user logged in!!") val entities = database.hrpDao.getHRPTrack(SyncState.UNSYNCED) + if (entities.isNullOrEmpty()) return@withContext 1 - val dtos = mutableListOf() - entities?.let { - it.forEach { cache -> - dtos.add(cache.toDTO()) - cache.syncState = SyncState.SYNCED - } - } + val CHUNK_SIZE = 20 + val chunks = entities.chunked(CHUNK_SIZE) + var successCount = 0 + var failCount = 0 - if (dtos.isEmpty()) return@withContext 1 - try { - val response = tmcNetworkApiService.saveHRPTrackData( - UserDataDTO( - userId = user.userId, - entries = dtos - ) - ) - val statusCode = response.code() - if (statusCode == 200) { - val responseString = response.body()?.string() - if (responseString != null) { - val jsonObj = JSONObject(responseString) + for (chunk in chunks) { + try { + val chunkDtos = chunk.map { cache -> + cache.toDTO() + } - val responseStatusCode = jsonObj.getInt("statusCode") - Timber.d("Push to amrit hrp track data : $responseStatusCode") - when (responseStatusCode) { - 200 -> { - try { - updateSyncStatusHrpt(entities) - return@withContext 1 - } catch (e: Exception) { - Timber.d("HRP Track entries not synced $e") + val response = tmcNetworkApiService.saveHRPTrackData( + UserDataDTO(userId = user.userId, entries = chunkDtos) + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Push to Amrit HRP Track chunk: $responseStatusCode") + when (responseStatusCode) { + 200 -> { + chunk.forEach { it.syncState = SyncState.SYNCED } + updateSyncStatusHrpt(chunk) + successCount += chunk.size + } + 401, 5002 -> { + if (userRepo.refreshTokenTmc(user.userName, user.password)) { + Timber.d("Token refreshed, HRP Track chunk will retry next cycle") + } + failCount += chunk.size + } + else -> { + Timber.e("HRP Track chunk failed with statusCode: $responseStatusCode") + failCount += chunk.size } - - } - - 5002 -> { - if (userRepo.refreshTokenTmc( - user.userName, user.password - ) - ) throw SocketTimeoutException("Refreshed Token!") - else throw IllegalStateException("User Logged out!!") - } - - 5000 -> { - val errorMessage = jsonObj.getString("errorMessage") - if (errorMessage == "No record found") return@withContext 0 - } - - else -> { - throw IllegalStateException("$responseStatusCode received, !?") } } + } else { + Timber.e("HRP Track chunk HTTP error: $statusCode") + failCount += chunk.size } + } catch (e: Exception) { + Timber.e(e, "HRP Track chunk push failed: ${chunk.size} records") + failCount += chunk.size } - - } catch (e: SocketTimeoutException) { - Timber.d("get_tb error : $e") - return@withContext pushUnSyncedRecordsHRPTrack() - - } catch (e: java.lang.IllegalStateException) { - Timber.d("get_tb error : $e") - return@withContext -1 } - -1 + + Timber.d("HRP Track push complete: $successCount succeeded, $failCount failed out of ${entities.size}") + return@withContext 1 } } - private suspend fun pushUnSyncedRecordsHRNonPAssess(): Int { + // RECORD-LEVEL ISOLATION: Records now sent in chunks of 20. + // Previously all-or-nothing batch. One bad chunk doesn't affect others. + // Also removed dangerous recursive retry on SocketTimeoutException. + private suspend fun pushUnSyncedRecordsHRNonPAssess(): Int { return withContext(Dispatchers.IO) { val user = preferenceDao.getLoggedInUser() ?: throw IllegalStateException("No user logged in!!") val entities = database.hrpDao.getNonPregnantAssess(SyncState.UNSYNCED) + if (entities.isNullOrEmpty()) return@withContext 1 - val dtos = mutableListOf() + // ECR side-effect: push ECR data before chunking HRNonP Assess val ecrDTOs = mutableSetOf() - entities?.let { - it.forEach { cache -> - dtos.add(cache.toHighRiskAssessDTO()) - ecrDTOs.add(mapECR(cache)) - cache.syncState = SyncState.SYNCED - } + entities.forEach { cache -> + ecrDTOs.add(mapECR(cache)) } - val uploadDone = ecrRepo.postECRDataToAmritServer(ecrDTOs) if (uploadDone) { ecrDTOs.forEach { @@ -856,139 +1049,125 @@ class HRPRepo @Inject constructor( } } } - if (dtos.isEmpty()) return@withContext 1 - try { - val response = tmcNetworkApiService.saveHighRiskAssessData( - UserDataDTO( - userId = user.userId, - entries = dtos - ) - ) - val statusCode = response.code() - if (statusCode == 200) { - val responseString = response.body()?.string() - if (responseString != null) { - val jsonObj = JSONObject(responseString) - - val responseStatusCode = jsonObj.getInt("statusCode") - Timber.d("Push to amrit non hrp assess data : $responseStatusCode") - when (responseStatusCode) { - 200 -> { - try { - updateSyncStatusHrpNonAssess(entities) - return@withContext 1 - } catch (e: Exception) { - Timber.d("HRP non Assess entries not synced $e") - } - } - - 5002 -> { - if (userRepo.refreshTokenTmc( - user.userName, user.password - ) - ) throw SocketTimeoutException("Refreshed Token!") - else throw IllegalStateException("User Logged out!!") - } + val CHUNK_SIZE = 20 + val chunks = entities.chunked(CHUNK_SIZE) + var successCount = 0 + var failCount = 0 - 5000 -> { - val errorMessage = jsonObj.getString("errorMessage") - if (errorMessage == "No record found") return@withContext 0 - } + for (chunk in chunks) { + try { + val chunkDtos = chunk.map { cache -> + cache.toHighRiskAssessDTO() + } - else -> { - throw IllegalStateException("$responseStatusCode received, !?") + val response = tmcNetworkApiService.saveHighRiskAssessData( + UserDataDTO(userId = user.userId, entries = chunkDtos) + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Push to Amrit HRNonP Assess chunk: $responseStatusCode") + when (responseStatusCode) { + 200 -> { + chunk.forEach { it.syncState = SyncState.SYNCED } + updateSyncStatusHrpNonAssess(chunk) + successCount += chunk.size + } + 401, 5002 -> { + if (userRepo.refreshTokenTmc(user.userName, user.password)) { + Timber.d("Token refreshed, HRNonP Assess chunk will retry next cycle") + } + failCount += chunk.size + } + else -> { + Timber.e("HRNonP Assess chunk failed with statusCode: $responseStatusCode") + failCount += chunk.size + } } } + } else { + Timber.e("HRNonP Assess chunk HTTP error: $statusCode") + failCount += chunk.size } + } catch (e: Exception) { + Timber.e(e, "HRNonP Assess chunk push failed: ${chunk.size} records") + failCount += chunk.size } - - } catch (e: SocketTimeoutException) { - Timber.d("get_ hrp error : $e") - return@withContext pushUnSyncedRecordsHRNonPAssess() - - } catch (e: java.lang.IllegalStateException) { - Timber.d("get_ hrp error : $e") - return@withContext -1 } - -1 + + Timber.d("HRNonP Assess push complete: $successCount succeeded, $failCount failed out of ${entities.size}") + return@withContext 1 } } + // RECORD-LEVEL ISOLATION: Records now sent in chunks of 20. + // Previously all-or-nothing batch. One bad chunk doesn't affect others. + // Also removed dangerous recursive retry on SocketTimeoutException. private suspend fun pushUnSyncedRecordsHRNonPTrack(): Int { - return withContext(Dispatchers.IO) { val user = preferenceDao.getLoggedInUser() ?: throw IllegalStateException("No user logged in!!") val entities = database.hrpDao.getHRNonPTrack(SyncState.UNSYNCED) + if (entities.isNullOrEmpty()) return@withContext 1 - val dtos = mutableListOf() - entities?.let { - it.forEach { cache -> - dtos.add(cache.toDTO()) - cache.syncState = SyncState.SYNCED - } - } + val CHUNK_SIZE = 20 + val chunks = entities.chunked(CHUNK_SIZE) + var successCount = 0 + var failCount = 0 - if (dtos.isEmpty()) return@withContext 1 - try { - val response = tmcNetworkApiService.saveHRNonPTrackData( - UserDataDTO( - userId = user.userId, - entries = dtos - ) - ) - val statusCode = response.code() - if (statusCode == 200) { - val responseString = response.body()?.string() - if (responseString != null) { - val jsonObj = JSONObject(responseString) + for (chunk in chunks) { + try { + val chunkDtos = chunk.map { cache -> + cache.toDTO() + } - val responseStatusCode = jsonObj.getInt("statusCode") - Timber.d("Push to amrit non hrp track data : $responseStatusCode") - when (responseStatusCode) { - 200 -> { - try { - updateSyncStatusHrNonTrack(entities) - return@withContext 1 - } catch (e: Exception) { - Timber.d("HRP non track entries not synced $e") + val response = tmcNetworkApiService.saveHRNonPTrackData( + UserDataDTO(userId = user.userId, entries = chunkDtos) + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Push to Amrit HRNonP Track chunk: $responseStatusCode") + when (responseStatusCode) { + 200 -> { + chunk.forEach { it.syncState = SyncState.SYNCED } + updateSyncStatusHrNonTrack(chunk) + successCount += chunk.size + } + 401, 5002 -> { + if (userRepo.refreshTokenTmc(user.userName, user.password)) { + Timber.d("Token refreshed, HRNonP Track chunk will retry next cycle") + } + failCount += chunk.size + } + else -> { + Timber.e("HRNonP Track chunk failed with statusCode: $responseStatusCode") + failCount += chunk.size } - - } - - 5002 -> { - if (userRepo.refreshTokenTmc( - user.userName, user.password - ) - ) throw SocketTimeoutException("Refreshed Token!") - else throw IllegalStateException("User Logged out!!") - } - - 5000 -> { - val errorMessage = jsonObj.getString("errorMessage") - if (errorMessage == "No record found") return@withContext 0 - } - - else -> { - throw IllegalStateException("$responseStatusCode received, !?") } } + } else { + Timber.e("HRNonP Track chunk HTTP error: $statusCode") + failCount += chunk.size } + } catch (e: Exception) { + Timber.e(e, "HRNonP Track chunk push failed: ${chunk.size} records") + failCount += chunk.size } - - } catch (e: SocketTimeoutException) { - Timber.d("get_ hrp error : $e") - return@withContext pushUnSyncedRecordsHRNonPTrack() - - } catch (e: java.lang.IllegalStateException) { - Timber.d("get_ hrp error : $e") - return@withContext -1 } - -1 + + Timber.d("HRNonP Track push complete: $successCount succeeded, $failCount failed out of ${entities.size}") + return@withContext 1 } } @@ -999,6 +1178,9 @@ class HRPRepo @Inject constructor( } } } + private fun updateSyncStatusHrpMBP(entity: HRPMicroBirthPlanCache) { + database.hrpDao.saveRecord(entity) + } private fun updateSyncStatusHrpa(entities: List?) { entities?.let { @@ -1077,10 +1259,12 @@ class HRPRepo @Inject constructor( medicalIssues = cache.medicalIssues, pastCSection = cache.pastCSection, isHighRisk = cache.isHighRisk, - isRegistered = false + isRegistered = false, + isKitHandedOver = false + ) } else { - ecrPost = ecr.asPostModel() + ecrPost = ecr.asPostModel(context) ecrPost.misCarriage = cache.misCarriage ecrPost.homeDelivery = cache.homeDelivery ecrPost.medicalIssues = cache.medicalIssues diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/HbncRepo.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/HbncRepo.kt index 4973964c8..fa6228f55 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/repositories/HbncRepo.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/HbncRepo.kt @@ -68,7 +68,7 @@ class HbncRepo @Inject constructor( true } catch (e: Exception) { - Timber.d("Error : $e raised at saveHbncData") + Timber.e("Error : $e raised at saveHbncData") false } } @@ -84,15 +84,19 @@ class HbncRepo @Inject constructor( val hbncPostSet = mutableSetOf() hbncList.forEach { - hbncPostSet.clear() - val household = database.householdDao.getHousehold(it.hhId) - ?: throw IllegalStateException("No household exists for hhId: ${it.hhId}!!") - val ben = database.benDao.getBen(it.hhId, it.benId) - ?: throw IllegalStateException("No beneficiary exists for benId: ${it.benId}!!") - val hbncCount = database.hbncDao.hbncCount() -// hbncPostSet.add(it.asPostModel(user, household, hbncCount)) - it.syncState = SyncState.SYNCING - database.hbncDao.update(it) + try { + hbncPostSet.clear() + val household = database.householdDao.getHousehold(it.hhId) + ?: throw IllegalStateException("No household exists for hhId: ${it.hhId}!!") + val ben = database.benDao.getBen(it.hhId, it.benId) + ?: throw IllegalStateException("No beneficiary exists for benId: ${it.benId}!!") + val hbncCount = database.hbncDao.hbncCount() +// hbncPostSet.add(it.asPostModel(user, household, hbncCount)) + it.syncState = SyncState.SYNCING + database.hbncDao.update(it) + } catch (e: Exception) { + Timber.e(e, "HbncRepo: Error processing HBNC record benId=${it.benId}") + } } return@withContext true } @@ -142,7 +146,7 @@ class HbncRepo @Inject constructor( return@withContext 1 } - 5002 -> { + 401,5002 -> { if (userRepo.refreshTokenTmc( user.userName, user.password ) @@ -162,11 +166,11 @@ class HbncRepo @Inject constructor( } } catch (e: SocketTimeoutException) { getHBNCDetailsFromServer() - Timber.d("get hbnc data error : $e") + Timber.e("get hbnc data error : $e") return@withContext -2 } catch (e: java.lang.IllegalStateException) { - Timber.d("get hbnc error : $e") + Timber.e("get hbnc error : $e") return@withContext -1 } -1 @@ -225,7 +229,7 @@ class HbncRepo @Inject constructor( return@withContext 1 } - 5002 -> { + 401,5002 -> { if (userRepo.refreshTokenTmc( user.userName, user.password ) @@ -246,11 +250,11 @@ class HbncRepo @Inject constructor( } catch (e: SocketTimeoutException) { getHBNCDetailsFromServer() - Timber.d("get hbnc data error : $e") + Timber.e("get hbnc data error : $e") return@withContext -2 } catch (e: java.lang.IllegalStateException) { - Timber.d("get hbnc error : $e") + Timber.e("get hbnc error : $e") return@withContext -1 } -1 diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/HbycRepo.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/HbycRepo.kt index 8ce5851e2..c5572a5f0 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/repositories/HbycRepo.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/HbycRepo.kt @@ -46,7 +46,7 @@ class HbycRepo @Inject constructor( true } catch (e: Exception) { - Timber.d("Error : $e raised at saveHbycData") + Timber.e("Error : $e raised at saveHbycData") false } } @@ -62,15 +62,19 @@ class HbycRepo @Inject constructor( val hbycPostList = mutableSetOf() hbycList.forEach { - hbycPostList.clear() - val household = database.householdDao.getHousehold(it.hhId) - ?: throw IllegalStateException("No household exists for hhId: ${it.hhId}!!") - val ben = database.benDao.getBen(it.hhId, it.benId) - ?: throw IllegalStateException("No beneficiary exists for benId: ${it.benId}!!") - val hbycCount = database.hbycDao.hbycCount() - hbycPostList.add(it.asPostModel(user, household, ben, hbycCount)) - it.syncState = SyncState.SYNCING - database.hbycDao.setSynced(it) + try { + hbycPostList.clear() + val household = database.householdDao.getHousehold(it.hhId) + ?: throw IllegalStateException("No household exists for hhId: ${it.hhId}!!") + val ben = database.benDao.getBen(it.hhId, it.benId) + ?: throw IllegalStateException("No beneficiary exists for benId: ${it.benId}!!") + val hbycCount = database.hbycDao.hbycCount() + hbycPostList.add(it.asPostModel(user, household, ben, hbycCount)) + it.syncState = SyncState.SYNCING + database.hbycDao.setSynced(it) + } catch (e: Exception) { + Timber.e(e, "HbycRepo: Error processing HBYC record benId=${it.benId}") + } } return@withContext true @@ -126,7 +130,7 @@ class HbycRepo @Inject constructor( return@withContext 1 } - 5002 -> { + 401, 5002 -> { if (userRepo.refreshTokenTmc( user.userName, user.password ) @@ -146,11 +150,11 @@ class HbycRepo @Inject constructor( } } catch (e: SocketTimeoutException) { getHBYCDetailsFromServer() - Timber.d("get hbyc data error : $e") + Timber.e("get hbyc data error : $e") return@withContext -2 } catch (e: java.lang.IllegalStateException) { - Timber.d("get hbyc error : $e") + Timber.e("get hbyc error : $e") return@withContext -1 } -1 @@ -209,7 +213,7 @@ class HbycRepo @Inject constructor( return@withContext 1 } - 5002 -> { + 401,5002 -> { if (userRepo.refreshTokenTmc( user.userName, user.password ) @@ -230,11 +234,11 @@ class HbycRepo @Inject constructor( } catch (e: SocketTimeoutException) { getHBYCDetailsFromServer() - Timber.d("get hbyc data error : $e") + Timber.e("get hbyc data error : $e") return@withContext -2 } catch (e: java.lang.IllegalStateException) { - Timber.d("get hbyc error : $e") + Timber.e("get hbyc error : $e") return@withContext -1 } -1 diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/HouseholdRepo.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/HouseholdRepo.kt index f836cef73..96ba97912 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/repositories/HouseholdRepo.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/HouseholdRepo.kt @@ -2,6 +2,7 @@ package org.piramalswasthya.sakhi.repositories import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import org.piramalswasthya.sakhi.database.room.SyncState import org.piramalswasthya.sakhi.database.room.dao.BenDao import org.piramalswasthya.sakhi.database.room.dao.HouseholdDao import org.piramalswasthya.sakhi.model.BenRegCache @@ -59,5 +60,10 @@ class HouseholdRepo @Inject constructor( } } + suspend fun updateHouseholdToSync(householdId: Long) { + withContext(Dispatchers.IO) { + dao.updateHouseholdToSync(householdId = householdId,"U",2) + } + } } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/ImmunizationRepo.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/ImmunizationRepo.kt index 483a425e2..4d94bbe00 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/repositories/ImmunizationRepo.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/ImmunizationRepo.kt @@ -4,6 +4,7 @@ import com.google.gson.Gson import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.json.JSONObject +import org.piramalswasthya.sakhi.BuildConfig import org.piramalswasthya.sakhi.database.room.SyncState import org.piramalswasthya.sakhi.database.room.dao.BenDao import org.piramalswasthya.sakhi.database.room.dao.ImmunizationDao @@ -35,7 +36,6 @@ class ImmunizationRepo @Inject constructor( val user = preferenceDao.getLoggedInUser() ?: throw IllegalStateException("No user logged in!!") - val lastTimeStamp = preferenceDao.getLastSyncedTimeStamp() try { val response = tmcNetworkApiService.getChildImmunizationDetails( GetDataPaginatedRequest( @@ -51,7 +51,7 @@ class ImmunizationRepo @Inject constructor( if (responseString != null) { val jsonObj = JSONObject(responseString) - val errorMessage = jsonObj.getString("errorMessage") + val errorMessage = jsonObj.optString("errorMessage", "") val responseStatusCode = jsonObj.getInt("statusCode") Timber.d("Pull from amrit child immunization data : $responseStatusCode") when (responseStatusCode) { @@ -67,7 +67,7 @@ class ImmunizationRepo @Inject constructor( return@withContext 1 } - 5002 -> { + 401,5002 -> { if (userRepo.refreshTokenTmc( user.userName, user.password ) @@ -87,11 +87,11 @@ class ImmunizationRepo @Inject constructor( } } catch (e: SocketTimeoutException) { - Timber.d("get_child_immunization error : $e") + Timber.e("get_child_immunization error : $e") return@withContext -2 } catch (e: java.lang.IllegalStateException) { - Timber.d("get_child_immunization error : $e") + Timber.e("get_child_immunization error : $e") return@withContext -1 } -1 @@ -99,8 +99,24 @@ class ImmunizationRepo @Inject constructor( } private suspend fun saveImmunizationCacheFromResponse(dataObj: String): List { - val immunizationList = + val immunizationList: List = try { Gson().fromJson(dataObj, Array::class.java).toList() + } catch (e: Exception) { + // Server may return an object wrapper instead of array directly + val jsonElement = Gson().fromJson(dataObj, com.google.gson.JsonElement::class.java) + if (jsonElement.isJsonObject) { + val jsonObject = jsonElement.asJsonObject + // Try extracting array from known keys + val arrayElement = jsonObject.entrySet().firstOrNull { it.value.isJsonArray }?.value + if (arrayElement != null) { + Gson().fromJson(arrayElement, Array::class.java).toList() + } else { + listOf(Gson().fromJson(dataObj, ImmunizationPost::class.java)) + } + } else { + emptyList() + } + } immunizationList.forEach { immunizationDTO -> val immunization: ImmunizationCache? = immunizationDao.getImmunizationRecord( @@ -116,6 +132,11 @@ class ImmunizationRepo @Inject constructor( return immunizationList } + // RECORD-LEVEL ISOLATION: Immunization records are now sent in + // chunks of 20 instead of one giant batch. Previously, if ANY record in + // the batch was malformed, the ENTIRE batch failed and ALL records stayed + // UNSYNCED. Now each chunk is independent — one bad chunk doesn't affect + // the others. Failed chunks' records stay UNSYNCED for the next sync cycle. suspend fun pushUnSyncedChildImmunizationRecords(): Boolean { return withContext(Dispatchers.IO) { @@ -126,62 +147,64 @@ class ImmunizationRepo @Inject constructor( val immunizationCacheList: List = immunizationDao.getUnsyncedImmunization(SyncState.UNSYNCED) - val immunizationDTOs = mutableListOf() - immunizationCacheList.forEach { cache -> - var immunizationDTO = cache.asPostModel() - val vaccine = immunizationDao.getVaccineById(cache.vaccineId)!! - immunizationDTO.vaccineName = vaccine.vaccineName - immunizationDTOs.add(immunizationDTO) - } - if (immunizationDTOs.isEmpty()) return@withContext true - try { - val response = tmcNetworkApiService.postChildImmunizationDetails(immunizationDTOs) - val statusCode = response.code() - if (statusCode == 200) { - val responseString = response.body()?.string() - if (responseString != null) { - val jsonObj = JSONObject(responseString) + if (immunizationCacheList.isEmpty()) return@withContext true + + // Chunk records to prevent all-or-nothing batch failure + val CHUNK_SIZE = 20 + val chunks = immunizationCacheList.chunked(CHUNK_SIZE) + var successCount = 0 + var failCount = 0 + + for (chunk in chunks) { + try { + val chunkDtos = chunk.map { cache -> + val immunizationDTO = cache.asPostModel() + val vaccine = immunizationDao.getVaccineById(cache.vaccineId)!! + immunizationDTO.vaccineName = vaccine.vaccineName + immunizationDTO + } - val responseStatusCode = jsonObj.getInt("statusCode") - Timber.d("Push to Amrit Child Immunization data : $responseStatusCode") - when (responseStatusCode) { - 200 -> { - try { - updateSyncStatusImmunization(immunizationCacheList) - return@withContext true - } catch (e: Exception) { - Timber.d("Child Immunization entries not synced $e") + val response = tmcNetworkApiService.postChildImmunizationDetails(chunkDtos) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Push to Amrit Child Immunization chunk: $responseStatusCode") + when (responseStatusCode) { + 200 -> { + updateSyncStatusImmunization(chunk) + successCount += chunk.size } - } - 5002 -> { - if (userRepo.refreshTokenTmc( - user.userName, user.password - ) - ) throw SocketTimeoutException("Refreshed Token!") - else throw IllegalStateException("User Logged out!!") - } - - 5000 -> { - val errorMessage = jsonObj.getString("errorMessage") - if (errorMessage == "No record found") return@withContext false - } + 401, 5002 -> { + // Token expired — try refreshing for subsequent chunks + if (userRepo.refreshTokenTmc(user.userName, user.password)) { + Timber.d("Token refreshed, chunk will retry next cycle") + } + failCount += chunk.size + } - else -> { - throw IllegalStateException("$responseStatusCode received, dont know what todo!?") + else -> { + Timber.e("Immunization chunk failed with statusCode: $responseStatusCode") + failCount += chunk.size + } } } + } else { + Timber.e("Immunization chunk HTTP error: $statusCode") + failCount += chunk.size } + } catch (e: Exception) { + Timber.e(e, "Immunization chunk push failed: ${chunk.size} records") + failCount += chunk.size } - - } catch (e: SocketTimeoutException) { - Timber.d("save_child_immunization error : $e") - return@withContext pushUnSyncedChildImmunizationRecords() - } catch (e: java.lang.IllegalStateException) { - Timber.d("save_child_immunization error : $e") - return@withContext false } - false + + Timber.d("Immunization push complete: $successCount succeeded, $failCount failed out of ${immunizationCacheList.size}") + // Worker succeeds — failed records stay UNSYNCED for next cycle + return@withContext true } } @@ -201,13 +224,6 @@ class ImmunizationRepo @Inject constructor( val timeString = timeFormat.format(millis) return "${dateString}T${timeString}.000Z" } - - private fun getLongFromDate(dateString: String): Long { - //Jul 22, 2023 8:17:23 AM" - val f = SimpleDateFormat("MMM d, yyyy h:mm:ss a", Locale.ENGLISH) - val date = f.parse(dateString) - return date?.time ?: throw IllegalStateException("Invalid date for dateReg") - } } suspend fun getVaccineDetailsFromServer(): Int { @@ -215,7 +231,6 @@ class ImmunizationRepo @Inject constructor( val user = preferenceDao.getLoggedInUser() ?: throw IllegalStateException("No user logged in!!") - val lastTimeStamp = preferenceDao.getLastSyncedTimeStamp() try { val response = tmcNetworkApiService.getAllChildVaccines(category = "CHILD") val statusCode = response.code() @@ -224,7 +239,7 @@ class ImmunizationRepo @Inject constructor( if (responseString != null) { val jsonObj = JSONObject(responseString) - val errorMessage = jsonObj.getString("errorMessage") + val errorMessage = jsonObj.optString("errorMessage", "") val responseStatusCode = jsonObj.getInt("statusCode") Timber.d("Pull from amrit child vaccine data : $responseStatusCode") when (responseStatusCode) { @@ -240,7 +255,7 @@ class ImmunizationRepo @Inject constructor( return@withContext 1 } - 5002 -> { + 401,5002 -> { if (userRepo.refreshTokenTmc( user.userName, user.password ) @@ -260,11 +275,11 @@ class ImmunizationRepo @Inject constructor( } } catch (e: SocketTimeoutException) { - Timber.d("get_child_vaccines error : $e") + Timber.e("get_child_vaccines error : $e") getVaccineDetailsFromServer() } catch (e: java.lang.IllegalStateException) { - Timber.d("get_child_vaccines error : $e") + Timber.e("get_child_vaccines error : $e") return@withContext -1 } -1 @@ -273,11 +288,24 @@ class ImmunizationRepo @Inject constructor( private suspend fun saveVaccinesFromResponse(dataObj: String) { val vaccineList = Gson().fromJson(dataObj, Array::class.java).toList() + val mitaninOnlyVaccines = setOf( + "PCV-1", + "PCV-2", + "PCV-Booster" + ) vaccineList.forEach { vaccine -> - val existingVaccine: Vaccine? = immunizationDao.getVaccineByName(vaccine.vaccineName) + if ( + vaccine.vaccineName in mitaninOnlyVaccines && + !BuildConfig.FLAVOR.contains("mitanin", ignoreCase = true) + ) { + return@forEach + } + val existingVaccine = immunizationDao.getVaccineByName(vaccine.vaccineName) + if (existingVaccine == null) { immunizationDao.addVaccine(vaccine) } } } + } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/IncentiveRepo.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/IncentiveRepo.kt index 5df9a8fab..007be10f2 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/repositories/IncentiveRepo.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/IncentiveRepo.kt @@ -1,8 +1,14 @@ package org.piramalswasthya.sakhi.repositories +import android.content.Context +import android.net.Uri import com.google.gson.Gson +import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import okhttp3.MultipartBody +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.RequestBody.Companion.asRequestBody import org.json.JSONObject import org.piramalswasthya.sakhi.database.room.dao.IncentiveDao import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao @@ -11,10 +17,15 @@ import org.piramalswasthya.sakhi.model.IncentiveActivityListRequest import org.piramalswasthya.sakhi.model.IncentiveActivityNetwork import org.piramalswasthya.sakhi.model.IncentiveRecordListRequest import org.piramalswasthya.sakhi.model.IncentiveRecordNetwork +import org.piramalswasthya.sakhi.model.UploadResponse import org.piramalswasthya.sakhi.model.User import org.piramalswasthya.sakhi.model.getDateTimeStringFromLong import org.piramalswasthya.sakhi.network.AmritApiService +import org.piramalswasthya.sakhi.utils.HelperUtil.getFilesName +import org.piramalswasthya.sakhi.utils.HelperUtil.toRequestBody import timber.log.Timber +import java.io.File +import java.io.FileOutputStream import java.net.SocketTimeoutException import java.util.Calendar import javax.inject.Inject @@ -23,17 +34,22 @@ class IncentiveRepo @Inject constructor( private val amritApiService: AmritApiService, private val incentiveDao: IncentiveDao, private val preferenceDao: PreferenceDao, - private val userRepo: UserRepo + private val userRepo: UserRepo, + @ApplicationContext private val context: Context ) { val list = incentiveDao.getAllRecords() + val activity_list = incentiveDao.getAllActivity() suspend fun pullAndSaveAllIncentiveActivities(user: User): Boolean { return withContext(Dispatchers.IO) { try { - val requestBody = IncentiveActivityListRequest(0, 0) + val currentLang = preferenceDao.getCurrentLanguage().symbol + val stateId = user.state.id + val districtId = user.district.id + val requestBody = IncentiveActivityListRequest(stateId, districtId, currentLang) val response = amritApiService.getAllIncentiveActivities(requestBody = requestBody) val statusCode = response.code() if (statusCode == 200) { @@ -57,7 +73,7 @@ class IncentiveRepo @Inject constructor( return@withContext true } - 5002 -> { + 401, 5002 -> { if (userRepo.refreshTokenTmc( user.userName, user.password ) @@ -77,7 +93,7 @@ class IncentiveRepo @Inject constructor( } } catch (e: SocketTimeoutException) { - Timber.d("incentives error : $e") + Timber.e("incentives error : $e") pullAndSaveAllIncentiveActivities(user) return@withContext true } catch (e: Exception) { @@ -94,24 +110,23 @@ class IncentiveRepo @Inject constructor( Gson().fromJson(dataObj, Array::class.java).toList() val activityList = activities.map { it.asCacheModel() } - activityList.forEach { activity -> - val activityCache = incentiveDao.getActivityById(activity.id) - if (activityCache == null) { - incentiveDao.insert(activity) - } - } + incentiveDao.insert(*activityList.toTypedArray()) + } suspend fun pullAndSaveAllIncentiveRecords(user: User): Boolean { return withContext(Dispatchers.IO) { try { + val requestBody = IncentiveRecordListRequest( - user.userId, getDateTimeStringFromLong( + user.userId, + getDateTimeStringFromLong( preferenceDao.lastIncentivePullTimestamp )!!, getDateTimeStringFromLong( Calendar.getInstance().setToEndOfTheDay().timeInMillis - )!! + )!!, + villageID = user.state.id ) val response = amritApiService.getAllIncentiveRecords(requestBody = requestBody) val statusCode = response.code() @@ -136,7 +151,7 @@ class IncentiveRepo @Inject constructor( return@withContext true } - 5002 -> { + 401, 5002 -> { if (userRepo.refreshTokenTmc( user.userName, user.password ) @@ -155,7 +170,7 @@ class IncentiveRepo @Inject constructor( } } } catch (e: SocketTimeoutException) { - Timber.d("incentives error : $e") + Timber.e("incentives error : $e") pullAndSaveAllIncentiveRecords(user) return@withContext true } catch (e: Exception) { @@ -169,12 +184,174 @@ class IncentiveRepo @Inject constructor( private suspend fun saveIncentiveRecordsData(dataObj: String) { val records = Gson().fromJson(dataObj, Array::class.java).toList() val recordList = records.map { it.asCacheModel() } - recordList.forEach { - val record = - incentiveDao.getRecordById(it.id) - if (record == null) { - incentiveDao.insert(it) + incentiveDao.insert(*recordList.toTypedArray()) + + } + + /* suspend fun uploadIncentiveFiles( + id : Long, + userId: Long, + moduleName: String, + fileUris: List + ): Result { + return try { + val fileParts = mutableListOf() + + fileUris.forEach { uriString -> + val uri = Uri.parse(uriString) + val file = createTempFileFromUri(uri) + + if (file != null) { + val requestFile = file.asRequestBody( + getMimeType(uri)?.toMediaTypeOrNull() + ) + + val part = MultipartBody.Part.createFormData( + "files", + file.name, + requestFile + ) + + fileParts.add(part) + } else { + Timber.w("Could not create file from URI: $uriString") + } + } + + if (fileParts.isEmpty()) { + return Result.failure(Exception("No valid files to upload")) + } + + val response = amritApiService.uploadIncentiveDocuments( + id = id.toRequestBody(), + userId = userId.toRequestBody(), + moduleName = moduleName.toRequestBody(), + images = fileParts + ) + + // Clean up temp files + fileParts.forEach { part -> + // Delete temp files if needed + } + + if (response.isSuccessful && response.body() != null) { + Result.success(response.body()!!) + } else { + Result.failure(Exception("Upload failed: ${response.message()}")) + } + + } catch (e: Exception) { + Timber.e(e, "Error uploading files") + Result.failure(e) + } + }*/ + + suspend fun uploadIncentiveFiles( + id: Long, + userId: Long, + moduleName: String, + activityName : String, + fileUris: List + ): Result { + val tempFiles = mutableListOf() + + return try { + val fileParts = mutableListOf() + + fileUris.forEach { uriString -> + val uri = Uri.parse(uriString) + val file = createTempFileFromUri(uri) + + if (file != null) { + tempFiles.add(file) + val mimeType = getMimeType(uri) ?: "image/*" + + val requestFile = file.asRequestBody( + mimeType.toMediaTypeOrNull() + ) + + val part = MultipartBody.Part.createFormData( + "images[]", + file.name, + requestFile + ) + + fileParts.add(part) + } + } + + if (fileParts.isEmpty()) { + return Result.failure(Exception("No valid files to upload")) + } + + val response = amritApiService.uploadIncentiveDocuments( + id = id.toRequestBody(), + userId = userId.toRequestBody(), + moduleName = moduleName.toRequestBody(), + activityName = activityName.toRequestBody(), + images = fileParts + ) + + if (response.isSuccessful && response.body() != null) { + Result.success(response.body()!!) + } else { + Result.failure( + Exception("Upload failed: ${response.code()} ${response.message()}") + ) + } + + } catch (e: Exception) { + Timber.e(e, "Error uploading files") + Result.failure(e) + } + finally { + tempFiles.forEach { file -> + if (file.exists()) { + val deleted = file.delete() + if (deleted) { + Timber.d("Temp file deleted: ${file.name}") + } else { + Timber.w("Failed to delete temp file: ${file.name}") + } + } } } } + + + private fun createTempFileFromUri(uri: Uri): File? { + return try { + val inputStream = context.contentResolver.openInputStream(uri) + ?: run { + Timber.w("InputStream is null for URI: $uri") + return null + } + + val fileName = getFilesName(uri, context) ?: "temp_file_${System.currentTimeMillis()}" + val tempFile = File(context.cacheDir, fileName) + + inputStream.use { input -> + FileOutputStream(tempFile).use { output -> + input.copyTo(output) + } + } + + if (tempFile.length() == 0L) { + Timber.w("Temp file is empty, deleting: ${tempFile.name}") + tempFile.delete() + return null + } + + tempFile + } catch (e: Exception) { + Timber.e(e, "Error creating temp file from URI") + null + } + } + + private fun getMimeType(uri: Uri): String? { + return context.contentResolver.getType(uri) + } + + } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/InfantRegRepo.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/InfantRegRepo.kt index b962e61af..0ae72707c 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/repositories/InfantRegRepo.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/InfantRegRepo.kt @@ -56,30 +56,44 @@ class InfantRegRepo @Inject constructor( val infantRegPostList = mutableSetOf() + // RECORD-LEVEL ISOLATION: Each infant registration record is + // processed independently. If one record fails, remaining records continue. + // Failed records stay UNSYNCED and retry on next sync cycle. + var successCount = 0 + var failCount = 0 + infantRegList.forEach { - infantRegPostList.clear() - val ben = benDao.getBen(it.motherBenId) - ?: throw IllegalStateException("No beneficiary exists for benId: ${it.motherBenId}!!") - infantRegPostList.add(it.asPostModel()) - it.syncState = SyncState.SYNCING - infantRegDao.updateInfantReg(it) - val uploadDone = postDataToAmritServer(infantRegPostList) - if (uploadDone) { - it.processed = "P" - it.syncState = SyncState.SYNCED - } else { + try { + infantRegPostList.clear() + val ben = benDao.getBen(it.motherBenId) + ?: throw IllegalStateException("No beneficiary exists for benId: ${it.motherBenId}!!") + infantRegPostList.add(it.asPostModel()) + it.syncState = SyncState.SYNCING + infantRegDao.updateInfantReg(it) + val uploadDone = postDataToAmritServer(infantRegPostList) + if (uploadDone) { + it.processed = "P" + it.syncState = SyncState.SYNCED + successCount++ + } else { + it.syncState = SyncState.UNSYNCED + failCount++ + } + infantRegDao.updateInfantReg(it) + } catch (e: Exception) { + Timber.e(e, "Infant registration push failed for motherBenId: ${it.motherBenId}") it.syncState = SyncState.UNSYNCED + infantRegDao.updateInfantReg(it) + failCount++ } - infantRegDao.updateInfantReg(it) - if (!uploadDone) - return@withContext false } + Timber.d("Infant registration push complete: $successCount succeeded, $failCount failed out of ${infantRegList.size}") return@withContext true } } - private suspend fun postDataToAmritServer(infantRegPostList: MutableSet): Boolean { + private suspend fun postDataToAmritServer(infantRegPostList: MutableSet, retryCount: Int = 3): Boolean { if (infantRegPostList.isEmpty()) return false val user = preferenceDao.getLoggedInUser() @@ -106,7 +120,7 @@ class InfantRegRepo @Inject constructor( return true } - 5002 -> { + 401, 5002 -> { if (userRepo.refreshTokenTmc( user.userName, user.password @@ -130,10 +144,12 @@ class InfantRegRepo @Inject constructor( Timber.w("Bad Response from server, need to check $infantRegPostList $response ") return false } catch (e: SocketTimeoutException) { - Timber.d("Caught exception $e here") - return postDataToAmritServer(infantRegPostList) + Timber.e("Caught exception $e here") + if (retryCount > 0) return postDataToAmritServer(infantRegPostList, retryCount - 1) + Timber.e("postDataToAmritServer: max retries exhausted") + return false } catch (e: JSONException) { - Timber.d("Caught exception $e here") + Timber.e("Caught exception $e here") return false } } @@ -175,7 +191,7 @@ class InfantRegRepo @Inject constructor( return@withContext 1 } - 5002 -> { + 401,5002 -> { if (userRepo.refreshTokenTmc( user.userName, user.password ) @@ -195,11 +211,11 @@ class InfantRegRepo @Inject constructor( } } catch (e: SocketTimeoutException) { - Timber.d("get_infant_reg error : $e") + Timber.e("get_infant_reg error : $e") return@withContext -2 } catch (e: java.lang.IllegalStateException) { - Timber.d("get_infant_reg error : $e") + Timber.e("get_infant_reg error : $e") return@withContext -1 } -1 diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/KalaAzarRepo.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/KalaAzarRepo.kt new file mode 100644 index 000000000..b2ccb0895 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/KalaAzarRepo.kt @@ -0,0 +1,265 @@ +package org.piramalswasthya.sakhi.repositories + +import com.google.gson.Gson +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.json.JSONObject +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.database.room.dao.BenDao +import org.piramalswasthya.sakhi.database.room.dao.KalaAzarDao +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao +import org.piramalswasthya.sakhi.helpers.Konstants +import org.piramalswasthya.sakhi.model.KalaAzarScreeningCache +import org.piramalswasthya.sakhi.network.AmritApiService +import org.piramalswasthya.sakhi.network.GetDataPaginatedRequest +import org.piramalswasthya.sakhi.network.GetDataPaginatedRequestForDisease +import org.piramalswasthya.sakhi.network.KALAZARScreeningDTO +import org.piramalswasthya.sakhi.network.KalaAzarScreeningRequestDTO +import timber.log.Timber +import java.net.SocketTimeoutException +import java.text.SimpleDateFormat +import java.util.Locale +import javax.inject.Inject + +class KalaAzarRepo @Inject constructor( + private val kalaAzarDao: KalaAzarDao, + private val benDao: BenDao, + private val preferenceDao: PreferenceDao, + private val userRepo: UserRepo, + private val tmcNetworkApiService: AmritApiService +) { + + suspend fun getKalaAzarScreening(benId: Long): KalaAzarScreeningCache? { + return withContext(Dispatchers.IO) { + kalaAzarDao.getKalaAzarScreening(benId) + } + } + + suspend fun saveKalaAzarScreening(kalaAzarScreeningCache: KalaAzarScreeningCache) { + withContext(Dispatchers.IO) { + kalaAzarDao.saveKalaAzarScreening(kalaAzarScreeningCache) + } + } + + suspend fun getKalaAzarSuspected(benId: Long): KalaAzarScreeningCache? { + return withContext(Dispatchers.IO) { + kalaAzarDao.getKalaAzarSuspected(benId) + } + } + + /* suspend fun saveKalaAzarSuspected(tbSuspectedCache: TBSuspectedCache) { + withContext(Dispatchers.IO) { + kalaAzarDao.saveTbSuspected(tbSuspectedCache) + } + }*/ + + suspend fun getKalaAzarScreeningDetailsFromServer(): Int { + return withContext(Dispatchers.IO) { + val user = + preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") + val lastTimeStamp = preferenceDao.getLastSyncedTimeStamp() + try { + val response = tmcNetworkApiService.getMalariaScreeningData( + GetDataPaginatedRequestForDisease( + ashaId = user.userId, + pageNo = 0, + fromDate = BenRepo.getCurrentDate(Konstants.defaultTimeStamp), + toDate = getCurrentDate(), + diseaseTypeID = 2 + ) + + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + + val errorMessage = jsonObj.optString("errorMessage", "") + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Pull from amrit tb screening data : $responseStatusCode") + when (responseStatusCode) { + 200 -> { + try { + val dataObj = jsonObj.getString("data") + saveKalaAzarScreeningCacheFromResponse(dataObj) + } catch (e: Exception) { + Timber.d("Kala Azar Screening entries not synced $e") + return@withContext 0 + } + + return@withContext 1 + } + + 5002 -> { + if (userRepo.refreshTokenTmc( + user.userName, user.password + ) + ) throw SocketTimeoutException("Refreshed Token!") + else throw IllegalStateException("User Logged out!!") + } + + 5000 -> { + if (errorMessage == "No record found") return@withContext 0 + } + + else -> { + throw IllegalStateException("$responseStatusCode received, dont know what todo!?") + } + } + } + } + + } catch (e: SocketTimeoutException) { + Timber.e("get_tb error : $e") + return@withContext -2 + + } catch (e: java.lang.IllegalStateException) { + Timber.e("get_tb error : $e") + return@withContext -1 + } + -1 + } + } + + private suspend fun saveKalaAzarScreeningCacheFromResponse(dataObj: String): MutableList { + val kalaAzarScreeningList = mutableListOf() + val kalaAzarLists: List = if (dataObj.trimStart().startsWith("[")) { + Gson().fromJson(dataObj, Array::class.java)?.toList() ?: emptyList() + } else { + val requestDTO = Gson().fromJson(dataObj, KalaAzarScreeningRequestDTO::class.java) + requestDTO?.kalaAzarLists ?: emptyList() + } + kalaAzarLists.forEach { kalaAzarScreeningDTO -> + kalaAzarScreeningDTO.visitDate?.let { + var kalaAzarScreeningCache: KalaAzarScreeningCache? = + kalaAzarDao.getKalaAzarScreening( + kalaAzarScreeningDTO.benId, + getLongFromDate(kalaAzarScreeningDTO.visitDate), + getLongFromDate(kalaAzarScreeningDTO.visitDate) - 19_800_000 + ) + if (kalaAzarScreeningCache == null) { + benDao.getBen(kalaAzarScreeningDTO.benId)?.let { + kalaAzarDao.saveKalaAzarScreening(kalaAzarScreeningDTO.toCache()) + } + } + } + } + return kalaAzarScreeningList + } + + + + // RECORD-LEVEL ISOLATION: Coordinator always returns true so the + // WorkManager worker succeeds. Failed records stay UNSYNCED for next cycle. + suspend fun pushUnSyncedRecords(): Boolean { + val screeningResult = pushUnSyncedRecordsKalaAzarScreening() + Timber.d("Kala Azar push result: screening=$screeningResult") + // Worker succeeds — failed records stay UNSYNCED for next cycle + return true + } + + // RECORD-LEVEL ISOLATION: Kala Azar Screening records are now sent in + // chunks of 20 instead of one giant batch. Previously, if ANY record in + // the batch was malformed, the ENTIRE batch failed and ALL records stayed + // UNSYNCED. Now each chunk is independent — one bad chunk doesn't affect + // the others. Failed chunks' records stay UNSYNCED for the next sync cycle. + private suspend fun pushUnSyncedRecordsKalaAzarScreening(): Int { + + return withContext(Dispatchers.IO) { + val user = + preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") + + val tbsnList: List = kalaAzarDao.getKalaAzarScreening(SyncState.UNSYNCED) + + if (tbsnList.isEmpty()) return@withContext 1 + + val CHUNK_SIZE = 20 + val chunks = tbsnList.chunked(CHUNK_SIZE) + var successCount = 0 + var failCount = 0 + + for (chunk in chunks) { + try { + val chunkDtos = chunk.map { it.toDTO() } + + val response = tmcNetworkApiService.saveKalaAzarScreeningData( + KalaAzarScreeningRequestDTO( + userId = user.userId, + kalaAzarLists = chunkDtos + ) + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Push to Amrit Kala Azar Screening chunk: $responseStatusCode") + when (responseStatusCode) { + 200 -> { + updateSyncStatusScreening(chunk) + successCount += chunk.size + } + + 401, 5002 -> { + if (userRepo.refreshTokenTmc(user.userName, user.password)) { + Timber.d("Token refreshed, Kala Azar chunk will retry next cycle") + } + failCount += chunk.size + } + + else -> { + Timber.e("Kala Azar Screening chunk failed with statusCode: $responseStatusCode") + failCount += chunk.size + } + } + } + } else { + Timber.e("Kala Azar Screening chunk HTTP error: $statusCode") + failCount += chunk.size + } + } catch (e: Exception) { + Timber.e(e, "Kala Azar Screening chunk push failed: ${chunk.size} records") + failCount += chunk.size + } + } + + Timber.d("Kala Azar Screening push complete: $successCount succeeded, $failCount failed out of ${tbsnList.size}") + return@withContext 1 + } + } + + + + private suspend fun updateSyncStatusScreening(tbsnList: List) { + tbsnList.forEach { + it.syncState = SyncState.SYNCED + kalaAzarDao.saveKalaAzarScreening(it) + } + } + + + companion object { + private val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) + private val timeFormat = SimpleDateFormat("HH:mm:ss", Locale.ENGLISH) + private fun getCurrentDate(millis: Long = System.currentTimeMillis()): String { + val dateString = dateFormat.format(millis) + val timeString = timeFormat.format(millis) + return "${dateString}T${timeString}.000Z" + } + + private fun getLongFromDate(dateString: String): Long { + val date = try { + SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH).parse(dateString) + } catch (_: Exception) { + SimpleDateFormat("MMM d, yyyy h:mm:ss a", Locale.ENGLISH).parse(dateString) + } + return date?.time ?: throw IllegalStateException("Invalid date for dateReg") + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/LeprosyRepo.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/LeprosyRepo.kt new file mode 100644 index 000000000..bd31fba94 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/LeprosyRepo.kt @@ -0,0 +1,677 @@ +package org.piramalswasthya.sakhi.repositories + +import android.content.Context +import com.google.gson.Gson +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.json.JSONException +import org.json.JSONObject +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.database.room.dao.BenDao +import org.piramalswasthya.sakhi.database.room.dao.LeprosyDao +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao +import org.piramalswasthya.sakhi.helpers.Konstants +import org.piramalswasthya.sakhi.model.BenWithLeprosyScreeningCache +import org.piramalswasthya.sakhi.model.LeprosyFollowUpCache +import org.piramalswasthya.sakhi.model.LeprosyFollowUpRequestDTO +import org.piramalswasthya.sakhi.model.LeprosyScreeningCache +import org.piramalswasthya.sakhi.network.AmritApiService +import org.piramalswasthya.sakhi.network.GetDataPaginatedRequestForDisease +import org.piramalswasthya.sakhi.network.LeprosyFollowUpDTO +import org.piramalswasthya.sakhi.network.LeprosyScreeningDTO +import org.piramalswasthya.sakhi.network.LeprosyScreeningRequestDTO +import timber.log.Timber +import java.net.SocketTimeoutException +import java.text.SimpleDateFormat +import java.util.Locale +import javax.inject.Inject + +class LeprosyRepo @Inject constructor( + private val leprosyDao: LeprosyDao, + private val benDao: BenDao, + private val preferenceDao: PreferenceDao, + private val userRepo: UserRepo, + private val tmcNetworkApiService: AmritApiService, + @ApplicationContext private val context: Context + +) { + + suspend fun getLeprosyScreening(benId: Long): LeprosyScreeningCache? { + return withContext(Dispatchers.IO) { + leprosyDao.getLeprosyScreening(benId) + } + } + + suspend fun saveLeprosyScreening(leprosyScreeningCache: LeprosyScreeningCache) { + withContext(Dispatchers.IO) { + leprosyDao.saveLeprosyScreening(leprosyScreeningCache) + } + } + + suspend fun updateLeprosyScreening(leprosyScreeningCache: LeprosyScreeningCache) { + withContext(Dispatchers.IO){ + leprosyDao.updateLeprosyScreening(leprosyScreeningCache) + } + } + + /* suspend fun getTBSuspected(benId: Long): TBSuspectedCache? { + return withContext(Dispatchers.IO) { + malariaDao.getTbSuspected(benId) + } + } + + suspend fun saveTBSuspected(tbSuspectedCache: TBSuspectedCache) { + withContext(Dispatchers.IO) { + malariaDao.saveTbSuspected(tbSuspectedCache) + } + }*/ + + suspend fun getLeprosyScreeningDetailsFromServer(): Int { + return withContext(Dispatchers.IO) { + val user = + preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") + val lastTimeStamp = preferenceDao.getLastSyncedTimeStamp() + try { + val response = tmcNetworkApiService.getMalariaScreeningData( + GetDataPaginatedRequestForDisease( + ashaId = user.userId, + pageNo = 0, + fromDate = BenRepo.getCurrentDate(Konstants.defaultTimeStamp), + toDate = getCurrentDate(), + diseaseTypeID = 5, + userName = user.userName + ) + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + + val errorMessage = jsonObj.optString("errorMessage", "") + if (!jsonObj.has("statusCode")) { + Timber.e("Leprosy screening response missing statusCode. Raw response: $responseString") + return@withContext -1 + } + val responseStatusCode = jsonObj.optInt("statusCode", -1) + Timber.d("Pull from amrit leprosy screening data : $responseStatusCode") + when (responseStatusCode) { + 200 -> { + try { + val dataObj = jsonObj.getString("data") + saveTBScreeningCacheFromResponse(dataObj) + } catch (e: Exception) { + Timber.d("Leprosy Screening entries not synced $e") + return@withContext 0 + } + + return@withContext 1 + } + + 5002 -> { + if (userRepo.refreshTokenTmc( + user.userName, user.password + ) + ) throw SocketTimeoutException("Refreshed Token!") + else throw IllegalStateException("User Logged out!!") + } + + 5000 -> { + return@withContext 0 + } + + else -> { + Timber.e("Leprosy screening unexpected statusCode: $responseStatusCode, response: $responseString") + throw IllegalStateException("$responseStatusCode received, dont know what todo!?") + } + } + } + } + + } catch (e: SocketTimeoutException) { + Timber.e("get_tb error : $e") + return@withContext -2 + } catch (e: JSONException) { + Timber.e("JSON parsing error for leprosy screening data: $e") + return@withContext -1 + } catch (e: java.lang.IllegalStateException) { + Timber.e("get_tb error : $e") + return@withContext -1 + } catch (e: Exception) { + Timber.e("get_leprosy_screening unexpected error : $e") + return@withContext -1 + } + -1 + } + } + + private suspend fun saveTBScreeningCacheFromResponse(dataObj: String): MutableList { + val leprosyScreeningList = mutableListOf() + val leprosyLists: List = if (dataObj.trimStart().startsWith("[")) { + Gson().fromJson(dataObj, Array::class.java)?.toList() ?: emptyList() + } else { + val requestDTO = Gson().fromJson(dataObj, LeprosyScreeningRequestDTO::class.java) + requestDTO?.leprosyLists ?: emptyList() + } + leprosyLists.forEach { leprosyScreeningDTO -> + leprosyScreeningDTO.homeVisitDate?.let { + var tbScreeningCache: LeprosyScreeningCache? = + leprosyDao.getLeprosyScreening( + leprosyScreeningDTO.benId, + getLongFromDate(leprosyScreeningDTO.homeVisitDate), + getLongFromDate(leprosyScreeningDTO.homeVisitDate) - 19_800_000 + ) + if (tbScreeningCache == null) { + benDao.getBen(leprosyScreeningDTO.benId)?.let { + leprosyDao.saveLeprosyScreening(leprosyScreeningDTO.toCache()) + } + } + } + } + return leprosyScreeningList + } + + + // RECORD-LEVEL ISOLATION: Coordinator always returns true so the + // WorkManager worker succeeds. Failed records stay UNSYNCED for next cycle. + suspend fun pushUnSyncedRecords(): Boolean { + val screeningResult = pushUnSyncedRecordsLeprosyScreening() + Timber.d("Leprosy Screening push result: screening=$screeningResult") + // Worker succeeds — failed records stay UNSYNCED for next cycle + return true + } + + // RECORD-LEVEL ISOLATION: Leprosy Screening records are now sent in + // chunks of 20 instead of one giant batch. Previously, if ANY record in + // the batch was malformed, the ENTIRE batch failed and ALL records stayed + // UNSYNCED. Now each chunk is independent — one bad chunk doesn't affect + // the others. Failed chunks' records stay UNSYNCED for the next sync cycle. + private suspend fun pushUnSyncedRecordsLeprosyScreening(): Int { + + return withContext(Dispatchers.IO) { + val user = + preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") + + val leprosyasnList: List = leprosyDao.getLeprosyScreening(SyncState.UNSYNCED) + + if (leprosyasnList.isEmpty()) return@withContext 1 + + val CHUNK_SIZE = 20 + val chunks = leprosyasnList.chunked(CHUNK_SIZE) + var successCount = 0 + var failCount = 0 + + for (chunk in chunks) { + try { + val chunkDtos = chunk.map { it.toDTO() } + + val response = tmcNetworkApiService.saveLeprosyScreeningData( + LeprosyScreeningRequestDTO( + userId = user.userId, + leprosyLists = chunkDtos + ) + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Push to Amrit Leprosy Screening chunk: $responseStatusCode") + when (responseStatusCode) { + 200 -> { + updateSyncStatusScreening(chunk) + successCount += chunk.size + } + + 401, 5002 -> { + if (userRepo.refreshTokenTmc(user.userName, user.password)) { + Timber.d("Token refreshed, Leprosy Screening chunk will retry next cycle") + } + failCount += chunk.size + } + + else -> { + Timber.e("Leprosy Screening chunk failed with statusCode: $responseStatusCode") + failCount += chunk.size + } + } + } + } else { + Timber.e("Leprosy Screening chunk HTTP error: $statusCode") + failCount += chunk.size + } + } catch (e: Exception) { + Timber.e(e, "Leprosy Screening chunk push failed: ${chunk.size} records") + failCount += chunk.size + } + } + + Timber.d("Leprosy Screening push complete: $successCount succeeded, $failCount failed out of ${leprosyasnList.size}") + return@withContext 1 + } + } + + + + private suspend fun updateSyncStatusScreening(leprosysnList: List) { + leprosysnList.forEach { + it.syncState = SyncState.SYNCED + leprosyDao.saveLeprosyScreening(it) + } + } + + +// /*suspend fun getCurrentFollowUp(benId: Long, visitNumber: Int): LeprosyFollowUpCache? { +// return withContext(Dispatchers.IO) { +// leprosyDao.getFollowUpByVisit(benId, visitNumber) +// } +// }*/ + + suspend fun getAllFollowUpsForBeneficiary(benId: Long): List { + return withContext(Dispatchers.IO) { + leprosyDao.getAllFollowUpsForBeneficiary(benId) + } + } + + suspend fun getFollowUpsForCurrentVisit(benId: Long, visitNumber: Int): List { + return withContext(Dispatchers.IO) { + leprosyDao.getFollowUpsByVisit(benId, visitNumber) + } + } + + suspend fun saveFollowUp(followUp: LeprosyFollowUpCache) { + withContext(Dispatchers.IO) { + leprosyDao.insertFollowUp(followUp) + } + } + + suspend fun updateFollowUp(followUp: LeprosyFollowUpCache) { + withContext(Dispatchers.IO) { + leprosyDao.updateFollowUp(followUp) + } + } + + suspend fun getFollowUpsForVisit(benId: Long, visitNumber: Int): List { + return withContext(Dispatchers.IO) { + leprosyDao.getFollowUpsForVisit(benId, visitNumber) + } + } + + suspend fun completeVisitAndStartNext(benId: Long): Boolean { + return withContext(Dispatchers.IO) { + val screening = leprosyDao.getLeprosyScreening(benId) ?: return@withContext false + + screening.currentVisitNumber++ + screening.leprosyStatus = context.resources.getStringArray(R.array.leprosy_status)[0] + screening.isConfirmed = false + screening.leprosySymptomsPosition = 1 + screening.syncState = SyncState.UNSYNCED + + leprosyDao.updateLeprosyScreening(screening) + true + } + } + + + + suspend fun getBenWithLeprosyData(benId: Long): BenWithLeprosyScreeningCache? { + return withContext(Dispatchers.IO) { + benDao.getBenWithLeprosyScreeningAndFollowUps(benId) + } + } + + + suspend fun getAllLeprosyDataFromServer(): Int { + return withContext(Dispatchers.IO) { + val user = preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") + try { + val response = tmcNetworkApiService.getAllLeprosyData( + GetDataPaginatedRequestForDisease( + ashaId = user.userId, + pageNo = 0, + fromDate = BenRepo.getCurrentDate(Konstants.defaultTimeStamp), + toDate = getCurrentDate(), + diseaseTypeID = 5, + userName = user.userName + ) + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + Timber.d("Raw leprosy response: $responseString") + + val jsonObj = JSONObject(responseString) + + if (!jsonObj.has("statusCode")) { + Timber.e("Leprosy data response missing statusCode. Raw response: $responseString") + return@withContext -1 + } + val statusCodeFromResponse = jsonObj.optInt("statusCode", -1) + val status = jsonObj.optString("status", "") + + Timber.d("Pull all leprosy data - Status: $statusCodeFromResponse, Status: $status") + + when (statusCodeFromResponse) { + 200 -> { + try { + val dataArray = jsonObj.getJSONArray("data") + saveAllLeprosyDataFromResponse(dataArray.toString()) + return@withContext 1 + } catch (e: Exception) { + Timber.d("Leprosy entries not synced $e") + return@withContext 0 + } + } + + 5002 -> { + if (userRepo.refreshTokenTmc(user.userName, user.password)) + throw SocketTimeoutException("Refreshed Token!") + else throw IllegalStateException("User Logged out!!") + } + + 5000 -> { + return@withContext 0 + } + + else -> { + Timber.e("Leprosy data unexpected statusCode: $statusCodeFromResponse, response: $responseString") + throw IllegalStateException("$statusCodeFromResponse received, dont know what todo!?") + } + } + } + } else { + Timber.e("HTTP error for leprosy data: $statusCode") + } + } catch (e: SocketTimeoutException) { + Timber.e("get_all_leprosy error : $e") + return@withContext -2 + } catch (e: JSONException) { + Timber.e("JSON parsing error for leprosy data: $e") + return@withContext -1 + } catch (e: java.lang.IllegalStateException) { + Timber.e("get_all_leprosy error : $e") + return@withContext -1 + } catch (e: Exception) { + Timber.e("get_all_leprosy unexpected error : $e") + return@withContext -1 + } + -1 + } + } + + suspend fun getAllLeprosyFollowUpDataFromServer(): Int { + return withContext(Dispatchers.IO) { + val user = preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") + try { + val response = tmcNetworkApiService.getAllLeprosyFollowUpData( + GetDataPaginatedRequestForDisease( + ashaId = user.userId, + pageNo = 0, + fromDate = BenRepo.getCurrentDate(Konstants.defaultTimeStamp), + toDate = getCurrentDate(), + diseaseTypeID = 5, + userName = user.userName + ) + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + Timber.d("Raw leprosy followup response: $responseString") + + val jsonObj = JSONObject(responseString) + + if (!jsonObj.has("statusCode")) { + Timber.e("Leprosy followup response missing statusCode. Raw response: $responseString") + return@withContext -1 + } + val statusCodeFromResponse = jsonObj.optInt("statusCode", -1) + val status = jsonObj.optString("status", "") + + Timber.d("Pull all leprosy followup data - Status: $statusCodeFromResponse, Status: $status") + + when (statusCodeFromResponse) { + 200 -> { + try { + val dataArray = jsonObj.getJSONArray("data") + saveAllLeprosyFollowUpDataFromResponse(dataArray.toString()) + return@withContext 1 + } catch (e: Exception) { + Timber.d("Leprosy followup entries not synced $e") + return@withContext 0 + } + } + + 5002 -> { + if (userRepo.refreshTokenTmc(user.userName, user.password)) + throw SocketTimeoutException("Refreshed Token!") + else throw IllegalStateException("User Logged out!!") + } + + 5000 -> { + return@withContext 0 + } + + else -> { + Timber.e("Leprosy followup unexpected statusCode: $statusCodeFromResponse, response: $responseString") + throw IllegalStateException("$statusCodeFromResponse received, dont know what todo!?") + } + } + } + } else { + Timber.e("HTTP error for leprosy followup data: $statusCode") + } + } catch (e: SocketTimeoutException) { + Timber.e("get_all_leprosy_followup error : $e") + return@withContext -2 + } catch (e: JSONException) { + Timber.e("JSON parsing error for leprosy followup data: $e") + return@withContext -1 + } catch (e: java.lang.IllegalStateException) { + Timber.e("get_all_leprosy_followup error : $e") + return@withContext -1 + } catch (e: Exception) { + Timber.e("get_all_leprosy_followup unexpected error : $e") + return@withContext -1 + } + -1 + } + } + + // RECORD-LEVEL ISOLATION: Leprosy FollowUp records are now sent in + // chunks of 20 instead of one giant batch. Previously, if ANY record in + // the batch was malformed, the ENTIRE batch failed and ALL records stayed + // UNSYNCED. Now each chunk is independent — one bad chunk doesn't affect + // the others. Failed chunks' records stay UNSYNCED for the next sync cycle. + suspend fun pushUnSyncedLeprosyFollowUpData(): Int { + return withContext(Dispatchers.IO) { + val user = preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") + + val unsyncedFollowUps: List = leprosyDao.getAllFollowUpsByBenId().filter { + it.syncState == SyncState.UNSYNCED + } + + if (unsyncedFollowUps.isEmpty()) return@withContext 1 + + val CHUNK_SIZE = 20 + val chunks = unsyncedFollowUps.chunked(CHUNK_SIZE) + var successCount = 0 + var failCount = 0 + + for (chunk in chunks) { + try { + val chunkDtos = chunk.map { it.toDTO() } + + val response = tmcNetworkApiService.saveLeprosyFollowUpData(chunkDtos) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Push Leprosy FollowUp chunk: $responseStatusCode") + when (responseStatusCode) { + 200 -> { + updateSyncStatusFollowUps(chunk) + successCount += chunk.size + } + + 401, 5002 -> { + if (userRepo.refreshTokenTmc(user.userName, user.password)) { + Timber.d("Token refreshed, Leprosy FollowUp chunk will retry next cycle") + } + failCount += chunk.size + } + + else -> { + Timber.e("Leprosy FollowUp chunk failed with statusCode: $responseStatusCode") + failCount += chunk.size + } + } + } + } else { + Timber.e("Leprosy FollowUp chunk HTTP error: $statusCode") + failCount += chunk.size + } + } catch (e: Exception) { + Timber.e(e, "Leprosy FollowUp chunk push failed: ${chunk.size} records") + failCount += chunk.size + } + } + + Timber.d("Leprosy FollowUp push complete: $successCount succeeded, $failCount failed out of ${unsyncedFollowUps.size}") + return@withContext 1 + } + } + + private suspend fun saveAllLeprosyDataFromResponse(dataObj: String) { + + + try { + + val leprosyList = Gson().fromJson(dataObj, Array::class.java) + + + leprosyList?.forEachIndexed { index, leprosyScreeningDTO -> + + + leprosyScreeningDTO.homeVisitDate?.let { visitDate -> + + val visitLong = getLongFromDate(visitDate) + val previousVisitWindow = visitLong - 19_800_000 + + + + val existingScreening = leprosyDao.getLeprosyScreening( + leprosyScreeningDTO.benId, + visitLong, + previousVisitWindow + ) + + if (existingScreening == null) { + + + val benExists = benDao.getBen(leprosyScreeningDTO.benId) + + if (benExists != null) { + val cacheObj = leprosyScreeningDTO.toCache().copy( + syncState = SyncState.SYNCED + ) + leprosyDao.saveLeprosyScreening(cacheObj) + + + } else { + Timber.w(" Ben does NOT exist locally → Skipping save for benId=${leprosyScreeningDTO.benId}") + } + + } else { + + Timber.d( + " Existing screening FOUND (id=${existingScreening.id}) → Updating record for benId=${leprosyScreeningDTO.benId}" + ) + + val updatedCache = leprosyScreeningDTO.toCache().copy( + id = existingScreening.id, + syncState = SyncState.SYNCED + ) + + leprosyDao.updateLeprosyScreening(updatedCache) + + } + } ?: run { + Timber.w(" homeVisitDate is NULL for benId=${leprosyScreeningDTO.benId} → Skipping this record") + } + } + + Timber.d(" Successfully saved ${leprosyList?.size ?: 0} leprosy screening records") + + } catch (e: Exception) { + Timber.e(e, "Error saving leprosy data") + throw e + } + } + + + private suspend fun saveAllLeprosyFollowUpDataFromResponse(dataObj: String) { + try { + val followUpList = Gson().fromJson(dataObj, Array::class.java) + followUpList?.forEach { followUpDTO -> + followUpDTO.followUpDate?.let { followUpDate -> + leprosyDao.insertFollowUp(followUpDTO.toCache().copy( + syncState = SyncState.SYNCED + )) + } + } + Timber.d("Successfully upserted ${followUpList?.size ?: 0} leprosy followup records") + } catch (e: Exception) { + Timber.e("Error saving leprosy followup data: $e") + throw e + } + } + + private suspend fun updateSyncStatusFollowUps(followUpList: List) { + followUpList.forEach { + it.syncState = SyncState.SYNCED + leprosyDao.updateFollowUp(it) + } + } + + // RECORD-LEVEL ISOLATION: Coordinator always returns true so the + // WorkManager worker succeeds. Failed records stay UNSYNCED for next cycle. + suspend fun pushAllUnSyncedRecords(): Boolean { + val followUpResult = pushUnSyncedLeprosyFollowUpData() + Timber.d("Leprosy FollowUp push result: followUp=$followUpResult") + // Worker succeeds — failed records stay UNSYNCED for next cycle + return true + } + + companion object { + private val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) + private val timeFormat = SimpleDateFormat("HH:mm:ss", Locale.ENGLISH) + private fun getCurrentDate(millis: Long = System.currentTimeMillis()): String { + val dateString = dateFormat.format(millis) + val timeString = timeFormat.format(millis) + return "${dateString}T${timeString}.000Z" + } + + private fun getLongFromDate(dateString: String): Long { + val date = try { + SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH).parse(dateString) + } catch (_: Exception) { + SimpleDateFormat("MMM d, yyyy h:mm:ss a", Locale.ENGLISH).parse(dateString) + } + return date?.time ?: throw IllegalStateException("Invalid date for dateReg") + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/MaaMeetingRepo.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/MaaMeetingRepo.kt new file mode 100644 index 000000000..83af080d2 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/MaaMeetingRepo.kt @@ -0,0 +1,211 @@ +package org.piramalswasthya.sakhi.repositories + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.database.room.dao.MaaMeetingDao +import org.piramalswasthya.sakhi.model.MaaMeetingEntity +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao +import org.piramalswasthya.sakhi.network.AmritApiService +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.MultipartBody +import okhttp3.RequestBody.Companion.asRequestBody +import okhttp3.RequestBody.Companion.toRequestBody +import java.io.File +import java.util.Locale +import androidx.core.content.FileProvider +import javax.inject.Inject +import com.squareup.moshi.Moshi +import org.piramalswasthya.sakhi.repositories.BenRepo.Companion.getCurrentDate +import android.util.Base64 +import kotlinx.coroutines.flow.Flow +import org.piramalswasthya.sakhi.network.GetDataRequest +import org.piramalswasthya.sakhi.utils.HelperUtil.compressImageToTemp +import org.piramalswasthya.sakhi.utils.HelperUtil.convertToLocalDate +import org.piramalswasthya.sakhi.utils.HelperUtil.convertToServerDate +import org.piramalswasthya.sakhi.utils.HelperUtil.copyToTemp +import org.piramalswasthya.sakhi.utils.HelperUtil.detectExtAndMime +import org.piramalswasthya.sakhi.utils.HelperUtil.getFileName +import org.piramalswasthya.sakhi.model.MaaMeetingGetAllResponse +import java.time.LocalDate +import java.time.format.DateTimeFormatter +import java.time.temporal.ChronoUnit + +class MaaMeetingRepo @Inject constructor( + @ApplicationContext val appContext: Context, + private val dao: MaaMeetingDao, + private val api: AmritApiService, + private val pref: PreferenceDao, + private val moshi: Moshi +) { + + fun buildEntity( + date: String?, + place: String?, + participants: Int?, + villageName: String? = null, + mitaninActivityCheckList : String? = null, +// selectAll : String? = null, + noOfPragnentWoment: String? = null, + noOfLactingMother: String? = null, + u1: String?, + u2: String?, + u3: String?, + u4: String?, + u5: String? + ) = MaaMeetingEntity( + meetingDate = date, + place = place, + mitaninActivityCheckList = mitaninActivityCheckList, +// selectAll = selectAll, + villageName = villageName, + noOfPragnentWomen = noOfPragnentWoment, + noOfLactingMother = noOfLactingMother, + participants = participants, + ashaId = pref.getLoggedInUser()?.userId, + meetingImages = listOfNotNull(u1, u2, u3, u4, u5), + syncState = SyncState.UNSYNCED + ) + + suspend fun save(entity: MaaMeetingEntity) = withContext(Dispatchers.IO) { + val id = dao.insert(entity) + id + } + + suspend fun tryUpsync() = withContext(Dispatchers.IO) { + val pending = dao.getBySyncState(SyncState.UNSYNCED) + if (pending.isEmpty()) return@withContext + pending.forEach { row -> + val imagesParts = (row.meetingImages ?: emptyList()).mapNotNull { uriStr -> + val uri = android.net.Uri.parse(uriStr) + val name = getFileName(uri, appContext) ?: "upload" + val mime = appContext.contentResolver.getType(uri) ?: "application/octet-stream" + val fileForUpload = if (mime.startsWith("image/")) compressImageToTemp( + uri, + name, + appContext + ) else copyToTemp(uri, name, appContext) + fileForUpload?.let { file -> + val body = file.asRequestBody(mime.toMediaTypeOrNull()) + MultipartBody.Part.createFormData("meetingImages", file.name, body) + } + } + + val response = api.postMaaMeetingMultipart( + villageName = (convertToServerDate(row.villageName) ?: "").toRequestBody("text/plain".toMediaTypeOrNull()), + noOfPragnentWoment = (convertToServerDate(row.noOfPragnentWomen) ?: "").toRequestBody("text/plain".toMediaTypeOrNull()), + noOfLactingMother = (convertToServerDate(row.noOfLactingMother) ?: "").toRequestBody("text/plain".toMediaTypeOrNull()), + mitaninActivityCheckList = (convertToServerDate(row.mitaninActivityCheckList) ?: "").toRequestBody("text/plain".toMediaTypeOrNull()), +// selectAll = (convertToServerDate(row.selectAll) ?: "").toRequestBody("text/plain".toMediaTypeOrNull()), + meetingDate = (convertToServerDate(row.meetingDate) ?: "").toRequestBody("text/plain".toMediaTypeOrNull()), + place = (row.place ?: "").toRequestBody("text/plain".toMediaTypeOrNull()), + participants = ((row.participants + ?: 0).toString()).toRequestBody("text/plain".toMediaTypeOrNull()), + ashaId = ((row.ashaId + ?: 0).toString()).toRequestBody("text/plain".toMediaTypeOrNull()), + createdBy = ((pref.getLoggedInUser()?.userName + ?: 0).toString()).toRequestBody("text/plain".toMediaTypeOrNull()), + meetingImages = imagesParts + ) + if (response.isSuccessful) { + dao.updateSyncState(row.id, SyncState.SYNCED) + } + } + } + + suspend fun downSyncAndPersist() = withContext(Dispatchers.IO) { + + val response = api.getMaaMeetings( + GetDataRequest( + 0, + getCurrentDate(pref.getLastSyncedTimeStamp()), + getCurrentDate(), + 0, + pref.getLoggedInUser()?.userId?.toLong()!!, + pref.getLoggedInUser()?.userName!!, + pref.getLoggedInUser()?.userId?.toLong()!! + ) + ) + + if (!response.isSuccessful) return@withContext + + val body = response.body()?.string() ?: return@withContext + val adapter = moshi.adapter(MaaMeetingGetAllResponse::class.java) + val parsed = adapter.fromJson(body) ?: return@withContext + + val serverList = parsed.data ?: emptyList() + + if (serverList.isEmpty()) { + return@withContext + } + + serverList.forEach { item -> + + val imageUriList = (item.meetingImages ?: emptyList()).mapNotNull { base64 -> + try { + val base64Data = base64.substringAfter(",", base64) + val bytes = Base64.decode(base64Data, Base64.DEFAULT) + val (ext, _) = detectExtAndMime(bytes) + + val file = File( + appContext.cacheDir, + "meeting_${System.currentTimeMillis()}.$ext" + ) + + file.outputStream().use { it.write(bytes) } + + FileProvider.getUriForFile( + appContext, + "${appContext.packageName}.provider", + file + ).toString() + + } catch (e: Exception) { + null + } + } + + val entity = MaaMeetingEntity( + id = item.id?.toLong()!!, + meetingDate = convertToLocalDate(item.meetingDate), + place = item.place, + villageName = item.villageName, + mitaninActivityCheckList = item.mitaninActivityCheckList, + noOfLactingMother = item.noOfLactingMother, + noOfPragnentWomen = item.noOfPragnentWoment, + participants = item.participants, + ashaId = item.ashaId, + meetingImages = imageUriList, + syncState = SyncState.SYNCED + ) + + dao.insert(entity) + } + + } + private val formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy", Locale.ENGLISH) + suspend fun isThreeMonthsPassedSinceLastMeeting(meetingDate: String?): Boolean = + withContext(Dispatchers.IO) { + if (meetingDate.isNullOrBlank()) return@withContext true + + val lastMeeting = try { + LocalDate.parse(meetingDate, formatter) + } catch (e: Exception) { + return@withContext true + } + + val today = LocalDate.now() + val threeMonthsLater = lastMeeting.plusMonths(3) + + today >= threeMonthsLater + } + fun getAllMaaMeetings(): Flow> = dao.getAllMaaData() + + suspend fun getMaaMeetingById(id: Long): MaaMeetingEntity? { + return dao.getMaaMeetingById(id) + } +} + + diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/MalariaRepo.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/MalariaRepo.kt new file mode 100644 index 000000000..873a23865 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/MalariaRepo.kt @@ -0,0 +1,591 @@ +package org.piramalswasthya.sakhi.repositories + +import com.google.gson.Gson +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.json.JSONObject +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.database.room.dao.BenDao +import org.piramalswasthya.sakhi.database.room.dao.MalariaDao +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao +import org.piramalswasthya.sakhi.helpers.Konstants +import org.piramalswasthya.sakhi.model.IRSRoundScreening +import org.piramalswasthya.sakhi.model.MalariaConfirmedCasesCache +import org.piramalswasthya.sakhi.model.MalariaScreeningCache +import org.piramalswasthya.sakhi.model.PregnantWomanAncCache +import org.piramalswasthya.sakhi.network.AmritApiService +import org.piramalswasthya.sakhi.network.GetDataPaginatedRequest +import org.piramalswasthya.sakhi.network.GetDataPaginatedRequestForDisease +import org.piramalswasthya.sakhi.network.IRSScreeningRequestDTO +import org.piramalswasthya.sakhi.network.MalariaConfirmedDTO +import org.piramalswasthya.sakhi.network.MalariaConfirmedRequestDTO +import org.piramalswasthya.sakhi.network.MalariaScreeningDTO +import org.piramalswasthya.sakhi.network.MalariaScreeningRequestDTO +import org.piramalswasthya.sakhi.utils.HelperUtil +import timber.log.Timber +import java.net.SocketTimeoutException +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Locale +import javax.inject.Inject + +class MalariaRepo @Inject constructor( + private val malariaDao: MalariaDao, + private val benDao: BenDao, + private val preferenceDao: PreferenceDao, + private val userRepo: UserRepo, + private val tmcNetworkApiService: AmritApiService +) { + + suspend fun getLatestVisitForBen(benId: Long): MalariaScreeningCache? { + return withContext(Dispatchers.IO) { + malariaDao.getLatestVisitForBen(benId) + } + } + + suspend fun getlastvisitIdforBen(benId: Long): Long? { + return withContext(Dispatchers.IO) { + malariaDao.getLastVisitIdForBen(benId) + } + } + + suspend fun saveMalariaScreening(malariaScreeningCache: MalariaScreeningCache) { + withContext(Dispatchers.IO) { + malariaDao.saveMalariaScreening(malariaScreeningCache) + } + } + + suspend fun getMalariaConfirmed(benId: Long): MalariaConfirmedCasesCache? { + return withContext(Dispatchers.IO) { + malariaDao.getMalariaConfirmed(benId) + } + } + + suspend fun saveMalariaConfirmed(tbSuspectedCache: MalariaConfirmedCasesCache) { + withContext(Dispatchers.IO) { + malariaDao.saveMalariaConfirmed(tbSuspectedCache) + } + } + + + suspend fun getIRSScreening(benId: Long): IRSRoundScreening? { + return withContext(Dispatchers.IO) { + malariaDao.getIRSScreening(benId) + } + } + + + suspend fun saveIRSScreening(irsRoundScreening: IRSRoundScreening) { + withContext(Dispatchers.IO) { + if (irsRoundScreening.id == 0) { + malariaDao.saveIRSScreening(irsRoundScreening) + + } else { + malariaDao.update(irsRoundScreening) + + } + } + } + + suspend fun updateIRSRecord(irsRoundScreening: Array) { + withContext(Dispatchers.IO) { + malariaDao.updateIRS(*irsRoundScreening) + } + } + + suspend fun getAllActiveIRSRecords(hhId: Long): List { + return withContext(Dispatchers.IO) { + malariaDao.getAllActiveIRSRecords(hhId) + } + } + + + suspend fun getIRSScreeningDetailsFromServer(): Int { + return withContext(Dispatchers.IO) { + val user = + preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") + val lastTimeStamp = preferenceDao.getLastSyncedTimeStamp() + try { + val response = tmcNetworkApiService.getScreeningData( + 1L + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + + val errorMessage = jsonObj.optString("errorMessage", "") + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Pull from amrit tb screening data : $responseStatusCode") + when (responseStatusCode) { + 200 -> { + try { + val dataObj = jsonObj.getString("data") + saveIRSScreeningCacheFromResponse(dataObj) + } catch (e: Exception) { + Timber.d("IRS Screening entries not synced $e") + return@withContext 0 + } + + return@withContext 1 + } + + 5002 -> { + if (userRepo.refreshTokenTmc( + user.userName, user.password + ) + ) throw SocketTimeoutException("Refreshed Token!") + else throw IllegalStateException("User Logged out!!") + } + + 5000 -> { + if (errorMessage == "No record found") return@withContext 0 + } + + else -> { + throw IllegalStateException("$responseStatusCode received, dont know what todo!?") + } + } + } + } + + } catch (e: SocketTimeoutException) { + Timber.e("get_tb error : $e") + return@withContext -2 + + } catch (e: java.lang.IllegalStateException) { + Timber.e("get_tb error : $e") + return@withContext -1 + } + -1 + } + } + + + suspend fun getMalariaScreeningDetailsFromServer(): Int { + return withContext(Dispatchers.IO) { + val user = + preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") + val lastTimeStamp = preferenceDao.getLastSyncedTimeStamp() + try { + val response = tmcNetworkApiService.getMalariaScreeningData( + GetDataPaginatedRequestForDisease( + ashaId = user.userId, + pageNo = 0, + fromDate = BenRepo.getCurrentDate(Konstants.defaultTimeStamp), + toDate = getCurrentDate(), + diseaseTypeID = 1 + ) + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + + val responseStatusCode = jsonObj.getInt("statusCode") + val errorMessage = jsonObj.optString("errorMessage", "") + Timber.d("Pull from amrit tb screening data : $responseStatusCode") + when (responseStatusCode) { + 200 -> { + try { + val dataObj = jsonObj.getString("data") + saveMalariaScreeningCacheFromResponse(dataObj) + } catch (e: Exception) { + Timber.d("TB Screening entries not synced $e") + return@withContext 0 + } + + return@withContext 1 + } + + 5002 -> { + if (userRepo.refreshTokenTmc( + user.userName, user.password + ) + ) throw SocketTimeoutException("Refreshed Token!") + else throw IllegalStateException("User Logged out!!") + } + + 5000 -> { + if (errorMessage == "No record found") return@withContext 0 + } + + else -> { + throw IllegalStateException("$responseStatusCode received, don't know what todo!?") + } + } + } + } + + } catch (e: SocketTimeoutException) { + Timber.e("get_tb error : $e") + return@withContext -2 + + } catch (e: java.lang.IllegalStateException) { + Timber.e("get_tb error : $e") + return@withContext -1 + } + -1 + } + } + + private suspend fun saveMalariaScreeningCacheFromResponse(dataObj: String): MutableList { + val malariaScreeningList = mutableListOf() + val malariaLists: List = if (dataObj.trimStart().startsWith("[")) { + Gson().fromJson(dataObj, Array::class.java)?.toList() ?: emptyList() + } else { + val requestDTO = Gson().fromJson(dataObj, MalariaScreeningRequestDTO::class.java) + requestDTO?.malariaLists ?: emptyList() + } + malariaLists.forEach { malariaScreeningDTO -> + malariaScreeningDTO.caseDate?.let { + var tbScreeningCache: MalariaScreeningCache? = + malariaDao.getMalariaScreening( + malariaScreeningDTO.benId, + getLongFromDate(malariaScreeningDTO.caseDate), + getLongFromDate(malariaScreeningDTO.caseDate) - 19_800_000 + ) + if (tbScreeningCache == null) { + benDao.getBen(malariaScreeningDTO.benId)?.let { + malariaDao.saveMalariaScreening(malariaScreeningDTO.toCache()) + } + } + } + } + return malariaScreeningList + } + + private suspend fun saveIRSScreeningCacheFromResponse(dataObj: String): MutableList { + val irsScreeningList = mutableListOf() + var requestDTO = Gson().fromJson(dataObj, IRSScreeningRequestDTO::class.java) + requestDTO?.rounds?.forEach { irsScreeningDTO -> + irsScreeningDTO.date?.let { + var iRsScreeningCache: IRSRoundScreening? = + malariaDao.getIRSScreening( + irsScreeningDTO.householdId, + ) + if (iRsScreeningCache == null) { + benDao.getBen(irsScreeningDTO.householdId)?.let { + malariaDao.saveIRSScreening(irsScreeningDTO.toCache()) + } + } + } + } + return irsScreeningList + } + + suspend fun getMalariaConfiremedDetailsFromServer(): Int { + return withContext(Dispatchers.IO) { + val user = + preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") + val lastTimeStamp = preferenceDao.getLastSyncedTimeStamp() + try { + val response = tmcNetworkApiService.getMalariaConfirmedData( + GetDataPaginatedRequestForDisease( + ashaId = user.userId, + pageNo = 0, + fromDate = BenRepo.getCurrentDate(Konstants.defaultTimeStamp), + toDate = getCurrentDate(), + diseaseTypeID = 1 + ) + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + + val errorMessage = jsonObj.optString("errorMessage", "") + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Pull from amrit malaria confirmed data : $responseStatusCode") + when (responseStatusCode) { + 200 -> { + try { + val dataObj = jsonObj.getString("data") + saveMalariaConfirmedCacheFromResponse(dataObj) + } catch (e: Exception) { + Timber.d("Malaria Confirmed entries not synced $e") + return@withContext 0 + } + + return@withContext 1 + } + + 5002 -> { + if (userRepo.refreshTokenTmc( + user.userName, user.password + ) + ) throw SocketTimeoutException("Refreshed Token!") + else throw IllegalStateException("User Logged out!!") + } + + 5000 -> { + if (errorMessage == "No record found") return@withContext 0 + } + + else -> { + throw IllegalStateException("$responseStatusCode received, don't know what todo!?") + } + } + } + } + + } catch (e: SocketTimeoutException) { + Timber.e("get_tb error : $e") + return@withContext -2 + + } catch (e: java.lang.IllegalStateException) { + Timber.e("get_tb error : $e") + return@withContext -1 + } + -1 + } + } + + private suspend fun saveMalariaConfirmedCacheFromResponse(dataObj: String): MutableList { + val malariaConfirmedList = mutableListOf() + val malariaFollowListUp: List = if (dataObj.trimStart().startsWith("[")) { + Gson().fromJson(dataObj, Array::class.java)?.toList() ?: emptyList() + } else { + val requestDTO = Gson().fromJson(dataObj, MalariaConfirmedRequestDTO::class.java) + requestDTO?.malariaFollowListUp ?: emptyList() + } + malariaFollowListUp.forEach { malariaConfirmedDTO -> + malariaConfirmedDTO.dateOfDiagnosis?.let { + val malariaConfirmedCache: MalariaConfirmedCasesCache? = + malariaDao.getMalariaConfirmed( + malariaConfirmedDTO.benId, + getLongFromDate(malariaConfirmedDTO.dateOfDiagnosis), + getLongFromDate(malariaConfirmedDTO.dateOfDiagnosis) - 19_800_000 + ) + if (malariaConfirmedCache == null) { + benDao.getBen(malariaConfirmedDTO.benId)?.let { + malariaDao.saveMalariaConfirmed(malariaConfirmedDTO.toCache()) + } + } + } + } + return malariaConfirmedList + } + + // RECORD-LEVEL ISOLATION: Coordinator always returns true so the + // WorkManager worker succeeds. Each sub-method handles its own failures + // independently — failed records stay UNSYNCED and retry on next sync cycle. + suspend fun pushUnSyncedRecords(): Boolean { + val screeningResult = pushUnSyncedRecordsMalariaScreening() + val confirmedResult = pushUnSyncedRecordsTBSuspected() + Timber.d("Malaria push results: screening=$screeningResult, confirmed=$confirmedResult") + // Worker succeeds — failed records stay UNSYNCED for next cycle + return true + } + + // RECORD-LEVEL ISOLATION: Malaria Screening records are now sent in + // chunks of 20 instead of one giant batch. Previously, if ANY record in + // the batch was malformed, the ENTIRE batch failed and ALL records stayed + // UNSYNCED. Now each chunk is independent — one bad chunk doesn't affect + // the others. Failed chunks' records stay UNSYNCED for the next sync cycle. + private suspend fun pushUnSyncedRecordsMalariaScreening(): Int { + + return withContext(Dispatchers.IO) { + val user = + preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") + + val malariasnList: List = malariaDao.getMalariaScreening(SyncState.UNSYNCED) + + if (malariasnList.isEmpty()) return@withContext 1 + + val CHUNK_SIZE = 20 + val chunks = malariasnList.chunked(CHUNK_SIZE) + var successCount = 0 + var failCount = 0 + + for (chunk in chunks) { + try { + val chunkDtos = chunk.map { it.toDTO() } + + val response = tmcNetworkApiService.saveMalariaScreeningData( + MalariaScreeningRequestDTO( + userId = user.userId, + malariaLists = chunkDtos + ) + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Push to Amrit Malaria Screening chunk: $responseStatusCode") + when (responseStatusCode) { + 200 -> { + chunk.forEach { + it.syncState = SyncState.SYNCED + malariaDao.saveMalariaScreening(it) + } + successCount += chunk.size + } + + 401, 5002 -> { + if (userRepo.refreshTokenTmc(user.userName, user.password)) { + Timber.d("Token refreshed, Malaria Screening chunk will retry next cycle") + } + failCount += chunk.size + } + + else -> { + Timber.e("Malaria Screening chunk failed with statusCode: $responseStatusCode") + failCount += chunk.size + } + } + } + } else { + Timber.e("Malaria Screening chunk HTTP error: $statusCode") + failCount += chunk.size + } + } catch (e: Exception) { + Timber.e(e, "Malaria Screening chunk push failed: ${chunk.size} records") + failCount += chunk.size + } + } + + Timber.d("Malaria Screening push complete: $successCount succeeded, $failCount failed out of ${malariasnList.size}") + return@withContext 1 + } + } + + // RECORD-LEVEL ISOLATION: Same chunking pattern as Malaria Screening. + // Records sent in chunks of 20 with per-chunk error isolation. + private suspend fun pushUnSyncedRecordsTBSuspected(): Int { + return withContext(Dispatchers.IO) { + val user = + preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") + + val tbspList: List = malariaDao.getMalariaConfirmed(SyncState.UNSYNCED) + + if (tbspList.isEmpty()) return@withContext 1 + + val CHUNK_SIZE = 20 + val chunks = tbspList.chunked(CHUNK_SIZE) + var successCount = 0 + var failCount = 0 + + for (chunk in chunks) { + try { + val chunkDtos = chunk.map { it.toDTO() } + + val response = tmcNetworkApiService.saveMalariaConfirmedData( + MalariaConfirmedRequestDTO( + userId = user.userId, + malariaFollowListUp = chunkDtos + ) + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Push to Amrit Malaria Confirmed chunk: $responseStatusCode") + when (responseStatusCode) { + 200 -> { + chunk.forEach { + it.syncState = SyncState.SYNCED + malariaDao.saveMalariaConfirmed(it) + } + successCount += chunk.size + } + + 401, 5002 -> { + if (userRepo.refreshTokenTmc(user.userName, user.password)) { + Timber.d("Token refreshed, Malaria Confirmed chunk will retry next cycle") + } + failCount += chunk.size + } + + else -> { + Timber.e("Malaria Confirmed chunk failed with statusCode: $responseStatusCode") + failCount += chunk.size + } + } + } + } else { + Timber.e("Malaria Confirmed chunk HTTP error: $statusCode") + failCount += chunk.size + } + } catch (e: Exception) { + Timber.e(e, "Malaria Confirmed chunk push failed: ${chunk.size} records") + failCount += chunk.size + } + } + + Timber.d("Malaria Confirmed push complete: $successCount succeeded, $failCount failed out of ${tbspList.size}") + return@withContext 1 + } + } + + + private suspend fun updateSyncStatusScreening(malariasnList: List) { + malariasnList.forEach { + it.syncState = SyncState.SYNCED + malariaDao.saveMalariaScreening(it) + } + } + + private suspend fun updateSyncStatusSuspected(tbspList: List) { + tbspList.forEach { + it.syncState = SyncState.SYNCED + malariaDao.saveMalariaConfirmed(it) + } + } + + companion object { + private val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) + private val timeFormat = SimpleDateFormat("HH:mm:ss", Locale.ENGLISH) + private fun getCurrentDate(millis: Long = System.currentTimeMillis()): String { + val dateString = dateFormat.format(millis) + val timeString = timeFormat.format(millis) + return "${dateString}T${timeString}.000Z" + } + + private fun getLongFromDate(dateString: String): Long { + val date = try { + SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH).parse(dateString) + } catch (_: Exception) { + SimpleDateFormat("MMM d, yyyy h:mm:ss a", Locale.ENGLISH).parse(dateString) + } + return date?.time ?: throw IllegalStateException("Invalid date for dateReg") + } + } + + + suspend fun canSubmit(householdId: Long): Boolean { + val (start, end) = HelperUtil.getYearRange() + val count = malariaDao.countRoundsInYear(householdId, start, end) + return count < 4 + } + + suspend fun getCount(householdId: Long) : Int { + val (start, end) = HelperUtil.getYearRange() + val count = malariaDao.countRoundsInYear(householdId, start, end) + return count + } + + suspend fun submitRound(round: IRSRoundScreening): Boolean { + return if (canSubmit(round.householdId)) { + malariaDao.saveIRSScreening(round) + true + } else { + false // already reached limit + } + } + + + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/MaternalHealthRepo.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/MaternalHealthRepo.kt index 34bfde8e1..56962e6c2 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/repositories/MaternalHealthRepo.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/MaternalHealthRepo.kt @@ -1,5 +1,6 @@ package org.piramalswasthya.sakhi.repositories +import android.app.Application import com.google.gson.Gson import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -27,13 +28,14 @@ import java.util.concurrent.TimeUnit import javax.inject.Inject class MaternalHealthRepo @Inject constructor( + private val context: Application, private val amritApiService: AmritApiService, private val maternalHealthDao: MaternalHealthDao, private val database: InAppDb, private val userRepo: UserRepo, private val benDao: BenDao, private val preferenceDao: PreferenceDao, -) { + ) { suspend fun getSavedRegistrationRecord(benId: Long): PregnantWomanRegistrationCache? { return withContext(Dispatchers.IO) { @@ -52,6 +54,11 @@ class MaternalHealthRepo @Inject constructor( maternalHealthDao.getSavedRecord(benId, visitNumber) } } + suspend fun getSavedRecordANC(benId: Long): PregnantWomanAncCache? { + return withContext(Dispatchers.IO) { + maternalHealthDao.getSavedRecordANC(benId) + } + } suspend fun getLatestAncRecord(benId: Long): PregnantWomanAncCache? { return withContext(Dispatchers.IO) { @@ -64,6 +71,13 @@ class MaternalHealthRepo @Inject constructor( maternalHealthDao.getAllActiveAncRecords(benId) } } + suspend fun getAllInActiveAncRecords(benId: Long): List { + return withContext(Dispatchers.IO) { + maternalHealthDao.getAllInActiveAncRecords(benId) + } + } + + suspend fun getBenFromId(benId: Long): BenRegCache? { return withContext(Dispatchers.IO) { @@ -99,7 +113,6 @@ class MaternalHealthRepo @Inject constructor( @OptIn(ExperimentalCoroutinesApi::class) val ancDueCount = maternalHealthDao.getAllPregnancyRecords().transformLatest { - Timber.d("From DB : ${it.count()}") var count = 0 val notDeliveredList = it.filter { !it.value.any { it.pregnantWomanDelivered == true } } notDeliveredList.keys.forEach { activePwrRecrod -> @@ -133,30 +146,44 @@ class MaternalHealthRepo @Inject constructor( val ancPostList = mutableSetOf() + // RECORD-LEVEL ISOLATION: Each ANC visit record is processed + // independently. If one record fails, remaining records continue. + // Failed records stay UNSYNCED and retry on next sync cycle. + var successCount = 0 + var failCount = 0 + ancList.forEach { - ancPostList.clear() - val ben = benDao.getBen(it.benId) - ?: throw IllegalStateException("No beneficiary exists for benId: ${it.benId}!!") - ancPostList.add(it.asPostModel()) - it.syncState = SyncState.SYNCING - maternalHealthDao.updateANC(it) - val uploadDone = postDataToAmritServer(ancPostList) - if (uploadDone) { - it.processed = "P" - it.syncState = SyncState.SYNCED - } else { + try { + ancPostList.clear() + val ben = benDao.getBen(it.benId) + ?: throw IllegalStateException("No beneficiary exists for benId: ${it.benId}!!") + ancPostList.add(it.asPostModel()) + it.syncState = SyncState.SYNCING + maternalHealthDao.updateANC(it) + val uploadDone = postDataToAmritServer(ancPostList) + if (uploadDone) { + it.processed = "P" + it.syncState = SyncState.SYNCED + successCount++ + } else { + it.syncState = SyncState.UNSYNCED + failCount++ + } + maternalHealthDao.updateANC(it) + } catch (e: Exception) { + Timber.e(e, "ANC visit push failed for benId: ${it.benId}") it.syncState = SyncState.UNSYNCED + maternalHealthDao.updateANC(it) + failCount++ } - maternalHealthDao.updateANC(it) - if (!uploadDone) - return@withContext false } + Timber.d("ANC visit push complete: $successCount succeeded, $failCount failed out of ${ancList.size}") return@withContext true } } - private suspend fun postDataToAmritServer(ancPostList: MutableSet): Boolean { + private suspend fun postDataToAmritServer(ancPostList: MutableSet, retryCount: Int = 3): Boolean { if (ancPostList.isEmpty()) return false val user = preferenceDao.getLoggedInUser() @@ -164,6 +191,10 @@ class MaternalHealthRepo @Inject constructor( try { + ancPostList.forEach{ + it.providerServiceMapID = user.serviceMapId.toString() + } + val response = amritApiService.postAncForm(ancPostList.toList()) val statusCode = response.code() @@ -183,7 +214,7 @@ class MaternalHealthRepo @Inject constructor( return true } - 5002 -> { + 401,5002 -> { if (userRepo.refreshTokenTmc( user.userName, user.password @@ -199,7 +230,9 @@ class MaternalHealthRepo @Inject constructor( } catch (e: IOException) { e.printStackTrace() } catch (e: SocketTimeoutException) { - postDataToAmritServer(ancPostList) + if (retryCount > 0) return postDataToAmritServer(ancPostList, retryCount - 1) + Timber.e("postDataToAmritServer: max retries exhausted") + return false } catch (e: Exception) { e.printStackTrace() } @@ -209,10 +242,12 @@ class MaternalHealthRepo @Inject constructor( Timber.w("Bad Response from server, need to check $ancPostList $response ") return false } catch (e: SocketTimeoutException) { - Timber.d("Caught exception $e here") - return postDataToAmritServer(ancPostList) + Timber.e("Caught exception $e here") + if (retryCount > 0) return postDataToAmritServer(ancPostList, retryCount - 1) + Timber.e("postDataToAmritServer: max retries exhausted") + return false } catch (e: JSONException) { - Timber.d("Caught exception $e here") + Timber.e("Caught exception $e here") return false } } @@ -226,30 +261,44 @@ class MaternalHealthRepo @Inject constructor( val pwrPostList = mutableSetOf() + // RECORD-LEVEL ISOLATION: Each PWR record is processed + // independently. If one record fails, remaining records continue. + // Failed records stay UNSYNCED and retry on next sync cycle. + var successCount = 0 + var failCount = 0 + pwrList.forEach { - pwrPostList.clear() - val ben = benDao.getBen(it.benId) - ?: throw IllegalStateException("No beneficiary exists for benId: ${it.benId}!!") - pwrPostList.add(it.asPwrPost()) - it.syncState = SyncState.SYNCING - maternalHealthDao.updatePwr(it) - val uploadDone = postPwrToAmritServer(pwrPostList) - if (uploadDone) { - it.processed = "P" - it.syncState = SyncState.SYNCED - } else { + try { + pwrPostList.clear() + val ben = benDao.getBen(it.benId) + ?: throw IllegalStateException("No beneficiary exists for benId: ${it.benId}!!") + pwrPostList.add(it.asPwrPost()) + it.syncState = SyncState.SYNCING + maternalHealthDao.updatePwr(it) + val uploadDone = postPwrToAmritServer(pwrPostList) + if (uploadDone) { + it.processed = "P" + it.syncState = SyncState.SYNCED + successCount++ + } else { + it.syncState = SyncState.UNSYNCED + failCount++ + } + maternalHealthDao.updatePwr(it) + } catch (e: Exception) { + Timber.e(e, "PWR push failed for benId: ${it.benId}") it.syncState = SyncState.UNSYNCED + maternalHealthDao.updatePwr(it) + failCount++ } - maternalHealthDao.updatePwr(it) - if (!uploadDone) - return@withContext false } + Timber.d("PWR push complete: $successCount succeeded, $failCount failed out of ${pwrList.size}") return@withContext true } } - suspend fun postPwrToAmritServer(pwrPostList: MutableSet): Boolean { + suspend fun postPwrToAmritServer(pwrPostList: MutableSet, retryCount: Int = 3): Boolean { if (pwrPostList.isEmpty()) return false val user = preferenceDao.getLoggedInUser() @@ -275,7 +324,7 @@ class MaternalHealthRepo @Inject constructor( return true } - 5002 -> { + 401,5002 -> { if (userRepo.refreshTokenTmc( user.userName, user.password @@ -299,10 +348,12 @@ class MaternalHealthRepo @Inject constructor( Timber.w("Bad Response from server, need to check $pwrPostList $response ") return false } catch (e: SocketTimeoutException) { - Timber.d("Caught exception $e here") - return postPwrToAmritServer(pwrPostList) + Timber.e("Caught exception $e here") + if (retryCount > 0) return postPwrToAmritServer(pwrPostList, retryCount - 1) + Timber.e("postPwrToAmritServer: max retries exhausted") + return false } catch (e: JSONException) { - Timber.d("Caught exception $e here") + Timber.e("Caught exception $e here") return false } } @@ -344,7 +395,7 @@ class MaternalHealthRepo @Inject constructor( return@withContext 1 } - 5002 -> { + 401,5002 -> { if (userRepo.refreshTokenTmc( user.userName, user.password ) @@ -364,11 +415,11 @@ class MaternalHealthRepo @Inject constructor( } } catch (e: SocketTimeoutException) { - Timber.d("get_tb error : $e") + Timber.e("get_tb error : $e") return@withContext -2 } catch (e: java.lang.IllegalStateException) { - Timber.d("get_tb error : $e") + Timber.e("get_tb error : $e") return@withContext -1 } -1 @@ -469,7 +520,7 @@ class MaternalHealthRepo @Inject constructor( return@withContext 1 } - 5002 -> { + 401,5002 -> { if (userRepo.refreshTokenTmc( user.userName, user.password ) @@ -489,11 +540,11 @@ class MaternalHealthRepo @Inject constructor( } } catch (e: SocketTimeoutException) { - Timber.d("get_tb error : $e") + Timber.e("get_tb error : $e") return@withContext -2 } catch (e: java.lang.IllegalStateException) { - Timber.d("get_tb error : $e") + Timber.e("get_tb error : $e") return@withContext -1 } -1 @@ -505,17 +556,28 @@ class MaternalHealthRepo @Inject constructor( Gson().fromJson(dataObj, Array::class.java).toList() ancList.forEach { ancDTO -> ancDTO.createdDate?.let { - val hasBen = benDao.getBen(ancDTO.benId) != null + val ben = benDao.getBen(ancDTO.benId) + val hasBen = ben != null val ancCache: PregnantWomanAncCache? = maternalHealthDao.getSavedRecord(ancDTO.benId, ancDTO.ancVisit) if (hasBen && ancCache == null) { - maternalHealthDao.saveRecord(ancDTO.toAncCache()) + maternalHealthDao.saveRecord(ancDTO.toAncCache(context)) + } + if (hasBen && ancDTO.isBabyDelivered == true) { + ben?.let { benRecord -> + if (benRecord.genDetails?.reproductiveStatusId != 3) { + benRecord.genDetails?.reproductiveStatus = "Postnatal Mother" + benRecord.genDetails?.reproductiveStatusId = 3 + benDao.updateBen(benRecord) + } + } } } } return ancList } + suspend fun setToInactive(eligBenIds: Set) { withContext(Dispatchers.IO) { val records = maternalHealthDao.getAllActiveAncRecords(eligBenIds) diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/MdsrRepo.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/MdsrRepo.kt index 2bb3bbc05..6183eb9b7 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/repositories/MdsrRepo.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/MdsrRepo.kt @@ -31,7 +31,7 @@ class MdsrRepo @Inject constructor( mdsrDao.upsert(mdsrCache) true } catch (e: Exception) { - Timber.d("Error : $e raised at saveMdsrData") + Timber.e("Error : $e raised at saveMdsrData") false } } @@ -43,26 +43,44 @@ class MdsrRepo @Inject constructor( val mdsrPostList = mutableSetOf() + // RECORD-LEVEL ISOLATION: Each MDSR record is processed + // independently with try/catch. Previously this method silently + // returned true even when records failed to upload (Pattern C). + // Now failures are logged and tracked while still allowing the + // worker to succeed (failed records retry on next sync cycle). + var successCount = 0 + var failCount = 0 + mdsrList.forEach { - mdsrPostList.clear() - mdsrPostList.add(it.asPostModel()) - it.syncState = SyncState.SYNCING - mdsrDao.updateMdsrRecord(it) - val uploadDone = postMdsrForm(mdsrPostList.toList()) - if (uploadDone) { - it.processed = "P" - it.syncState = SyncState.SYNCED - } else { + try { + mdsrPostList.clear() + mdsrPostList.add(it.asPostModel()) + it.syncState = SyncState.SYNCING + mdsrDao.updateMdsrRecord(it) + val uploadDone = postMdsrForm(mdsrPostList.toList()) + if (uploadDone) { + it.processed = "P" + it.syncState = SyncState.SYNCED + successCount++ + } else { + it.syncState = SyncState.UNSYNCED + failCount++ + } + mdsrDao.updateMdsrRecord(it) + } catch (e: Exception) { + Timber.e(e, "MDSR push failed for record") it.syncState = SyncState.UNSYNCED + mdsrDao.updateMdsrRecord(it) + failCount++ } - mdsrDao.updateMdsrRecord(it) } + Timber.d("MDSR push complete: $successCount succeeded, $failCount failed out of ${mdsrList.size}") return@withContext true } } - private suspend fun postMdsrForm(mdsrPostList: List): Boolean { + private suspend fun postMdsrForm(mdsrPostList: List, retryCount: Int = 3): Boolean { if (mdsrPostList.isEmpty()) return false val user = preferenceDao.getLoggedInUser() @@ -79,7 +97,7 @@ class MdsrRepo @Inject constructor( val errormessage = jsonObj.getString("errorMessage") if (jsonObj.isNull("statusCode")) throw IllegalStateException("Amrit server not responding properly, Contact Service Administrator!!") - val responsestatuscode = jsonObj.getInt("responseStatusCode") + val responsestatuscode = jsonObj.getInt("statusCode") when (responsestatuscode) { 200 -> { @@ -87,7 +105,7 @@ class MdsrRepo @Inject constructor( return true } - 5002 -> { + 401,5002 -> { if (userRepo.refreshTokenTmc( user.userName, user.password @@ -111,10 +129,12 @@ class MdsrRepo @Inject constructor( Timber.w("Bad Response from server, need to check $mdsrPostList $response ") return false } catch (e: SocketTimeoutException) { - Timber.d("Caught exception $e here") - return postMdsrForm(mdsrPostList) + Timber.e("Caught exception $e here") + if (retryCount > 0) return postMdsrForm(mdsrPostList, retryCount - 1) + Timber.e("postMdsrForm: max retries exhausted") + return false } catch (e: JSONException) { - Timber.d("Caught exception $e here") + Timber.e("Caught exception $e here") return false } @@ -157,7 +177,7 @@ class MdsrRepo @Inject constructor( return@withContext 1 } - 5002 -> { + 401,5002 -> { if (userRepo.refreshTokenTmc( user.userName, user.password ) @@ -177,11 +197,11 @@ class MdsrRepo @Inject constructor( } } catch (e: SocketTimeoutException) { - Timber.d("get_pnc error : $e") + Timber.e("get_pnc error : $e") return@withContext -2 } catch (e: java.lang.IllegalStateException) { - Timber.d("get_pnc error : $e") + Timber.e("get_pnc error : $e") return@withContext -1 } -1 diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/NcdReferalRepo.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/NcdReferalRepo.kt new file mode 100644 index 000000000..445d11b42 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/NcdReferalRepo.kt @@ -0,0 +1,108 @@ +package org.piramalswasthya.sakhi.repositories +import com.google.gson.Gson +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.json.JSONObject +import org.piramalswasthya.sakhi.database.room.InAppDb +import org.piramalswasthya.sakhi.database.room.NcdReferalDao +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao +import org.piramalswasthya.sakhi.model.ReferalCache +import org.piramalswasthya.sakhi.model.ReferralRequest +import org.piramalswasthya.sakhi.network.AmritApiService +import org.piramalswasthya.sakhi.network.GetCBACRequest +import org.piramalswasthya.sakhi.network.NCDReferalDTO +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale +import javax.inject.Inject + +class NcdReferalRepo@Inject constructor( + private val referalDao: NcdReferalDao, + private val preferenceDao: PreferenceDao, + private val userRepo: UserRepo, + private val tmcNetworkApiService: AmritApiService, + private val database: InAppDb +) { + suspend fun getReferedNCD(benId: Long): ReferalCache? { + return withContext(Dispatchers.IO) { + referalDao.getReferalFromBenId(benId) + } + } + + fun Long.toApiDateFormat(): String { + val sdf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH) + return sdf.format(Date(this)) + } + suspend fun pushAndUpdateNCDReferRecord() { + val unProcessedList = referalDao.getAllUnprocessedReferals() + if (unProcessedList.isEmpty()) return + + for (ncdRefer in unProcessedList) { + val dto = ncdRefer.toDTO() + + val request = ReferralRequest( + refer = dto, + ) + + + val response = tmcNetworkApiService.postRefer(request) + + response?.body()?.string()?.let { body -> + val jsonBody = JSONObject(body) + val isSuccess = jsonBody.getString("status") == "Success" + + if (isSuccess) { + updateSyncStatusRefer(ncdRefer) + } + } + } + } + + suspend fun pullAndPersistReferRecord(page: Int = 0): Int { + val userName = preferenceDao.getLoggedInUser()?.userName!! + val cbacRequest = GetCBACRequest(userName) + + val response = tmcNetworkApiService.getCbacReferData( + cbacRequest + ) + val body = response.body()?.string()?.let { JSONObject(it) } + body?.getInt("statusCode")?.takeIf { it == 5002 }?.let { + val user = preferenceDao.getLoggedInUser()!! + userRepo.refreshTokenTmc(user.userName, user.password) + pullAndPersistReferRecord(page) + } + val dataArray = body?.optJSONArray("data") + + if (dataArray != null && dataArray.length() > 0) { + val gson = Gson() + val cbacEntities = mutableListOf() + + for (i in 0 until dataArray.length()) { + val item = dataArray.getJSONObject(i) + val dto = gson.fromJson(item.toString(), NCDReferalDTO::class.java) + cbacEntities.add(dto.toCache()) + } + val existingBenIds = database.benDao.getExistingBenIds(cbacEntities.map { it.benId }) + val validEntities = cbacEntities.filter { it.benId in existingBenIds } + if (validEntities.isNotEmpty()) { + referalDao.insertAll(validEntities) + } + + + } + return 0 + } + + + + private suspend fun updateSyncStatusRefer(refer: ReferalCache) { + refer.syncState = SyncState.SYNCED + referalDao.upsert(refer) + } + suspend fun saveReferedNCD(referCache: ReferalCache) { + withContext(Dispatchers.IO) { + referalDao.upsert(referCache) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/PmjayRepo.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/PmjayRepo.kt index dbd977ef3..d8e4c43ea 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/repositories/PmjayRepo.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/PmjayRepo.kt @@ -30,7 +30,7 @@ class PmjayRepo @Inject constructor( true } catch (e: Exception) { - Timber.d("Error : $e raised at saveCdrData") + Timber.e("Error : $e raised at saveCdrData") false } } diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/PmsmaRepo.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/PmsmaRepo.kt index afa45ea73..248c5abc8 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/repositories/PmsmaRepo.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/PmsmaRepo.kt @@ -44,7 +44,7 @@ class PmsmaRepo @Inject constructor( true } catch (e: Exception) { - Timber.d("Error : $e raised at savePmsmaData") + Timber.e("Error : $e raised at savePmsmaData") false } } @@ -60,26 +60,40 @@ class PmsmaRepo @Inject constructor( val pmsmaPostList = mutableSetOf() + // RECORD-LEVEL ISOLATION: Each PMSMA record is processed + // independently. If one record fails, remaining records continue. + // Failed records stay UNSYNCED and retry on next sync cycle. + var successCount = 0 + var failCount = 0 + pmsmaList.forEach { - pmsmaPostList.clear() - pmsmaPostList.add(it.asPostModel()) - val uploadDone = postDataToAmritServer(pmsmaPostList) - if (uploadDone) { - it.processed = "P" - it.syncState = SyncState.SYNCED - } else { + try { + pmsmaPostList.clear() + pmsmaPostList.add(it.asPostModel()) + val uploadDone = postDataToAmritServer(pmsmaPostList) + if (uploadDone) { + it.processed = "P" + it.syncState = SyncState.SYNCED + successCount++ + } else { + it.syncState = SyncState.UNSYNCED + failCount++ + } + pmsmaDao.updatePmsmaRecord(it) + } catch (e: Exception) { + Timber.e(e, "PMSMA push failed for record") it.syncState = SyncState.UNSYNCED + pmsmaDao.updatePmsmaRecord(it) + failCount++ } - pmsmaDao.updatePmsmaRecord(it) - if (!uploadDone) - return@withContext false } + Timber.d("PMSMA push complete: $successCount succeeded, $failCount failed out of ${pmsmaList.size}") return@withContext true } } - private suspend fun postDataToAmritServer(pmsmaPostList: MutableSet): Boolean { + private suspend fun postDataToAmritServer(pmsmaPostList: MutableSet, retryCount: Int = 3): Boolean { if (pmsmaPostList.isEmpty()) return false @@ -103,7 +117,7 @@ class PmsmaRepo @Inject constructor( return true } - 5002 -> { + 401,5002 -> { val user = preferenceDao.getLoggedInUser() ?: throw IllegalStateException("User seems to be logged out!!") if (userRepo.refreshTokenTmc(user.userName, user.password)) @@ -126,10 +140,12 @@ class PmsmaRepo @Inject constructor( Timber.w("Bad Response from server, need to check $pmsmaPostList $response ") return false } catch (e: SocketTimeoutException) { - Timber.d("Caught exception $e here") - return postDataToAmritServer(pmsmaPostList) + Timber.e("Caught exception $e here") + if (retryCount > 0) return postDataToAmritServer(pmsmaPostList, retryCount - 1) + Timber.e("postDataToAmritServer: max retries exhausted") + return false } catch (e: Exception) { - Timber.d("Caught exception $e here") + Timber.e("Caught exception $e here") return false } } @@ -171,7 +187,7 @@ class PmsmaRepo @Inject constructor( return@withContext 1 } - 5002 -> { + 401,5002 -> { if (userRepo.refreshTokenTmc( user.userName, user.password ) @@ -191,11 +207,11 @@ class PmsmaRepo @Inject constructor( } } catch (e: SocketTimeoutException) { - Timber.d("get_pmsma error : $e") + Timber.e("get_pmsma error : $e") return@withContext -2 } catch (e: java.lang.IllegalStateException) { - Timber.d("get_pmsma error : $e") + Timber.e("get_pmsma error : $e") return@withContext -1 } -1 @@ -222,6 +238,26 @@ class PmsmaRepo @Inject constructor( pmsmaDao.getPmsma(benId) } + } + suspend fun getSavedRecord(benId: Long, visitNumber: Int): PMSMACache? { + return withContext(Dispatchers.IO) { + pmsmaDao.getSavedRecord(benId,visitNumber) + } + + } + suspend fun getActiveAncCountForBenIds(benId: Long): Int { + return withContext(Dispatchers.IO) { + pmsmaDao.getActiveAncCountForBenIds(benId) + } + + } + + + suspend fun getLastPmsmaVisit(benId: Long): PMSMACache? { + return withContext(Dispatchers.IO) { + pmsmaDao.getLastPmsmaVisit(benId) + } + } suspend fun setToInactive(eligBenIds: Set) { diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/PncRepo.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/PncRepo.kt index e2ec01a61..497b168e0 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/repositories/PncRepo.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/PncRepo.kt @@ -59,28 +59,42 @@ class PncRepo @Inject constructor( val pncPostList = mutableSetOf() + // RECORD-LEVEL ISOLATION: Each PNC visit record is processed + // independently. If one record fails, remaining records continue. + // Failed records stay UNSYNCED and retry on next sync cycle. + var successCount = 0 + var failCount = 0 + pncList.forEach { - pncPostList.clear() - pncPostList.add(it.asNetworkModel()) - it.syncState = SyncState.SYNCING - pncDao.update(it) - val uploadDone = postDataToAmritServer(pncPostList) - if (uploadDone) { - it.processed = "P" - it.syncState = SyncState.SYNCED - } else { + try { + pncPostList.clear() + pncPostList.add(it.asNetworkModel()) + it.syncState = SyncState.SYNCING + pncDao.update(it) + val uploadDone = postDataToAmritServer(pncPostList) + if (uploadDone) { + it.processed = "P" + it.syncState = SyncState.SYNCED + successCount++ + } else { + it.syncState = SyncState.UNSYNCED + failCount++ + } + pncDao.update(it) + } catch (e: Exception) { + Timber.e(e, "PNC visit push failed for record") it.syncState = SyncState.UNSYNCED + pncDao.update(it) + failCount++ } - pncDao.update(it) - if (!uploadDone) - return@withContext false } + Timber.d("PNC visit push complete: $successCount succeeded, $failCount failed out of ${pncList.size}") return@withContext true } } - private suspend fun postDataToAmritServer(ancPostList: MutableSet): Boolean { + private suspend fun postDataToAmritServer(ancPostList: MutableSet, retryCount: Int = 3): Boolean { if (ancPostList.isEmpty()) return false val user = preferenceDao.getLoggedInUser() @@ -107,7 +121,7 @@ class PncRepo @Inject constructor( return true } - 5002 -> { + 401, 5002 -> { if (userRepo.refreshTokenTmc( user.userName, user.password @@ -131,10 +145,12 @@ class PncRepo @Inject constructor( Timber.w("Bad Response from server, need to check $ancPostList $response ") return false } catch (e: SocketTimeoutException) { - Timber.d("Caught exception $e here") - return postDataToAmritServer(ancPostList) + Timber.e("Caught exception $e here") + if (retryCount > 0) return postDataToAmritServer(ancPostList, retryCount - 1) + Timber.e("postDataToAmritServer: max retries exhausted") + return false } catch (e: JSONException) { - Timber.d("Caught exception $e here") + Timber.e("Caught exception $e here") return false } } @@ -178,7 +194,7 @@ class PncRepo @Inject constructor( return@withContext 1 } - 5002 -> { + 401,5002 -> { if (userRepo.refreshTokenTmc( user.userName, user.password ) @@ -198,11 +214,11 @@ class PncRepo @Inject constructor( } } catch (e: SocketTimeoutException) { - Timber.d("get_pnc error : $e") + Timber.e("get_pnc error : $e") return@withContext -2 } catch (e: java.lang.IllegalStateException) { - Timber.d("get_pnc error : $e") + Timber.e("get_pnc error : $e") return@withContext -1 } -1 @@ -236,4 +252,9 @@ class PncRepo @Inject constructor( } } + suspend fun getAllPncVisitsForBeneficiary(benId: Long): List { + + return pncDao.getPncVisitsByBenId(benId) + } + } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/RecordsRepo.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/RecordsRepo.kt index f2767c315..61262288b 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/repositories/RecordsRepo.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/RecordsRepo.kt @@ -1,37 +1,93 @@ package org.piramalswasthya.sakhi.repositories +import androidx.paging.PagingSource +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.scopes.ActivityRetainedScoped import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.transformLatest +import org.piramalswasthya.sakhi.model.BenBasicCache import org.piramalswasthya.sakhi.database.room.dao.BenDao import org.piramalswasthya.sakhi.database.room.dao.ChildRegistrationDao import org.piramalswasthya.sakhi.database.room.dao.HouseholdDao import org.piramalswasthya.sakhi.database.room.dao.ImmunizationDao import org.piramalswasthya.sakhi.database.room.dao.MaternalHealthDao +import org.piramalswasthya.sakhi.database.room.dao.dynamicSchemaDao.FormResponseANCJsonDao import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao +import org.piramalswasthya.sakhi.helpers.Konstants +import org.piramalswasthya.sakhi.helpers.getTodayMillis +import org.piramalswasthya.sakhi.model.BenBasicDomain +import org.piramalswasthya.sakhi.model.BenBasicDomainForForm +import org.piramalswasthya.sakhi.model.BenWithAncListDomain +import org.piramalswasthya.sakhi.model.HomeVisitUiState +import org.piramalswasthya.sakhi.model.dynamicEntity.anc.ANCFormResponseJsonEntity import org.piramalswasthya.sakhi.model.filterMdsr +import org.piramalswasthya.sakhi.utils.HelperUtil +import org.piramalswasthya.sakhi.utils.HomeVisitHelper import java.util.Calendar +import java.util.concurrent.TimeUnit import javax.inject.Inject @ActivityRetainedScoped class RecordsRepo @Inject constructor( + @ApplicationContext private val context: Context, private val householdDao: HouseholdDao, private val benDao: BenDao, + private val ancHomeVisitDao : FormResponseANCJsonDao, private val vaccineDao: ImmunizationDao, private val maternalHealthDao: MaternalHealthDao, private val childRegistrationDao: ChildRegistrationDao, preferenceDao: PreferenceDao ) { private val selectedVillage = preferenceDao.getLocationRecord()!!.village.id + private val localizedResources = HelperUtil.getLocalizedResources(context, preferenceDao.getCurrentLanguage()) val hhList = householdDao.getAllHouseholdWithNumMembers(selectedVillage) .map { list -> list.map { it.asBasicDomainModel() } } val hhListCount = householdDao.getAllHouseholdsCount(selectedVillage) + val hhListforAsha = householdDao.getAllHouseholdForAshaFamilyMembers(selectedVillage) + .map { list -> list.map { it.asBasicDomainModel() } } + val allBenList = benDao.getAllBen(selectedVillage).map { list -> list.map { it.asBasicDomainModel() } } + + val childCountsByBen: Flow> = + benDao.getChildCountsForAllBen(selectedVillage) + .map { list -> list.associate { it.benId to it.childCount } } + + fun searchBen(query: String, filterType: Int, source: Int): Flow> = + benDao.searchBen(selectedVillage, source, filterType, query) + .map { list -> list.map { it.asBasicDomainModel() } } + + fun searchBenPagedSource(query: String, filterType: Int, source: Int): PagingSource = + benDao.searchBenPaged(selectedVillage, source, filterType, query) + + suspend fun searchBenOnce(query: String, filterType: Int, source: Int): List = + benDao.searchBenOnce(selectedVillage, source, filterType, query) + .map { it.asBasicDomainModel() } + val allBenListCount = benDao.getAllBenCount(selectedVillage) + val allBenWithoutAbhaList = + benDao.getAllBenWithoutAbha(selectedVillage).map { list -> list.map { it.asBasicDomainModel() } } + val allBenWithAbhaList = + benDao.getAllBenWithAbha(selectedVillage).map { list -> list.map { it.asBasicDomainModel() } } + + val benWithAbhaListCount = benDao.getAllBenWithAbhaCount(selectedVillage) + val benWithOldAbhaListCount = benDao.getAllBenWithOldAbhaCount(selectedVillage) + val benWithNewAbhaListCount = benDao.getAllBenWithNewAbhaCount(selectedVillage) + + val allBenWithRchList = + benDao.getAllBenWithRch(selectedVillage).map { list -> list.map { it.asBasicDomainModel() } } + + val allBenAboveThirtyList = benDao.getAllBenAboveThirty(selectedVillage).map { list -> list.map { it.asBasicDomainModel() } } + + val allBenWARAList = benDao.getAllBenWARA(selectedVillage).map { list -> list.map { it.asBasicDomainModel() } } + + val benWithRchListCount = benDao.getAllBenWithRchCount(selectedVillage) fun getBenList() = benDao.getAllBen(selectedVillage).map { list -> list.map { it.asBasicDomainModel() } } @@ -40,15 +96,19 @@ class RecordsRepo @Inject constructor( fun getBenListCount() = benDao.getAllBenGenderCount(selectedVillage, "FEMALE") -// val pregnantList = benDao.getAllPregnancyWomenList(selectedVillage) -// .map { list -> list.map { it.asBenBasicDomainModelForPmsmaForm() } } -// val pregnantListCount = pregnantList.map { it.size } - val ncdList = allBenList val ncdListCount = allBenListCount val getNcdEligibleList = benDao.getBenWithCbac(selectedVillage) + val getNcdrefferedList = benDao.getBenWithReferredCbac(selectedVillage) + val getHwcRefferedList = benDao.getReferredHWCBenList(selectedVillage) + + + val getNcdEligibleListCount = benDao.getBenWithCbacCount(selectedVillage) + val getNcdrefferedListCount = benDao.getReferredBenCount(selectedVillage) + val getHwcReferedListCount = benDao.getReferredHWCBenCount(selectedVillage) + val getNcdPriorityList = getNcdEligibleList.map { it.filter { it.savedCbacRecords.isNotEmpty() && it.savedCbacRecords.maxBy { it.createdDate }.total_score > 4 } } @@ -60,22 +120,57 @@ class RecordsRepo @Inject constructor( val getNcdNonEligibleListCount = getNcdNonEligibleList.map { it.count() } -// val ncdPriorityList = benDao.getAllNCDPriorityList(selectedVillage) -// .map { list -> list.map { it.asBenBasicDomainModelForCbacForm() } } -// val ncdPriorityListCount = ncdPriorityList.map { it.size } -// val ncdNonEligibleList = benDao.getAllNCDNonEligibleList(selectedVillage) -// .map { list -> list.map { it.asBenBasicDomainModelForCbacForm() } } -// val ncdNonEligibleListCount = ncdNonEligibleList.map { it.size } + fun malariaScreeningList(hhId:Long) = benDao.getAllMalariaScreeningBen(selectedVillage, hhId = hhId) + .map { list -> list.map { it.asMalariaScreeningDomainModel() } } + + fun aesScreeningList(hhId:Long) = benDao.getAllAESScreeningBen(selectedVillage, hhId = hhId) + .map { list -> list.map { it.asAESScreeningDomainModel() } } + + fun iRSRoundList(hhId:Long) = benDao.getAllIRSRoundBen(hhId = hhId) + fun getLastIRSRoundBen(hhId:Long) = benDao.getLastIRSRoundBen(hhId = hhId) + + + fun KalazarScreeningList(hhId:Long) = benDao.getAllKALAZARScreeningBen(selectedVillage, hhId = hhId) + .map { list -> list.map { it.asKALAZARScreeningDomainModel() } } + + fun LeprosyScreeningList(hhId:Long) = benDao.getAllLeprosyScreeningBen(selectedVillage, hhId = hhId) + .map { list -> list.map { it.asLeprosyScreeningDomainModel() } } + + fun LeprosySuspectedList() = benDao.getLeprosyScreeningBenBySymptoms(selectedVillage,0) + .map { list -> list.map { it.asLeprosyScreeningDomainModel() } } + + fun LeprosyConfirmedList() = benDao.getConfirmedLeprosyCases(selectedVillage =selectedVillage) + .map {list -> list.map { it.asLeprosyScreeningDomainModel()}} + + fun filariaScreeningList(hhId:Long) = benDao.getAllFilariaScreeningBen(selectedVillage, hhId = hhId) + .map { list -> list.map { it.asFilariaScreeningDomainModel() } } + val tbScreeningList = benDao.getAllTbScreeningBen(selectedVillage) .map { list -> list.map { it.asTbScreeningDomainModel() } } val tbScreeningListCount = tbScreeningList.map { it.size } + val tbSuspectedList = benDao.getTbScreeningList(selectedVillage) .map { list -> list.map { it.asTbSuspectedDomainModel() } } val tbSuspectedListCount = tbSuspectedList.map { it.size } + val tbConfirmedList = benDao.getTbConfirmedList(selectedVillage) + .map { list -> list.map { it.asTbSuspectedDomainModel() } } + val tbConfirmedListCount = tbConfirmedList.map { it.size } + + + + + val malariaConfirmedCasesList = benDao.getMalariaConfirmedCasesList(selectedVillage) + .map { list -> list.map { it.asMalariaConfirmedDomainModel() } } + val leprosySuspectedListCount = benDao.getLeprosyScreeningBenCountBySymptoms(selectedVillage,0) + val leprosyConfirmedCasesListCount = benDao.getConfirmedLeprosyCaseCount(selectedVillage =selectedVillage) + + + val malariaConfirmedCasesListCount = malariaConfirmedCasesList.map { it.size } + val menopauseList = benDao.getAllMenopauseStageList(selectedVillage) .map { list -> list.map { it.asBasicDomainModel() } } val menopauseListCount = menopauseList.map { it.size } @@ -84,23 +179,29 @@ class RecordsRepo @Inject constructor( .map { list -> list.map { it.asBasicDomainModelForFpotForm() } } val reproductiveAgeListCount = reproductiveAgeList.map { it.size } - // val infantList = benDao.getAllInfantList(selectedVillage) -// .map { list -> list.map { it.asBenBasicDomainModelForHbncForm() } } -// val infantListCount = infantList.map { it.size } val infantList = benDao.getAllInfantList(selectedVillage) .map { list -> list.map { it.asBasicDomainModel() } } val infantListCount = infantList.map { it.size } - // val childList = benDao.getAllChildList(selectedVillage) -// .map { list -> list.map { it.asBenBasicDomainModelForHbycForm() } } -// val childListCount = childList.map { it.size } val childList = benDao.getAllChildList(selectedVillage) .map { list -> list.map { it.asBasicDomainModel() } } val childListCount = childList.map { it.size } + + val childCard = benDao.getAllInfantList(selectedVillage) + .map { list -> list.map { it.asBasicDomainModel() } } + + val childFilteredList = benDao.getAllChildList(selectedVillage, 0, 5 * 365) + .map { list -> + list + .filter { !it.isDeath } + .map { it.asBasicDomainModel() } + } + val childFilteredListCount = childFilteredList.map { it.size } + val adolescentList = benDao.getAllAdolescentList(selectedVillage) - .map { list -> list.map { it.asBasicDomainModel() } } + .map { list -> list.map { it.asAdolescentDomainModel() } } val adolescentListCount = adolescentList.map { it.size } val immunizationList = benDao.getAllImmunizationDueList(selectedVillage) @@ -116,24 +217,55 @@ class RecordsRepo @Inject constructor( .map { list -> list.map { it.asBasicDomainModelForPNC() } } val pncMotherListCount = pncMotherList.map { it.size } + val pncMotherNonFollowUpList = benDao.getAllPNCMotherList(selectedVillage) + .map { list -> + list.filter { + if (!it.savedPncRecords.isNullOrEmpty()) { + it.savedPncRecords.last().pncDate != 0L && + it.savedPncRecords.last().pncDate < System.currentTimeMillis() - TimeUnit.DAYS.toMillis( + 90 + ) && + it.savedPncRecords.last().pncDate > System.currentTimeMillis() - TimeUnit.DAYS.toMillis( + 365 + ) +// it.savedPncRecords.any { it1 -> +// it1.pncDate != 0L && +// it1.pncDate < System.currentTimeMillis() - TimeUnit.DAYS.toMillis(90) && +// it1.pncDate > System.currentTimeMillis() - TimeUnit.DAYS.toMillis(365) +// } + } else { + false + } + } + .map { it.asBasicDomainModelForPNC() } } + + val pncMotherNonFollowUpListCount = pncMotherNonFollowUpList.map { it.size } + val cdrList = benDao.getAllCDRList(selectedVillage) .map { list -> list.map { it.asBenBasicDomainModelForCdrForm() } } // val cdrListCount = cdrList.map { it.size } + val gdrList = benDao.getAllGeneralDeathsList(selectedVillage) + .map { list ->list.map{ it.asBenBasicDomainModelForCdrForm()} } + + fun getGeneralDeathCount() = benDao.getAllGeneralDeathsCount(selectedVillage) + + + val nmdrList = benDao.getAllNonMaternalDeathsList(selectedVillage) + .map {list -> list.map{it.asBenBasicDomainModelForCdrForm()}} + val mdsrList = benDao.getAllMDSRList(selectedVillage) .map { list -> list.filterMdsr() } val childrenImmunizationDueListCount = vaccineDao.getChildrenImmunizationDueListCount() - // val childrenImmunizationList = benDao.getAllChildrenImmunizationList(selectedVillage) -// .map { list -> list.map { it.asBasicDomainModel() } } val childrenImmunizationList = vaccineDao.getBenWithImmunizationRecords( minDob = Calendar.getInstance().apply { set(Calendar.HOUR_OF_DAY, 0) set(Calendar.MINUTE, 0) set(Calendar.SECOND, 0) set(Calendar.MILLISECOND, 0) - add(Calendar.YEAR, -16) + add(Calendar.YEAR, -6) }.timeInMillis, maxDob = System.currentTimeMillis() ) val childrenImmunizationListCount = childrenImmunizationList.map { it.size } @@ -146,32 +278,75 @@ class RecordsRepo @Inject constructor( .map { list -> list.map { it.asDomainModel() } } val eligibleCoupleListCount = eligibleCoupleList.map { it.size } + val eligibleCoupleMissedPeriodList = benDao.getAllEligibleRegistrationList(selectedVillage) + .map { list -> + list.filter { + it.ecr != null && it.ecr.lmpDate != 0L && + System.currentTimeMillis() - it.ecr.lmpDate > TimeUnit.DAYS.toMillis(35) +// if (it.ecr != null && it.ecr.lmpDate != 0L) { +// System.currentTimeMillis() - it.ecr.lmpDate > TimeUnit.DAYS.toMillis(35) +// } else { +// true +// } + } + .map { it.asDomainModel() } } + val eligibleCoupleMissedPeriodListCount = eligibleCoupleMissedPeriodList.map { it.size } + val eligibleCoupleTrackingList = benDao.getAllEligibleTrackingList(selectedVillage) - .map { list -> list.map { it.asDomainModel() } } + .combine(childCountsByBen) { list, counts -> + list.map { it.asDomainModel(counts[it.ben.benId], localizedResources) } + } // .map { list -> list.map { it.asBenBasicDomainModelECTForm() } } val eligibleCoupleTrackingListCount = eligibleCoupleTrackingList.map { it.size } -// val deliveredWomenList = benDao.getAllEligibleTrackingList(selectedVillage) -// .map { list -> list.map { it.asBenBasicDomainModelECTForm() } } -// val deliveredWomenListCount = deliveredWomenList.map { it.size } + val eligibleCoupleTrackingNonFollowUpList = benDao.getAllEligibleTrackingList(selectedVillage) + .combine(childCountsByBen) { list, counts -> + list.filter { + if (!it.savedECTRecords.isNullOrEmpty()) { + it.savedECTRecords.last().visitDate != 0L && + it.savedECTRecords.last().visitDate < System.currentTimeMillis() - TimeUnit.DAYS.toMillis( + 90 + ) && + it.savedECTRecords.last().visitDate > System.currentTimeMillis() - TimeUnit.DAYS.toMillis( + 365 + ) + } else { + false + } + } + .map { it.asDomainModel(counts[it.ben.benId], localizedResources) } } + + val eligibleCoupleTrackingNonFollowUpListCount = eligibleCoupleTrackingNonFollowUpList.map { it.size } + + val eligibleCoupleTrackingMissedPeriodList = benDao.getAllEligibleTrackingList(selectedVillage) + .combine(childCountsByBen) { list, counts -> + list.filter { + if (!it.savedECTRecords.isNullOrEmpty() && it.savedECTRecords.last().lmpDate != 0L) { + System.currentTimeMillis() - it.savedECTRecords.last().lmpDate > TimeUnit.DAYS.toMillis(35) + } else { + false + } + } + .map { it.asDomainModel(counts[it.ben.benId], localizedResources) } } + val eligibleCoupleTrackingMissedPeriodListCount = eligibleCoupleTrackingMissedPeriodList.map { it.size } var hrpPregnantWomenList = benDao.getAllPregnancyWomenForHRList(selectedVillage) .map { list -> list.map { it.asDomainModel() } } + val hrpPregnantWomenListCount = benDao.getAllPregnancyWomenForHRListCount(selectedVillage) var hrpTrackingPregList = benDao.getAllHRPTrackingPregList(selectedVillage) - .map { list -> list.map { it.asDomainModel() } } - val hrpTrackingPregListCount = benDao.getAllHRPTrackingPregListCount(selectedVillage) + .map { list -> list.map { it.asDomainModel(localizedResources) } } -// val hrpTrackingPregHistCount = hrpDao.getHRPTrackHist(ben) + val hrpTrackingPregListCount = benDao.getAllHRPTrackingPregListCount(selectedVillage) var hrpNonPregnantWomenList = benDao.getAllNonPregnancyWomenList(selectedVillage) .map { list -> list.map { it.asDomainModel() } } val hrpNonPregnantWomenListCount = benDao.getAllNonPregnancyWomenListCount(selectedVillage) var hrpTrackingNonPregList = benDao.getAllHRPTrackingNonPregList(selectedVillage) - .map { list -> list.map { it.asDomainModel() } } + .map { list -> list.map { it.asDomainModel(localizedResources) } } val hrpTrackingNonPregListCount = benDao.getAllHRPTrackingNonPregListCount(selectedVillage) @@ -180,6 +355,9 @@ class RecordsRepo @Inject constructor( fun getPregnantWomenList() = benDao.getAllPregnancyWomenList(selectedVillage) .map { list -> list.map { it.asPwrDomainModel() } } + fun getPregnantWomenWithRchList() = benDao.getAllPregnancyWomenWithRchList(selectedVillage) + .map { list -> list.map { it.asPwrDomainModel() } } + fun getRegisteredInfants() = childRegistrationDao.getAllRegisteredInfants(selectedVillage) .map { it.map { it.asBasicDomainModel() } } @@ -188,16 +366,103 @@ class RecordsRepo @Inject constructor( // .map { list -> list.map { it.ben } } fun getPregnantWomenListCount() = benDao.getAllPregnancyWomenListCount(selectedVillage) + fun getAbortionPregnantWomanCount() = benDao.getAllAbortionWomenListCount(selectedVillage) + fun getHighRiskWomenCount() = benDao.getHighRiskWomenCount(selectedVillage) + fun getMaternalDeathCount() = benDao.getAllMDSRCount(selectedVillage) + fun getNonMaternalDeathCount() = benDao.getAllNonMaternalDeathsCount(selectedVillage) + fun getChildDeathCount() = benDao.getAllCDRListCount(selectedVillage) + + fun getRegisteredPmsmaWomenList() = + benDao.getAllRegisteredPmsmaWomenList(selectedVillage) + .map { list -> + list.map { it.asDomainModel() } + } fun getRegisteredPregnantWomanList() = benDao.getAllRegisteredPregnancyWomenList(selectedVillage) .map { list -> list.filter { !it.savedAncRecords.any { it.maternalDeath == true } } .map { it.asDomainModel() } } + fun getHighRiskPregnantWomanList() = + benDao.getAllHighRiskPregnancyWomenList(selectedVillage) + .map { list -> + list.filter { !it.savedAncRecords.any { it.maternalDeath == true } } + .map { it.asDomainModel() } + } + + fun getAbortionPregnantWomanList(): Flow> = + benDao.getAllAbortionWomenList(selectedVillage) + .map { benList -> + benList + .filter { woman -> + woman.savedAncRecords.any { anc -> + anc.isAborted == true && anc.abortionDate != null + } + } + .map { it.asDomainModel() } + } + + + suspend fun getBenById(benId: Long): BenBasicDomain? { + return benDao.getBenById(benId)?.asBasicDomainModel() + } + fun getRegisteredPregnantWomanListCount() = benDao.getAllRegisteredPregnancyWomenListCount(selectedVillage) + fun getRegisteredPregnantWomanNonFollowUpList() = + benDao.getAllRegisteredPregnancyWomenList(selectedVillage) + .map { list -> + list.filter { + if (!it.savedAncRecords.isNullOrEmpty()) { + it.savedAncRecords.last().ancDate != 0L && + it.savedAncRecords.last().ancDate < System.currentTimeMillis() - TimeUnit.DAYS.toMillis( + 90 + ) && + it.savedAncRecords.last().ancDate > System.currentTimeMillis() - TimeUnit.DAYS.toMillis( + 365 + ) +// it.savedAncRecords.any { it1 -> +// it1.ancDate != 0L && +// it1.ancDate < System.currentTimeMillis() - TimeUnit.DAYS.toMillis(90) && +// it1.ancDate > System.currentTimeMillis() - TimeUnit.DAYS.toMillis(365) +// } + } else { + false + } + } + .map { it.asDomainModel() } + } + + fun getRegisteredPregnantWomanNonFollowUpListCount() = + getRegisteredPregnantWomanNonFollowUpList().map { it.size } + + fun getDuePregnantWomanList() = + benDao.getAllRegisteredPregnancyWomenList(selectedVillage) + .map { list -> + list.filter { benWithAnc -> + // Exclude maternal deaths + if (benWithAnc.savedAncRecords.any { it.maternalDeath == true }) return@filter false + // Exclude delivered women + if (benWithAnc.savedAncRecords.any { it.pregnantWomanDelivered == true }) return@filter false + + val activePwr = benWithAnc.pwr.firstOrNull { it.active } ?: return@filter false + val ancRecords = benWithAnc.savedAncRecords + + if (ancRecords.isEmpty()) { + // First ANC: due if >= minAnc1Week weeks from LMP + TimeUnit.MILLISECONDS.toDays(getTodayMillis() - activePwr.lmpDate) >= Konstants.minAnc1Week * 7 + } else { + val lastAncRecord = ancRecords.maxBy { it.visitNumber } + // Subsequent ANCs: due if EDD > lastAnc+28days, visitNumber < 4, and > 28 days since last ANC + (activePwr.lmpDate + TimeUnit.DAYS.toMillis(280)) > (lastAncRecord.ancDate + TimeUnit.DAYS.toMillis(28)) && + lastAncRecord.visitNumber < 4 && + TimeUnit.MILLISECONDS.toDays(getTodayMillis() - lastAncRecord.ancDate) > 28 + } + }.map { it.asDomainModel() } + } + val hrpCases = benDao.getHrpCases(selectedVillage) .map { list -> list.distinctBy { it.benId }.map { it.asBasicDomainModel() } } @@ -213,6 +478,9 @@ class RecordsRepo @Inject constructor( fun getListForInfantReg() = benDao.getListForInfantRegister(selectedVillage) .map { list -> list.flatMap { it.asBasicDomainModel() } } + fun getListForLowWeightInfantReg() = benDao.getListForLowWeightInfantRegister(selectedVillage) + .map { list -> list.flatMap { it.asBasicDomainModel() } } + fun getInfantRegisterCount() = benDao.getInfantRegisterCount(selectedVillage) @OptIn(ExperimentalCoroutinesApi::class) @@ -236,4 +504,40 @@ class RecordsRepo @Inject constructor( } fun getHRECCount() = maternalHealthDao.getAllECRecords() + + + suspend fun getHomeVisitUiState(benId: Long): HomeVisitUiState { + val formResponses = ancHomeVisitDao.getSyncedVisitsByRchId(benId) + + val visits = HomeVisitHelper.getANCSortedHomeVisits(formResponses) + // val visits = ancHomeVisitDao.getVisitsForBen(benId) + val visitCount = visits.size + + val canView = visitCount > 0 + val canAdd = canShowAddButton(visits) + + return HomeVisitUiState( + canAddHomeVisit = canAdd, + canViewHomeVisit = canView + ) + } + + private fun canShowAddButton(visits: List): Boolean { + if (visits.size >= 9) return false + + if (visits.isEmpty()) return true + + val lastVisitDateStr = visits.first().visitDate ?: return true + val lastVisitMillis = HelperUtil.parseDateToMillis(lastVisitDateStr) + + if (lastVisitMillis == 0L) return true + + val diffDays = getDaysDiff(lastVisitMillis, System.currentTimeMillis()) + return diffDays >= 30 + } + + private fun getDaysDiff(from: Long, to: Long): Long { + val diff = to - from + return TimeUnit.MILLISECONDS.toDays(diff) + } } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/SaasBahuSammelanRepo.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/SaasBahuSammelanRepo.kt new file mode 100644 index 000000000..dc49fac8b --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/SaasBahuSammelanRepo.kt @@ -0,0 +1,207 @@ +package org.piramalswasthya.sakhi.repositories + +import android.content.Context +import android.util.Base64 +import android.util.Log +import androidx.core.content.FileProvider +import com.squareup.moshi.Moshi +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.MultipartBody +import okhttp3.RequestBody.Companion.asRequestBody +import okhttp3.RequestBody.Companion.toRequestBody +import org.json.JSONObject +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.database.room.dao.SaasBahuSammelanDao +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao +import org.piramalswasthya.sakhi.helpers.getDateFromLong +import org.piramalswasthya.sakhi.model.SaasBahuSammelanCache +import org.piramalswasthya.sakhi.model.SaasBahuSammelanGetAllResponse +import org.piramalswasthya.sakhi.network.AmritApiService +import org.piramalswasthya.sakhi.network.GetDataRequest +import org.piramalswasthya.sakhi.network.getLongFromDate +import org.piramalswasthya.sakhi.repositories.BenRepo.Companion.getCurrentDate +import org.piramalswasthya.sakhi.utils.HelperUtil.compressImageToTemp +import org.piramalswasthya.sakhi.utils.HelperUtil.copyToTemp +import org.piramalswasthya.sakhi.utils.HelperUtil.detectExtAndMime +import org.piramalswasthya.sakhi.utils.HelperUtil.getFileName +import timber.log.Timber +import java.io.File +import java.net.SocketTimeoutException +import javax.inject.Inject +import kotlin.collections.forEach + +class SaasBahuSammelanRepo @Inject constructor( + private val userRepo: UserRepo, + @ApplicationContext val appContext: Context, + private val preferenceDao: PreferenceDao, + private val tmcNetworkApiService: AmritApiService, + private val saasBahuDao: SaasBahuSammelanDao, + private val moshi: Moshi + + +) { + + suspend fun saveSammelanForm(saasBahuSammelanCache: SaasBahuSammelanCache) { + withContext(Dispatchers.IO) { + saasBahuDao.insertSammelan(saasBahuSammelanCache) + } + } + + suspend fun pushUnSyncedRecordsSaasBahuSammelan(): Boolean { + + return withContext(Dispatchers.IO) { + val user = + preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") + + val saasBahuList: List = + saasBahuDao.getBySyncState(SyncState.UNSYNCED) + try { + saasBahuList.forEach { row -> + val imagesParts = (row.sammelanImages ?: emptyList()).mapNotNull { uriStr -> + val uri = android.net.Uri.parse(uriStr) + val name = getFileName(uri, appContext) ?: "upload" + val mime = + appContext.contentResolver.getType(uri) ?: "application/octet-stream" + val fileForUpload = if (mime.startsWith("image/")) compressImageToTemp( + uri, + name, + appContext + ) else copyToTemp(uri, name, appContext) + fileForUpload?.let { file -> + val body = file.asRequestBody(mime.toMediaTypeOrNull()) + MultipartBody.Part.createFormData("sammelanImages", file.name, body) + } + } + + val response = tmcNetworkApiService.postSaasBahuSammelanMultipart( + meetingDate = (row.date.toString()).toRequestBody("text/plain".toMediaTypeOrNull()), + place = (row.place ?: "").toRequestBody("text/plain".toMediaTypeOrNull()), + participants = ((row.participants + ?: 0).toString()).toRequestBody("text/plain".toMediaTypeOrNull()), + ashaId = ((row.ashaId + ?: 0).toString()).toRequestBody("text/plain".toMediaTypeOrNull()), + meetingImages = imagesParts + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Push to amrit saas bahu sammelan data : $responseStatusCode") + when (responseStatusCode) { + 200 -> { + try { + updateSyncStatusScreening(saasBahuList) + return@withContext true + } catch (e: Exception) { + Timber.d("Saas Bahu Sammelan entries not synced $e") + } + + } + + 5002 -> { + if (userRepo.refreshTokenTmc( + user.userName, user.password + ) + ) throw SocketTimeoutException("Refreshed Token!") + else throw IllegalStateException("User Logged out!!") + } + + 5000 -> { + val errorMessage = jsonObj.getString("errorMessage") + if (errorMessage == "No record found") return@withContext false + } + + else -> { + throw IllegalStateException("$responseStatusCode received, dont know what todo!?") + } + } + } + } + } + } catch (e: SocketTimeoutException) { + Timber.e("get_tb error : $e") + return@withContext false + + } catch (e: java.lang.IllegalStateException) { + Timber.e("get_tb error : $e") + return@withContext false + } + true + } + } + + + + private suspend fun updateSyncStatusScreening(saasBahuList: List) { + saasBahuList.forEach { + it.syncState = SyncState.SYNCED + saasBahuDao.insertSammelan(it) + } + } + + + + + suspend fun SaasBahuSamelanGettDataFromServer() = withContext(Dispatchers.IO) { + val response = tmcNetworkApiService.getSaasBahuSammelans( + GetDataRequest( + 0, + getCurrentDate(preferenceDao.getLastSyncedTimeStamp()), + getCurrentDate(), + 0, + preferenceDao.getLoggedInUser()?.userId?.toLong()!!, + preferenceDao.getLoggedInUser()?.userName!!, + preferenceDao.getLoggedInUser()?.userId?.toLong()!! + ) + ) + if (!response.isSuccessful) return@withContext + val body = response.body()?.string() ?: return@withContext + Log.e("AHJAHA",body) + val adapter = moshi.adapter(SaasBahuSammelanGetAllResponse::class.java) + val parsed = adapter.fromJson(body) ?: return@withContext + saasBahuDao.clearAll() + parsed.data?.forEach { item -> + val imageBase64List = item.meetingImages ?: emptyList() + val imageUriList = imageBase64List.mapNotNull { base64 -> + try { + val base64Data = base64.substringAfter(",", base64) + val bytes = Base64.decode(base64Data, Base64.DEFAULT) + val (ext, _) = detectExtAndMime(bytes) + val file = File(appContext.cacheDir, "saas_bahu_sammelan${System.currentTimeMillis()}.$ext") + file.outputStream().use { it.write(bytes) } + val uri = FileProvider.getUriForFile( + appContext, + "${appContext.packageName}.provider", + file + ) + uri.toString() + } catch (e: Exception) { + Log.e("AHJAHA",e.toString()) + null + } + } + + val entity = SaasBahuSammelanCache( + id = item.id?.toLong()!!, + date = item.meetingDate, + place = item.place, + participants = item.participants, + ashaId = item.ashaId!!, + sammelanImages = imageUriList, + syncState = SyncState.SYNCED + ) + + saasBahuDao.insertSammelan(entity) + } + } + + + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/TBRepo.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/TBRepo.kt index 626662602..bc803f0e3 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/repositories/TBRepo.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/TBRepo.kt @@ -9,10 +9,13 @@ import org.piramalswasthya.sakhi.database.room.dao.BenDao import org.piramalswasthya.sakhi.database.room.dao.TBDao import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao import org.piramalswasthya.sakhi.helpers.Konstants +import org.piramalswasthya.sakhi.model.TBConfirmedTreatmentCache import org.piramalswasthya.sakhi.model.TBScreeningCache import org.piramalswasthya.sakhi.model.TBSuspectedCache import org.piramalswasthya.sakhi.network.AmritApiService import org.piramalswasthya.sakhi.network.GetDataPaginatedRequest +import org.piramalswasthya.sakhi.network.TBConfirmedRequestDTO +import org.piramalswasthya.sakhi.network.TBConfirmedTreatmentDTO import org.piramalswasthya.sakhi.network.TBScreeningDTO import org.piramalswasthya.sakhi.network.TBScreeningRequestDTO import org.piramalswasthya.sakhi.network.TBSuspectedDTO @@ -55,6 +58,25 @@ class TBRepo @Inject constructor( } } + suspend fun getTBConfirmed(benId: Long): TBConfirmedTreatmentCache? { + return withContext(Dispatchers.IO) { + tbDao.getTbConfirmed(benId) + } + } + + suspend fun saveTBConfirmed(tbConfirmedTreatmentCache: TBConfirmedTreatmentCache) { + withContext(Dispatchers.IO) + { + tbDao.saveTbConfirmed(tbConfirmedTreatmentCache) + } + } + + suspend fun getAllFollowUpsForBeneficiary(benId: Long): List { + return withContext(Dispatchers.IO) { + tbDao.getAllFollowUpsForBeneficiary(benId) + } + } + suspend fun getTBScreeningDetailsFromServer(): Int { return withContext(Dispatchers.IO) { val user = @@ -92,7 +114,7 @@ class TBRepo @Inject constructor( return@withContext 1 } - 5002 -> { + 401,5002 -> { if (userRepo.refreshTokenTmc( user.userName, user.password ) @@ -112,11 +134,11 @@ class TBRepo @Inject constructor( } } catch (e: SocketTimeoutException) { - Timber.d("get_tb error : $e") + Timber.e("get_tb error : $e") return@withContext -2 } catch (e: java.lang.IllegalStateException) { - Timber.d("get_tb error : $e") + Timber.e("get_tb error : $e") return@withContext -1 } -1 @@ -181,7 +203,7 @@ class TBRepo @Inject constructor( return@withContext 1 } - 5002 -> { + 401,5002 -> { if (userRepo.refreshTokenTmc( user.userName, user.password ) @@ -201,11 +223,11 @@ class TBRepo @Inject constructor( } } catch (e: SocketTimeoutException) { - Timber.d("get_tb error : $e") + Timber.e("get_tb error : $e") return@withContext -2 } catch (e: java.lang.IllegalStateException) { - Timber.d("get_tb error : $e") + Timber.e("get_tb error : $e") return@withContext -1 } -1 @@ -233,152 +255,335 @@ class TBRepo @Inject constructor( return tbSuspectedList } - suspend fun pushUnSyncedRecords(): Boolean { - val screeningResult = pushUnSyncedRecordsTBScreening() - val suspectedResult = pushUnSyncedRecordsTBSuspected() - return (screeningResult == 1) && (suspectedResult == 1) - } - - private suspend fun pushUnSyncedRecordsTBScreening(): Int { + suspend fun getTbConfirmedDetailsFromServer(): Int { return withContext(Dispatchers.IO) { - val user = - preferenceDao.getLoggedInUser() - ?: throw IllegalStateException("No user logged in!!") - val tbsnList: List = tbDao.getTBScreening(SyncState.UNSYNCED) - - val tbsnDtos = mutableListOf() - tbsnList.forEach { cache -> - tbsnDtos.add(cache.toDTO()) - } - if (tbsnDtos.isEmpty()) return@withContext 1 try { - val response = tmcNetworkApiService.saveTBScreeningData( - TBScreeningRequestDTO( - userId = user.userId, - tbScreeningList = tbsnDtos - ) - ) + + val user = + preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") + + + val response = tmcNetworkApiService.getTBConfirmedData() val statusCode = response.code() + if (statusCode == 200) { val responseString = response.body()?.string() + if (responseString != null) { val jsonObj = JSONObject(responseString) + val errorMessage = jsonObj.getString("errorMessage") val responseStatusCode = jsonObj.getInt("statusCode") - Timber.d("Push to amrit tb screening data : $responseStatusCode") + + when (responseStatusCode) { 200 -> { try { - updateSyncStatusScreening(tbsnList) - return@withContext 1 + val dataObj = jsonObj.getString("data") + + saveTBConfirmedCacheFromResponse(dataObj) + } catch (e: Exception) { - Timber.d("TB Screening entries not synced $e") + Timber.e(e, "TBConfirmed: Error while saving data") + return@withContext 0 } + return@withContext 1 } 5002 -> { - if (userRepo.refreshTokenTmc( - user.userName, user.password - ) - ) throw SocketTimeoutException("Refreshed Token!") - else throw IllegalStateException("User Logged out!!") + if (userRepo.refreshTokenTmc(user.userName, user.password)) + throw SocketTimeoutException("Refreshed Token!") + else + throw IllegalStateException("User Logged out!!") } 5000 -> { - val errorMessage = jsonObj.getString("errorMessage") - if (errorMessage == "No record found") return@withContext 0 + if (errorMessage == "No record found") { + return@withContext 0 + } } else -> { - throw IllegalStateException("$responseStatusCode received, dont know what todo!?") + throw IllegalStateException("$responseStatusCode received, don't know what todo!?") } } } } } catch (e: SocketTimeoutException) { - Timber.d("get_tb error : $e") return@withContext -2 - } catch (e: java.lang.IllegalStateException) { - Timber.d("get_tb error : $e") + } catch (e: IllegalStateException) { + return@withContext -1 + } catch (e: Exception) { return@withContext -1 } + -1 } } - private suspend fun pushUnSyncedRecordsTBSuspected(): Int { + + private suspend fun saveTBConfirmedCacheFromResponse(dataObj: String): MutableList { + + + val tbConfirmedList = mutableListOf() + + try { + val requestDTO = Gson().fromJson(dataObj, TBConfirmedRequestDTO::class.java) + + + requestDTO?.tbConfirmedList?.forEachIndexed { index, tbConfirmedDTO -> + + + try { + val cache = tbConfirmedDTO.toCache() + + tbDao.saveTbConfirmed(cache) + + + tbConfirmedList.add(cache) + + } catch (e: Exception) { + } + } + + } catch (e: Exception) { + Timber.e(e, "TBConfirmed: Error parsing or saving JSON") + } + + return tbConfirmedList + } + + + // RECORD-LEVEL ISOLATION: Coordinator always returns true so the + // WorkManager worker succeeds. Each sub-method handles its own failures + // independently — failed records stay UNSYNCED and retry on next sync cycle. + // Previously, if any sub-method failed, the coordinator returned false which + // could cause the worker to be marked as failed. + suspend fun pushUnSyncedRecords(): Boolean { + val screeningResult = pushUnSyncedRecordsTBScreening() + val suspectedResult = pushUnSyncedRecordsTBSuspected() + val confirmedResult = pushUnSyncedRecordsTBConfirmed() + Timber.d("TB push results: screening=$screeningResult, suspected=$suspectedResult, confirmed=$confirmedResult") + // Worker succeeds — failed records stay UNSYNCED for next cycle + return true + } + + // RECORD-LEVEL ISOLATION: TB Screening records are now sent in + // chunks of 20 instead of one giant batch. Previously, if ANY record in + // the batch was malformed, the ENTIRE batch failed and ALL records stayed + // UNSYNCED. Now each chunk is independent — one bad chunk doesn't affect + // the others. Failed chunks' records stay UNSYNCED for the next sync cycle. + // Also removed dangerous recursive retry on SocketTimeoutException that + // could cause infinite recursion and stack overflow. + private suspend fun pushUnSyncedRecordsTBScreening(): Int { + return withContext(Dispatchers.IO) { val user = preferenceDao.getLoggedInUser() ?: throw IllegalStateException("No user logged in!!") - val tbspList: List = tbDao.getTbSuspected(SyncState.UNSYNCED) + val tbsnList: List = tbDao.getTBScreening(SyncState.UNSYNCED) - val tbspDtos = mutableListOf() - tbspList.forEach { cache -> - tbspDtos.add(cache.toDTO()) - } - if (tbspDtos.isEmpty()) return@withContext 1 - try { - val response = tmcNetworkApiService.saveTBSuspectedData( - TBSuspectedRequestDTO( - userId = user.userId, - tbSuspectedList = tbspDtos + if (tbsnList.isEmpty()) return@withContext 1 + + // Chunk records to prevent all-or-nothing batch failure + val CHUNK_SIZE = 20 + val chunks = tbsnList.chunked(CHUNK_SIZE) + var successCount = 0 + var failCount = 0 + + for (chunk in chunks) { + try { + val chunkDtos = chunk.map { it.toDTO() } + + val response = tmcNetworkApiService.saveTBScreeningData( + TBScreeningRequestDTO( + userId = user.userId, + tbScreeningList = chunkDtos + ) ) - ) - val statusCode = response.code() - if (statusCode == 200) { - val responseString = response.body()?.string() - if (responseString != null) { - val jsonObj = JSONObject(responseString) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Push to Amrit TB Screening chunk: $responseStatusCode") + when (responseStatusCode) { + 200 -> { + updateSyncStatusScreening(chunk) + successCount += chunk.size + } - val errorMessage = jsonObj.getString("errorMessage") - val responseStatusCode = jsonObj.getInt("statusCode") - Timber.d("Push to amrit tb screening data : $responseStatusCode") - when (responseStatusCode) { - 200 -> { - try { - updateSyncStatusSuspected(tbspList) - return@withContext 1 - } catch (e: Exception) { - Timber.d("TB Screening entries not synced $e") + 401, 5002 -> { + // Token expired — try refreshing for subsequent chunks + if (userRepo.refreshTokenTmc(user.userName, user.password)) { + Timber.d("Token refreshed, TB Screening chunk will retry next cycle") + } + failCount += chunk.size } + else -> { + Timber.e("TB Screening chunk failed with statusCode: $responseStatusCode") + failCount += chunk.size + } } + } + } else { + Timber.e("TB Screening chunk HTTP error: $statusCode") + failCount += chunk.size + } + } catch (e: Exception) { + Timber.e(e, "TB Screening chunk push failed: ${chunk.size} records") + failCount += chunk.size + } + } - 5002 -> { - if (userRepo.refreshTokenTmc( - user.userName, user.password - ) - ) throw SocketTimeoutException("Refreshed Token!") - else throw IllegalStateException("User Logged out!!") - } + Timber.d("TB Screening push complete: $successCount succeeded, $failCount failed out of ${tbsnList.size}") + // Worker succeeds — failed records stay UNSYNCED for next cycle + return@withContext 1 + } + } - 5000 -> { - if (errorMessage == "No record found") return@withContext 0 - } + // RECORD-LEVEL ISOLATION: Same chunking pattern as TB Screening. + // Records sent in chunks of 20 with per-chunk error isolation. + private suspend fun pushUnSyncedRecordsTBSuspected(): Int { + return withContext(Dispatchers.IO) { + val user = + preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") - else -> { - throw IllegalStateException("$responseStatusCode received, dont know what todo!?") + val tbspList: List = tbDao.getTbSuspected(SyncState.UNSYNCED) + + if (tbspList.isEmpty()) return@withContext 1 + + val CHUNK_SIZE = 20 + val chunks = tbspList.chunked(CHUNK_SIZE) + var successCount = 0 + var failCount = 0 + + for (chunk in chunks) { + try { + val chunkDtos = chunk.map { it.toDTO() } + + val response = tmcNetworkApiService.saveTBSuspectedData( + TBSuspectedRequestDTO( + userId = user.userId, + tbSuspectedList = chunkDtos + ) + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Push to Amrit TB Suspected chunk: $responseStatusCode") + when (responseStatusCode) { + 200 -> { + updateSyncStatusSuspected(chunk) + successCount += chunk.size + } + + 401, 5002 -> { + if (userRepo.refreshTokenTmc(user.userName, user.password)) { + Timber.d("Token refreshed, TB Suspected chunk will retry next cycle") + } + failCount += chunk.size + } + + else -> { + Timber.e("TB Suspected chunk failed with statusCode: $responseStatusCode") + failCount += chunk.size + } } } + } else { + Timber.e("TB Suspected chunk HTTP error: $statusCode") + failCount += chunk.size } + } catch (e: Exception) { + Timber.e(e, "TB Suspected chunk push failed: ${chunk.size} records") + failCount += chunk.size } + } - } catch (e: SocketTimeoutException) { - Timber.d("get_tb error : $e") - return@withContext -2 + Timber.d("TB Suspected push complete: $successCount succeeded, $failCount failed out of ${tbspList.size}") + return@withContext 1 + } + } - } catch (e: java.lang.IllegalStateException) { - Timber.d("get_tb error : $e") - return@withContext -1 + // RECORD-LEVEL ISOLATION: Same chunking pattern as TB Screening. + // Records sent in chunks of 20 with per-chunk error isolation. + private suspend fun pushUnSyncedRecordsTBConfirmed(): Int { + return withContext(Dispatchers.IO) { + val user = + preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") + + val tbspList: List = tbDao.getTbConfirmed(SyncState.UNSYNCED) + + if (tbspList.isEmpty()) return@withContext 1 + + val CHUNK_SIZE = 20 + val chunks = tbspList.chunked(CHUNK_SIZE) + var successCount = 0 + var failCount = 0 + + for (chunk in chunks) { + try { + val chunkDtos = chunk.map { it.toDTO() } + + val response = tmcNetworkApiService.saveTBConfirmedData( + TBConfirmedRequestDTO( + userId = user.userId, + tbConfirmedList = chunkDtos + ) + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Push to Amrit TB Confirmed chunk: $responseStatusCode") + when (responseStatusCode) { + 200 -> { + updateSyncStatusConfirmed(chunk) + successCount += chunk.size + } + + 401, 5002 -> { + if (userRepo.refreshTokenTmc(user.userName, user.password)) { + Timber.d("Token refreshed, TB Confirmed chunk will retry next cycle") + } + failCount += chunk.size + } + + else -> { + Timber.e("TB Confirmed chunk failed with statusCode: $responseStatusCode") + failCount += chunk.size + } + } + } + } else { + Timber.e("TB Confirmed chunk HTTP error: $statusCode") + failCount += chunk.size + } + } catch (e: Exception) { + Timber.e(e, "TB Confirmed chunk push failed: ${chunk.size} records") + failCount += chunk.size + } } - -1 + + Timber.d("TB Confirmed push complete: $successCount succeeded, $failCount failed out of ${tbspList.size}") + return@withContext 1 } } @@ -397,6 +602,13 @@ class TBRepo @Inject constructor( } } + private suspend fun updateSyncStatusConfirmed(tbspList: List) { + tbspList.forEach { + it.syncState = SyncState.SYNCED + tbDao.saveTbConfirmed(it) + } + } + companion object { private val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) private val timeFormat = SimpleDateFormat("HH:mm:ss", Locale.ENGLISH) diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/UserRepo.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/UserRepo.kt index b7733642b..5c2b470fe 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/repositories/UserRepo.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/UserRepo.kt @@ -15,6 +15,7 @@ import org.piramalswasthya.sakhi.model.SyncStatusCache import org.piramalswasthya.sakhi.model.User import org.piramalswasthya.sakhi.network.AmritApiService import org.piramalswasthya.sakhi.network.TmcAuthUserRequest +import org.piramalswasthya.sakhi.network.TmcRefreshTokenRequest import org.piramalswasthya.sakhi.network.interceptors.TokenInsertTmcInterceptor import retrofit2.HttpException import timber.log.Timber @@ -35,7 +36,37 @@ class UserRepo @Inject constructor( val unProcessedRecordCount: Flow> = syncDao.getSyncStatus() + + suspend fun authenticateUser(userName: String, password: String): NetworkResponse { + return withContext(Dispatchers.IO) { + try { + val userId = getTokenAmrit(userName, password) + val user = setUserRole(userId, password) + return@withContext NetworkResponse.Success(user) + } catch (se: SocketTimeoutException) { + return@withContext NetworkResponse.Error(message = "Server timed out !") + } catch (se: HttpException) { + return@withContext when (se.code()) { + 401 -> NetworkResponse.Error(message = "Unauthorized: Invalid credentials") + else -> NetworkResponse.Error(message = "Unable to connect to server!") + } + // return@withContext NetworkResponse.Error(message = "Unable to connect to server !") + } catch (ce: ConnectException) { + return@withContext NetworkResponse.Error(message = "Server refused connection !") + } catch (ue: UnknownHostException) { + return@withContext NetworkResponse.Error(message = "Unable to connect to server !") + } catch (ie: Exception) { + if (ie.message == "Invalid username / password") + return@withContext NetworkResponse.Error(message = "Invalid Username/password") + else + return@withContext NetworkResponse.Error(message = ie.message ?: "Something went wrong... Try again later") + + } + } + } + + suspend fun saveToken(userName: String, password: String): NetworkResponse { return withContext(Dispatchers.IO) { try { val userId = getTokenAmrit(userName, password) @@ -53,7 +84,7 @@ class UserRepo @Inject constructor( if (ie.message == "Invalid username / password") return@withContext NetworkResponse.Error(message = "Invalid Username/password") else - return@withContext NetworkResponse.Error(message = "Something went wrong... Try again later") + return@withContext NetworkResponse.Error(message = ie.message ?: "Something went wrong... Try again later") } } @@ -66,6 +97,7 @@ class UserRepo @Inject constructor( return user } + private fun offlineLogin(userName: String, password: String): Boolean { val loggedInUser = preferenceDao.getLoggedInUser() loggedInUser?.let { @@ -95,17 +127,12 @@ class UserRepo @Inject constructor( suspend fun refreshTokenTmc(userName: String, password: String): Boolean { return withContext(Dispatchers.IO) { try { - val response = - amritApiService.getJwtToken( - json = TmcAuthUserRequest( - userName, - encrypt(password) - ) - ) - Timber.d("JWT : $response") - if (!response.isSuccessful) { - return@withContext false - } + val refreshToken = preferenceDao.getRefreshToken() + ?: return@withContext false + val response = amritApiService.getRefreshToken( + json = TmcRefreshTokenRequest(refreshToken) + ) + val responseBody = JSONObject( response.body()?.string() ?: throw IllegalStateException("Response success but data missing @ $response") @@ -113,27 +140,29 @@ class UserRepo @Inject constructor( val responseStatusCode = responseBody.getInt("statusCode") if (responseStatusCode == 200) { val data = responseBody.getJSONObject("data") + TokenInsertTmcInterceptor.setJwt(data.getString("jwtToken")) + preferenceDao.registerJWTAmritToken(data.getString("jwtToken")) + preferenceDao.registerRefreshToken(data.getString("refreshToken")) + val token = data.getString("key") TokenInsertTmcInterceptor.setToken(token) preferenceDao.registerAmritToken(token) return@withContext true } else { val errorMessage = responseBody.getString("errorMessage") - Timber.d("Error Message $errorMessage") + Timber.e("Error Message $errorMessage") + return@withContext false } - return@withContext false + } catch (se: SocketTimeoutException) { return@withContext refreshTokenTmc(userName, password) } catch (e: HttpException) { - Timber.d("Auth Failed!") + Timber.e("Auth Failed!") return@withContext false } catch (e: Exception) { - return@withContext false + return@withContext true } - - } - } private suspend fun getTokenAmrit(userName: String, password: String): Int { @@ -143,7 +172,6 @@ class UserRepo @Inject constructor( amritApiService.getJwtToken( json = TmcAuthUserRequest( userName, -// password, encryptedPassword ) ) @@ -154,11 +182,17 @@ class UserRepo @Inject constructor( ) val statusCode = responseBody.getInt("statusCode") if (statusCode == 5002) + throw IllegalStateException("Login failed") + if (statusCode == 401) throw IllegalStateException("Invalid username / password") val data = responseBody.getJSONObject("data") val token = data.getString("key") val userId = data.getInt("userID") - db.clearAllTables() + val refreshToken = data.getString("refreshToken") + // db.clearAllTables() + TokenInsertTmcInterceptor.setJwt(data.getString("jwtToken")) + preferenceDao.registerJWTAmritToken(data.getString("jwtToken")) + preferenceDao.registerRefreshToken(refreshToken) TokenInsertTmcInterceptor.setToken(token) preferenceDao.registerAmritToken(token) preferenceDao.lastAmritTokenFetchTimestamp = System.currentTimeMillis() @@ -166,5 +200,26 @@ class UserRepo @Inject constructor( } } + suspend fun saveFirebaseToken(userId: Int, token: String, updatedAt: String) { + withContext(Dispatchers.IO) { + try { + val requestBody = mapOf( + "userId" to userId, + "token" to token, + "updatedAt" to updatedAt + ) + + val response = amritApiService.saveFirebaseToken(requestBody) + + if (response.isSuccessful) { + Timber.d("Firebase token saved successfully: ${response.body()?.string()}") + } else { + Timber.e("Failed to save Firebase token: ${response.code()} ${response.errorBody()?.string()}") + } + } catch (e: Exception) { + Timber.e(e, "Exception while saving Firebase token") + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/UwinRepo.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/UwinRepo.kt new file mode 100644 index 000000000..bdadeca0a --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/UwinRepo.kt @@ -0,0 +1,278 @@ +package org.piramalswasthya.sakhi.repositories + + +import android.content.Context +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import com.squareup.moshi.Moshi +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.MultipartBody +import okhttp3.RequestBody.Companion.asRequestBody +import okhttp3.RequestBody.Companion.toRequestBody +import org.json.JSONObject +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.database.room.dao.SyncDao +import org.piramalswasthya.sakhi.database.room.dao.UwinDao +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao +import org.piramalswasthya.sakhi.model.UwinCache +import org.piramalswasthya.sakhi.model.UwinGetAllRequest +import org.piramalswasthya.sakhi.network.AmritApiService +import retrofit2.Response +import timber.log.Timber +import java.io.File +import java.io.FileOutputStream +import java.net.SocketTimeoutException +import javax.inject.Inject +import android.util.Base64 +import androidx.core.content.FileProvider +import org.piramalswasthya.sakhi.model.UwinNetwork +import org.piramalswasthya.sakhi.repositories.BenRepo.Companion.getCurrentDate +import org.piramalswasthya.sakhi.utils.HelperUtil.compressImageToTemp +import org.piramalswasthya.sakhi.utils.HelperUtil.copyToTemp +import org.piramalswasthya.sakhi.utils.HelperUtil.detectExtAndMime +import org.piramalswasthya.sakhi.utils.HelperUtil.getFileName +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + + +class UwinRepo @Inject constructor( + @ApplicationContext private val appContext: Context, + private val amritApiService: AmritApiService, + private val preferenceDao: PreferenceDao, + private val syncDao: SyncDao, + private val userRepo: UserRepo, + private val uwinDao: UwinDao, + private val moshi: Moshi +) { + + private val maxRetries = 3 + fun getAllLocalRecords() = uwinDao.getAllUwinRecords() + + suspend fun insertLocalRecord(record: UwinCache) = withContext(Dispatchers.IO) { + uwinDao.insert(record) + } + + suspend fun updateLocalRecord(record: UwinCache) = withContext(Dispatchers.IO) { + uwinDao.update(record) + } + + suspend fun getUwinById(id: Int): UwinCache? = uwinDao.getUwinById(id) + + private fun buildMultipartFromUris(network: UwinNetwork): List { + val files = listOfNotNull(network.uploadedFiles1, network.uploadedFiles2) + val parts = mutableListOf() + + files.forEach { uriStr -> + try { + val uri = android.net.Uri.parse(uriStr) + val mime = appContext.contentResolver.getType(uri) ?: "application/octet-stream" + val fileName = getFileName(uri, appContext) ?: "upload" + val file = + if (mime.startsWith("image/")) compressImageToTemp(uri, fileName, appContext) + else copyToTemp(uri, fileName, appContext) + file?.let { + val body = it.asRequestBody(mime.toMediaTypeOrNull()) + parts.add(MultipartBody.Part.createFormData("meetingImages", it.name, body)) + } + } catch (e: Exception) { + Timber.e(e, "❌ Failed to build multipart from URI: $uriStr") + } + } + return parts + } + + suspend fun tryUpsync(): Boolean = withContext(Dispatchers.IO) { + try { + val unsyncedSessions = uwinDao.getUnsyncedSessions() + if (unsyncedSessions.isEmpty()) { + Timber.d("☑️ No unsynced sessions found.") + return@withContext true + } + + var allSuccess = true + for (cache in unsyncedSessions) { + val success = postUwinSession(cache.asDomainModel()) + if (!success) { + allSuccess = false + Timber.e("❌ Failed to sync session with id=${cache.id}") + } + } + + Timber.d("✅ Upsync completed. Success = $allSuccess") + allSuccess + } catch (e: Exception) { + Timber.e(e, "❌ Exception during upsync.") + false + } + } + + suspend fun postUwinSession(network: UwinNetwork,retryCount: Int = 0): Boolean = withContext(Dispatchers.IO) { + + if (retryCount >= maxRetries) { + Timber.e("❌ Max retries ($maxRetries) exceeded for session id=${network.id}") + return@withContext false + } + + + val user = preferenceDao.getLoggedInUser() ?: return@withContext false + + val images = buildMultipartFromUris(network) + val meetingDateFormatted = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH) + .format(Date(network.sessionDate)) + + val meetingDate = meetingDateFormatted.toRequestBody("text/plain".toMediaTypeOrNull()) + val place = (network.place ?: "").toRequestBody("text/plain".toMediaTypeOrNull()) + val participants = + network.participantsCount.toString().toRequestBody("text/plain".toMediaTypeOrNull()) + val ashaId = user.userId.toString().toRequestBody("text/plain".toMediaTypeOrNull()) + val createdBy = user.userName.toRequestBody("text/plain".toMediaTypeOrNull()) + + + try { + val response = amritApiService.saveUwinSession( + meetingDate, place, participants, ashaId, createdBy, images + ) + + + if (response.isSuccessful) { + val bodyString = response.body()?.string() + Timber.d("📦 Raw Response Body: $bodyString") + + val json = try { + JSONObject(bodyString ?: "") + } catch (e: Exception) { + return@withContext false + } + val hasStatusCode = json.has("statusCode") + val statusCode = json.optInt("statusCode", -1) + val errorMessage = json.optString("errorMessage", "") + + if (!hasStatusCode && json.has("id")) { + + uwinDao.updateSyncState(network.id, SyncState.SYNCED) + return@withContext true + } + when (statusCode) { + 200 -> { + uwinDao.updateSyncState(network.id, SyncState.SYNCED) + true + } + + 5002 -> { + if (userRepo.refreshTokenTmc(user.userName, user.password)) { + return@withContext postUwinSession(network, retryCount + 1) + } + false + } + + else -> { + false + } + } + } else { + val errorBody = response.errorBody()?.string() + false + } + } catch (e: SocketTimeoutException) { + postUwinSession(network, retryCount + 1) + } catch (e: Exception) { + false + } + } + + + suspend fun downSyncAndPersist() = withContext(Dispatchers.IO) { + val user = preferenceDao.getLoggedInUser() ?: return@withContext + val response = amritApiService.getAllUwinSessions( + UwinGetAllRequest( + villageID = 0, + fromDate = getCurrentDate(preferenceDao.getLastSyncedTimeStamp()), + toDate = getCurrentDate(), + pageNo = 0, + userId = user.userId, + userName = user.userName, + ashaId = user.userId + ) + ) + + if (!response.isSuccessful) { + return@withContext + } + + val body = response.body()?.string() ?: return@withContext + val adapter = moshi.adapter(UwinGetAllResponse::class.java) + val parsed = adapter.fromJson(body) ?: return@withContext + + val sdf = SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) + + val entries = parsed.data?.entries ?: emptyList() + + + val localList = entries.mapNotNull { item -> + val imageUriList = item.meetingImages?.mapNotNull { base64 -> + try { + val base64Data = base64.substringAfter(",", base64) + val bytes = Base64.decode(base64Data, Base64.DEFAULT) + val (ext, _) = detectExtAndMime(bytes) + val file = File(appContext.cacheDir, "uwin_${System.currentTimeMillis()}.$ext") + file.outputStream().use { it.write(bytes) } + val uri = FileProvider.getUriForFile( + appContext, + "${appContext.packageName}.provider", + file + ) + uri.toString() + } catch (e: Exception) { + null + } + } ?: emptyList() + val sessionDateMillis = try { + item.meetingDate?.let { sdf.parse(it)?.time } ?: 0L + } catch (e: ParseException) { + 0L + } + UwinCache( + id = item.id ?: 0, + sessionDate = sessionDateMillis, + place = item.place, + participantsCount = item.participants ?: 0, + uploadedFiles1 = imageUriList.getOrNull(0), + uploadedFiles2 = imageUriList.getOrNull(1), + createdBy = user.userName, + updatedBy = user.userName, + syncState = SyncState.SYNCED + ) + } + + uwinDao.replaceAll(localList) + + } + + + @JsonClass(generateAdapter = true) + data class UwinGetAllResponse( + @Json(name = "data") val data: UwinData?, + @Json(name = "statusCode") val statusCode: Int?, + @Json(name = "status") val status: String? + ) + + @JsonClass(generateAdapter = true) + data class UwinData( + @Json(name = "entries") val entries: List? + ) + + @JsonClass(generateAdapter = true) + data class UwinServerItem( + @Json(name = "id") val id: Int?, + @Json(name = "ashaId") val ashaId: Int?, + @Json(name = "date") val meetingDate: String?, + @Json(name = "place") val place: String?, + @Json(name = "participants") val participants: Int?, + @Json(name = "attachments") val meetingImages: List? + ) +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/VLFRepo.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/VLFRepo.kt new file mode 100644 index 000000000..2b63a41ef --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/VLFRepo.kt @@ -0,0 +1,1743 @@ +package org.piramalswasthya.sakhi.repositories + +import android.content.Context +import android.os.Build +import android.util.Log +import androidx.annotation.RequiresApi +import com.google.gson.Gson +import com.google.gson.JsonArray +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.withContext +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.MultipartBody +import okhttp3.RequestBody.Companion.asRequestBody +import okhttp3.RequestBody.Companion.toRequestBody +import org.json.JSONArray +import org.json.JSONObject +import org.piramalswasthya.sakhi.database.room.InAppDb +import org.piramalswasthya.sakhi.database.room.SyncState +import org.piramalswasthya.sakhi.database.room.dao.VLFDao +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao +import org.piramalswasthya.sakhi.model.AHDCache +import org.piramalswasthya.sakhi.model.DewormingCache +import org.piramalswasthya.sakhi.model.ORSCampaignCache +import org.piramalswasthya.sakhi.model.PHCReviewMeetingCache +import org.piramalswasthya.sakhi.model.PulsePolioCampaignCache +import org.piramalswasthya.sakhi.model.VHNCCache +import org.piramalswasthya.sakhi.model.VHNDCache +import org.piramalswasthya.sakhi.model.dynamicEntity.filariaaMdaCampaign.FilariaMDACampaignFormResponseJsonEntity +import org.piramalswasthya.sakhi.network.AHDDTO +import org.piramalswasthya.sakhi.network.AmritApiService +import org.piramalswasthya.sakhi.network.DewormingDTO +import org.piramalswasthya.sakhi.network.GetVHNDRequest +import org.piramalswasthya.sakhi.network.PHCReviewDTO +import org.piramalswasthya.sakhi.network.UserDataDTO +import org.piramalswasthya.sakhi.network.VHNCDTO +import org.piramalswasthya.sakhi.network.VHNDDTO +import org.piramalswasthya.sakhi.ui.getFileName +import org.piramalswasthya.sakhi.utils.HelperUtil +import org.piramalswasthya.sakhi.utils.HelperUtil.compressImageToTemp +import timber.log.Timber +import java.io.File +import java.net.SocketTimeoutException +import java.text.SimpleDateFormat +import java.time.LocalDate +import java.time.YearMonth +import java.time.format.DateTimeFormatter +import java.util.Locale +import javax.inject.Inject + + +class VLFRepo @Inject constructor( + private val database: InAppDb, + private val userRepo: UserRepo, + private val preferenceDao: PreferenceDao, + private val tmcNetworkApiService: AmritApiService, + private val vlfDao: VLFDao, + @ApplicationContext private val appContext: Context + +) { + + suspend fun getVHND(id: Int): VHNDCache? { + return withContext(Dispatchers.IO) { + database.vlfDao.getVHND(id) + } + } + + suspend fun saveRecord(vhndCache: VHNDCache) { + withContext(Dispatchers.IO) { + database.vlfDao.saveRecord(vhndCache) + } + } + + var vhndList = vlfDao.getAllVHND() + .map { list -> list.map { it.toVhndDTODTO() } } + + suspend fun getVHNC(id: Int): VHNCCache? { + return withContext(Dispatchers.IO) { + database.vlfDao.getVHNC(id) + } + } + + suspend fun saveRecord(vhncCache: VHNCCache) { + withContext(Dispatchers.IO) { + database.vlfDao.saveRecord(vhncCache) + } + } + + var vhncList = vlfDao.getAllVHNC() + .map { list -> list.map { it.toVhncDTODTO() } } + + + suspend fun getPHC(id: Int): PHCReviewMeetingCache? { + return withContext(Dispatchers.IO) { + database.vlfDao.getPHC(id) + } + } + + suspend fun saveRecord(phcCache: PHCReviewMeetingCache) { + withContext(Dispatchers.IO) { + database.vlfDao.saveRecord(phcCache) + } + } + + var phcList = vlfDao.getAllPHC() + .map { list -> list.map { it.toPHCDTODTO() } } + + + suspend fun getAHD(id: Int): AHDCache? { + return withContext(Dispatchers.IO) { + database.vlfDao.getAHD(id) + } + } + + suspend fun saveAHDRecord(ahdCache: AHDCache) { + withContext(Dispatchers.IO) { + database.vlfDao.saveRecord(ahdCache) + } + } + + var ahdList = vlfDao.getAllAHD() + .map { list -> list.map { it.toAHDCache() } } + + + suspend fun getDeworming(id: Int): DewormingCache? { + return withContext(Dispatchers.IO) { + database.vlfDao.getDeworming(id) + } + } + + suspend fun saveDeworming(dewormingCache: DewormingCache) { + withContext(Dispatchers.IO) { + database.vlfDao.saveRecord(dewormingCache) + } + } + + var dewormingList = vlfDao.getAllDeworming() +// .map { list -> list.map { it.toDTO() } } + .map { list -> list.map { it.toDewormingCache() } } + + suspend fun getPulsePolioCampaign(id: Int): PulsePolioCampaignCache? { + return withContext(Dispatchers.IO) { + database.vlfDao.getPulsePolioCampaign(id) + } + } + + suspend fun savePulsePolioCampaign(pulsePolioCampaignCache: PulsePolioCampaignCache) { + withContext(Dispatchers.IO) { + database.vlfDao.saveRecord(pulsePolioCampaignCache) + } + } + + var pulsePolioCampaignList = vlfDao.getAllPulsePolioCampaign() + .map { list -> list } + + suspend fun getUnsyncedPulsePolioCampaign(): List { + return withContext(Dispatchers.IO) { + database.vlfDao.getPulsePolioCampaign(SyncState.UNSYNCED) ?: emptyList() + } + } + + suspend fun getORSCampaign(id: Int): ORSCampaignCache? { + return withContext(Dispatchers.IO) { + database.vlfDao.getORSCampaign(id) + } + } + + suspend fun saveORSCampaign(orsCampaignCache: ORSCampaignCache) { + withContext(Dispatchers.IO) { + database.vlfDao.saveRecord(orsCampaignCache) + } + } + + var orsCampaignList = vlfDao.getAllORSCampaign() + .map { list -> list } + + suspend fun getUnsyncedORSCampaign(): List { + return withContext(Dispatchers.IO) { + database.vlfDao.getORSCampaign(SyncState.UNSYNCED) ?: emptyList() + } + } + + suspend fun saveORSCampaignToServer(orsCampaignCache: ORSCampaignCache): Boolean { + return withContext(Dispatchers.IO) { + try { + + val formDataJson = orsCampaignCache.formDataJson ?: "" + val formDataObj = try { + JSONObject(formDataJson) + } catch (e: Exception) { + JSONObject() + } + + val fieldsObj = formDataObj.optJSONObject("fields") ?: JSONObject() + val campaignPhotosValue = fieldsObj.opt("campaign_photos") ?: fieldsObj.opt("campaignPhotos") + + val photoUris = when { + campaignPhotosValue is String -> { + try { + Gson().fromJson(campaignPhotosValue as String, Array::class.java).toList() + } catch (e: Exception) { + listOf(campaignPhotosValue.toString()).filter { it.isNotEmpty() } + } + } + campaignPhotosValue is org.json.JSONArray -> { + (0 until (campaignPhotosValue as org.json.JSONArray).length()) + .mapNotNull { campaignPhotosValue.opt(it)?.toString() } + } + else -> emptyList() + } + + val multipartParts = mutableListOf() + + val formDataJsonBody = formDataJson.toRequestBody("application/json".toMediaTypeOrNull()) + multipartParts.add( + MultipartBody.Part.createFormData("formDataJson", null, formDataJsonBody) + ) + + val imageParts = photoUris.mapNotNull { photoData -> + try { + val file: File? = when { + photoData.startsWith("data:image/") || photoData.contains(",") -> { + val base64Data = photoData.substringAfter(",", photoData) + val bytes = android.util.Base64.decode(base64Data, android.util.Base64.DEFAULT) + val tempFile = File.createTempFile("campaign_photo_", ".jpg", appContext.cacheDir) + tempFile.writeBytes(bytes) + tempFile + } + photoData.startsWith("content://") || photoData.startsWith("file://") -> { + val uri = android.net.Uri.parse(photoData) + val name = getFileName(uri, appContext) ?: "campaign_photo" + val mime = appContext.contentResolver.getType(uri) ?: "image/jpeg" + if (mime.startsWith("image/")) { + compressImageToTemp(uri, name, appContext) + } else { + null + } + } + else -> { + val filePath = File(photoData) + if (filePath.exists()) filePath else null + } + } + file?.let { + val mime = "image/jpeg" + val body = it.asRequestBody(mime.toMediaTypeOrNull()) + MultipartBody.Part.createFormData("campaignPhotos", it.name, body) + } + } catch (e: Exception) { + Timber.e(e, "Error processing image: $photoData") + null + } + } + multipartParts.addAll(imageParts) + + val response = tmcNetworkApiService.saveORSCampaignData( + campaignData = multipartParts + ) + + if (response.isSuccessful) { + orsCampaignCache.syncState = SyncState.SYNCED + database.vlfDao.saveRecord(orsCampaignCache) + true + } else { + false + } + } catch (e: Exception) { + Timber.e(e, "Error saving ORS Campaign to server") + false + } + } + } + + suspend fun getORSCampaignFromServer(): Int { + return withContext(Dispatchers.IO) { + val user = preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") + try { + val response = tmcNetworkApiService.getORSCampaignData() + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + val responseStatusCode = jsonObj.getInt("statusCode") + when (responseStatusCode) { + 200 -> { + try { + when (val dataValue = jsonObj.opt("data")) { + is org.json.JSONArray -> { + saveORSCampaignFromServer(dataValue.toString()) + return@withContext 1 + } + + is String -> { + saveORSCampaignFromServer(dataValue) + return@withContext 1 + } + + is JSONObject -> { + saveORSCampaignFromServer(dataValue.toString()) + return@withContext 1 + } + + else -> { + Timber.e("Unexpected data format: ${dataValue?.javaClass}") + return@withContext 0 + } + } + } catch (e: Exception) { + Timber.e(e, "ORS Campaign entries not synced") + return@withContext 0 + } + } + 5002 -> { + if (userRepo.refreshTokenTmc(user.userName, user.password)) { + throw SocketTimeoutException("Refreshed Token!") + } else { + throw IllegalStateException("User Logged out!!") + } + } + else -> { + throw IllegalStateException("$responseStatusCode received") + } + } + } + } + } catch (e: SocketTimeoutException) { + Timber.e("get ORS Campaign data error : $e") + return@withContext getORSCampaignFromServer() + } catch (e: IllegalStateException) { + Timber.e("get ORS Campaign data error : $e") + return@withContext -1 + } + -1 + } + } + + private suspend fun saveORSCampaignFromServer(dataObj: String) { + try { + val jsonArray = org.json.JSONArray(dataObj) + + var savedCount = 0 + for (i in 0 until jsonArray.length()) { + try { + val entryObj = jsonArray.getJSONObject(i) + val id = entryObj.optInt("id", 0) + + val fieldsObj = entryObj.optJSONObject("fields") + val formDataJson = if (fieldsObj != null) { + val formData = org.json.JSONObject() + formData.put("fields", fieldsObj) + formData.toString() + } else { + entryObj.optString("formDataJson", null) + } + + if (formDataJson != null && id > 0) { + val existing = database.vlfDao.getORSCampaign(id) + if (existing == null) { + val cache = ORSCampaignCache( + id = id, + formDataJson = formDataJson, + syncState = SyncState.SYNCED + ) + database.vlfDao.saveRecord(cache) + savedCount++ + Timber.d("Saved new ORS Campaign entry with id: $id") + } else { + // Update existing record if needed + existing.formDataJson = formDataJson + existing.syncState = SyncState.SYNCED + database.vlfDao.saveRecord(existing) + savedCount++ + Timber.d("Updated ORS Campaign entry with id: $id") + } + } else { + Timber.w("Skipping entry with id: $id, formDataJson is null or id is 0") + } + } catch (e: Exception) { + Timber.e(e, "cannot save ORS Campaign entry at index $i due to : $e") + } + } + Timber.d("Saved $savedCount ORS Campaign entries to database") + } catch (e: Exception) { + Timber.e(e, "Error parsing ORS Campaign data: $e") + } + } + + suspend fun savePulsePolioCampaignToServer(pulsePolioCampaignCache: PulsePolioCampaignCache): Boolean { + return withContext(Dispatchers.IO) { + try { + val formDataJson = pulsePolioCampaignCache.formDataJson ?: "" + val formDataObj = try { + JSONObject(formDataJson) + } catch (e: Exception) { + JSONObject() + } + + val fieldsObj = formDataObj.optJSONObject("fields") ?: JSONObject() + val campaignPhotosValue = fieldsObj.opt("campaign_photos") ?: fieldsObj.opt("campaignPhotos") + + val photoUris = when { + campaignPhotosValue is String -> { + try { + Gson().fromJson(campaignPhotosValue as String, Array::class.java).toList() + } catch (e: Exception) { + listOf(campaignPhotosValue.toString()).filter { it.isNotEmpty() } + } + } + campaignPhotosValue is org.json.JSONArray -> { + (0 until (campaignPhotosValue as org.json.JSONArray).length()) + .mapNotNull { campaignPhotosValue.opt(it)?.toString() } + } + else -> emptyList() + } + + val imageParts = photoUris.mapNotNull { photoData -> + try { + val file: File? = when { + photoData.startsWith("data:image/") || photoData.contains(",") -> { + val base64Data = photoData.substringAfter(",", photoData) + val bytes = android.util.Base64.decode(base64Data, android.util.Base64.DEFAULT) + val tempFile = File.createTempFile("campaign_photo_", ".jpg", appContext.cacheDir) + tempFile.writeBytes(bytes) + tempFile + } + photoData.startsWith("content://") || photoData.startsWith("file://") -> { + val uri = android.net.Uri.parse(photoData) + val name = getFileName(uri, appContext) ?: "campaign_photo" + val mime = appContext.contentResolver.getType(uri) ?: "image/jpeg" + if (mime.startsWith("image/")) { + compressImageToTemp(uri, name, appContext) + } else { + null + } + } + + else -> { + val filePath = File(photoData) + if (filePath.exists()) filePath else null + } + } + file?.let { + val mime = "image/jpeg" + val body = it.asRequestBody(mime.toMediaTypeOrNull()) + MultipartBody.Part.createFormData("campaignPhotos", it.name, body) + } + } catch (e: Exception) { + Timber.e(e, "Error processing image: $photoData") + null + } + } + + val multipartParts = mutableListOf() + + val formDataJsonBody = formDataJson.toRequestBody("application/json".toMediaTypeOrNull()) + multipartParts.add( + MultipartBody.Part.createFormData("formDataJson", null, formDataJsonBody) + ) + + multipartParts.addAll(imageParts) + + val response = tmcNetworkApiService.savePulsePolioCampaignData( + campaignData = multipartParts + ) + + if (response.isSuccessful) { + pulsePolioCampaignCache.syncState = SyncState.SYNCED + database.vlfDao.saveRecord(pulsePolioCampaignCache) + true + } else { + false + } + } catch (e: Exception) { + Timber.e(e, "Error saving Pulse Polio Campaign to server") + false + } + } + } + + suspend fun getAllPulsePolioCampaigns(): List { + return database.vlfDao.getAllPulsePolioCampaigns() + } + + suspend fun getAllORSCampaigns(): List { + return database.vlfDao.getAllORSCampaigns() + } + + + suspend fun getPulsePolioCampaignFromServer(): Int { + return withContext(Dispatchers.IO) { + val user = preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") + try { + val response = tmcNetworkApiService.getPulsePolioCampaignData() + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Pull Pulse Polio Campaign data : $responseStatusCode") + when (responseStatusCode) { + 200 -> { + try { + when (val dataValue = jsonObj.opt("data")) { + is org.json.JSONArray -> { + savePulsePolioCampaignFromServer(dataValue.toString()) + return@withContext 1 + } + + is String -> { + savePulsePolioCampaignFromServer(dataValue) + return@withContext 1 + } + + is JSONObject -> { + savePulsePolioCampaignFromServer(dataValue.toString()) + return@withContext 1 + } + + else -> { + Timber.e("Unexpected data format: ${dataValue?.javaClass}") + return@withContext 0 + } + } + } catch (e: Exception) { + Timber.e(e, "Pulse Polio Campaign entries not synced") + return@withContext 0 + } + } + 5002 -> { + if (userRepo.refreshTokenTmc(user.userName, user.password)) { + throw SocketTimeoutException("Refreshed Token!") + } else { + throw IllegalStateException("User Logged out!!") + } + } + else -> { + throw IllegalStateException("$responseStatusCode received") + } + } + } + } + } catch (e: SocketTimeoutException) { + Timber.e("get Pulse Polio Campaign data error : $e") + return@withContext getPulsePolioCampaignFromServer() + } catch (e: IllegalStateException) { + Timber.e("get Pulse Polio Campaign data error : $e") + return@withContext -1 + } + -1 + } + } + + private suspend fun savePulsePolioCampaignFromServer(dataObj: String) { + try { + val jsonArray = org.json.JSONArray(dataObj) + + var savedCount = 0 + for (i in 0 until jsonArray.length()) { + try { + val entryObj = jsonArray.getJSONObject(i) + val id = entryObj.optInt("id", 0) + + val fieldsObj = entryObj.optJSONObject("fields") + val formDataJson = if (fieldsObj != null) { + val formData = JSONObject() + formData.put("fields", fieldsObj) + formData.toString() + } else { + entryObj.optString("formDataJson", null) + } + + if (formDataJson != null && id > 0) { + val existing = database.vlfDao.getPulsePolioCampaign(id) + if (existing == null) { + val cache = PulsePolioCampaignCache( + id = id, + formDataJson = formDataJson, + syncState = SyncState.SYNCED + ) + database.vlfDao.saveRecord(cache) + savedCount++ + Timber.d("Saved new Pulse Polio Campaign entry with id: $id") + } else { + existing.formDataJson = formDataJson + existing.syncState = SyncState.SYNCED + database.vlfDao.saveRecord(existing) + savedCount++ + Timber.d("Updated Pulse Polio Campaign entry with id: $id") + } + } else { + Timber.w("Skipping entry with id: $id, formDataJson is null or id is 0") + } + } catch (e: Exception) { + Timber.e(e, "cannot save Pulse Polio Campaign entry at index $i due to : $e") + } + } + Timber.d("Saved $savedCount Pulse Polio Campaign entries to database") + } catch (e: Exception) { + Timber.e(e, "Error parsing Pulse Polio Campaign data: $e") + } + } + + + suspend fun getFilariaMdaCampaign(id: Int): FilariaMDACampaignFormResponseJsonEntity? { + return withContext(Dispatchers.IO) { + database.vlfDao.getFilariaMdaCampaign(id) + } + } + + suspend fun saveFilariaMdaCampaign(filariaMDaCampaignCache: FilariaMDACampaignFormResponseJsonEntity) { + withContext(Dispatchers.IO) { + database.vlfDao.saveRecord(filariaMDaCampaignCache) + } + } + + suspend fun getUnsyncedFilariaMdaCampaign(): List { + return withContext(Dispatchers.IO) { + database.vlfDao.getFilariaMdaCampaign(SyncState.UNSYNCED) ?: emptyList() + } + } + + + fun getLastSubmissionDate(formId: String): Flow { + Log.d("OverdueCheck", "Is overdue: $formId") + return when (formId) { + + "vhnd" -> vlfDao.getLastVHNDSubmissionDate() + "vhnc" -> vlfDao.getLastVHNCSubmissionDate() + "phc_review" -> vlfDao.getLastPHCSubmissionDate() + "ahd" -> vlfDao.getLastAHDSubmissionDate() + "deworming" -> vlfDao.getLastDewormingSubmissionDate() + "pulse_polio_campaign_form" -> { + vlfDao.getAllPulsePolioCampaignForDate().map { list -> + list.mapNotNull { cache -> + try { + val formDataJson = cache.formDataJson ?: return@mapNotNull null + val jsonObj = JSONObject(formDataJson) + val fieldsObj = jsonObj.optJSONObject("fields") ?: return@mapNotNull null + fieldsObj.optString("campaign_date").takeIf { it.isNotEmpty() } + } catch (e: Exception) { + null + } + }.maxOrNull() + } + } + "ors_campaign_form" -> { + vlfDao.getAllORSCampaignForDate().map { list -> + list.mapNotNull { cache -> + try { + val formDataJson = cache.formDataJson ?: return@mapNotNull null + val jsonObj = JSONObject(formDataJson) + val fieldsObj = jsonObj.optJSONObject("fields") ?: return@mapNotNull null + fieldsObj.optString("campaign_date").takeIf { it.isNotEmpty() } + } catch (e: Exception) { + null + } + }.maxOrNull() + } + } + else -> flowOf(null) // Return null for unknown formIds + } + } + + // RECORD-LEVEL ISOLATION: Coordinator always returns true so the + // WorkManager worker succeeds. Each sub-method handles its own failures + // independently — failed records stay UNSYNCED and retry on next sync cycle. + suspend fun pushUnSyncedRecords(): Boolean { + val vlfVHNDResult = pushUnSyncedRecordsVHND() + val vlfVHNCResult = pushUnSyncedRecordsVHNC() + val vlfPHCResult = pushUnSyncedRecordsPHC() + val vlfAHDResult = pushUnSyncedRecordsAHD() + val vlfDewormingResult = pushUnSyncedRecordsDeworming() + Timber.d("VLF push results: VHND=$vlfVHNDResult, VHNC=$vlfVHNCResult, PHC=$vlfPHCResult, AHD=$vlfAHDResult, Deworming=$vlfDewormingResult") + // Worker succeeds — failed records stay UNSYNCED for next cycle + return true + } + + //...................for VHND......................................... + private fun saveVHND(dataObj: String) { + val requestDTO = Gson().fromJson(dataObj, JsonObject::class.java) + val entries = requestDTO.getAsJsonArray("entries") + for (dto in entries) { + try { + val entry = Gson().fromJson(dto.toString(), VHNDDTO::class.java) + entry.vhndDate?.let { + val vhndCache = database + .vlfDao.getVHND(entry.id) + if (vhndCache == null) { + database.vlfDao.saveRecord(entry.toCache()) + } + } + } catch (e: java.lang.Exception) { + Timber.d("cannot save entry $dto due to : $e") + } + } + } + + suspend fun getVHNDFromServer(): Int { + return withContext(Dispatchers.IO) { + val user = + preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") + try { + val response = tmcNetworkApiService.getVLFData( + GetVHNDRequest( + "VHND", + user.userId, + + ) + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + + val errorMessage = jsonObj.optString("errorMessage", "") + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Pull from amrit hrp assess data : $responseStatusCode") + when (responseStatusCode) { + 200 -> { + try { + val dataObj = jsonObj.getString("data") + saveVHND(dataObj) + } catch (e: Exception) { + Timber.d("HRP Assess entries not synced $e") + return@withContext 0 + } + + return@withContext 1 + } + + 5002 -> { + if (userRepo.refreshTokenTmc( + user.userName, user.password + ) + ) throw SocketTimeoutException("Refreshed Token!") + else throw IllegalStateException("User Logged out!!") + } + + 5000 -> { + if (errorMessage == "No record found") return@withContext 0 + } + + else -> { + throw IllegalStateException("$responseStatusCode received, dont know what todo!?") + } + } + } + } + + } catch (e: SocketTimeoutException) { + Timber.e("get data error : $e") + return@withContext getVHNDFromServer() + + } catch (e: java.lang.IllegalStateException) { + Timber.e("get data error : $e") + return@withContext -1 + } + -1 + } + } + + private fun updateSyncStatusVHND(entities: List?) { + entities?.let { + entities.forEach { + database.vlfDao.saveRecord(it) + } + } + } + + // RECORD-LEVEL ISOLATION: Records now sent in chunks of 20. + // Previously all-or-nothing batch. One bad chunk doesn't affect others. + // Also removed dangerous recursive retry on SocketTimeoutException. + private suspend fun pushUnSyncedRecordsVHND(): Int { + return withContext(Dispatchers.IO) { + val user = preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") + + val entities = database.vlfDao.getVHND(SyncState.UNSYNCED) + if (entities.isNullOrEmpty()) return@withContext 1 + + val CHUNK_SIZE = 20 + val chunks = entities.chunked(CHUNK_SIZE) + var successCount = 0 + var failCount = 0 + + for (chunk in chunks) { + try { + val chunkDtos = chunk.map { cache -> cache.toVhndDTODTO() } + + val response = tmcNetworkApiService.saveVHNDData( + UserDataDTO(userId = user.userId, entries = chunkDtos) + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Push to Amrit VLF VHND chunk: $responseStatusCode") + when (responseStatusCode) { + 200 -> { + chunk.forEach { it.syncState = SyncState.SYNCED } + updateSyncStatusVHND(chunk) + successCount += chunk.size + } + 401, 5002 -> { + if (userRepo.refreshTokenTmc(user.userName, user.password)) { + Timber.d("Token refreshed, VLF VHND chunk will retry next cycle") + } + failCount += chunk.size + } + else -> { + Timber.e("VLF VHND chunk failed with statusCode: $responseStatusCode") + failCount += chunk.size + } + } + } + } else { + Timber.e("VLF VHND chunk HTTP error: $statusCode") + failCount += chunk.size + } + } catch (e: Exception) { + Timber.e(e, "VLF VHND chunk push failed: ${chunk.size} records") + failCount += chunk.size + } + } + + Timber.d("VLF VHND push complete: $successCount succeeded, $failCount failed out of ${entities.size}") + return@withContext 1 + } + } + + //............................................................................. + //...................for VHNC......................................... + private fun saveVHNC(dataObj: String) { + val requestDTO = Gson().fromJson(dataObj, JsonObject::class.java) + val entries = requestDTO.getAsJsonArray("entries") + for (dto in entries) { + try { + val entry = Gson().fromJson(dto.toString(), VHNCDTO::class.java) + entry.vhncDate?.let { + val vhncCache = database + .vlfDao.getVHNC(entry.id) + if (vhncCache == null) { + database.vlfDao.saveRecord(entry.toCache()) + } + } + } catch (e: java.lang.Exception) { + Timber.d("cannot save entry $dto due to : $e") + } + } + } + + suspend fun getVHNCFromServer(): Int { + return withContext(Dispatchers.IO) { + val user = + preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") + try { + val response = tmcNetworkApiService.getVLFData( + GetVHNDRequest( + "VHNC", + user.userId, + + ) + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + + val errorMessage = jsonObj.optString("errorMessage", "") + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Pull from amrit hrp assess data : $responseStatusCode") + when (responseStatusCode) { + 200 -> { + try { + val dataObj = jsonObj.getString("data") + saveVHNC(dataObj) + } catch (e: Exception) { +// Timber.d("HRP Assess entries not synced $e") + return@withContext 0 + } + + return@withContext 1 + } + + 5002 -> { + if (userRepo.refreshTokenTmc( + user.userName, user.password + ) + ) throw SocketTimeoutException("Refreshed Token!") + else throw IllegalStateException("User Logged out!!") + } + + 5000 -> { + if (errorMessage == "No record found") return@withContext 0 + } + + else -> { + throw IllegalStateException("$responseStatusCode received, dont know what todo!?") + } + } + } + } + + } catch (e: SocketTimeoutException) { + Timber.e("get data error : $e") + return@withContext getVHNCFromServer() + + } catch (e: java.lang.IllegalStateException) { + Timber.e("get data error : $e") + return@withContext -1 + } + -1 + } + } + + private fun updateSyncStatusVHNC(entities: List?) { + entities?.let { + entities.forEach { + database.vlfDao.saveRecord(it) + } + } + } + + // RECORD-LEVEL ISOLATION: Records now sent in chunks of 20. + // Previously all-or-nothing batch. One bad chunk doesn't affect others. + // Also removed dangerous recursive retry on SocketTimeoutException. + private suspend fun pushUnSyncedRecordsVHNC(): Int { + return withContext(Dispatchers.IO) { + val user = preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") + + val entities = database.vlfDao.getVHNC(SyncState.UNSYNCED) + if (entities.isNullOrEmpty()) return@withContext 1 + + val CHUNK_SIZE = 20 + val chunks = entities.chunked(CHUNK_SIZE) + var successCount = 0 + var failCount = 0 + + for (chunk in chunks) { + try { + val chunkDtos = chunk.map { cache -> cache.toVhncDTODTO() } + + val response = tmcNetworkApiService.saveVHNCData( + UserDataDTO(userId = user.userId, entries = chunkDtos) + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Push to Amrit VLF VHNC chunk: $responseStatusCode") + when (responseStatusCode) { + 200 -> { + chunk.forEach { it.syncState = SyncState.SYNCED } + updateSyncStatusVHNC(chunk) + successCount += chunk.size + } + 401, 5002 -> { + if (userRepo.refreshTokenTmc(user.userName, user.password)) { + Timber.d("Token refreshed, VLF VHNC chunk will retry next cycle") + } + failCount += chunk.size + } + else -> { + Timber.e("VLF VHNC chunk failed with statusCode: $responseStatusCode") + failCount += chunk.size + } + } + } + } else { + Timber.e("VLF VHNC chunk HTTP error: $statusCode") + failCount += chunk.size + } + } catch (e: Exception) { + Timber.e(e, "VLF VHNC chunk push failed: ${chunk.size} records") + failCount += chunk.size + } + } + + Timber.d("VLF VHNC push complete: $successCount succeeded, $failCount failed out of ${entities.size}") + return@withContext 1 + } + } + + //............................................................................. + + //...................for PHC......................................... + private fun savePHC(dataObj: String) { + val requestDTO = Gson().fromJson(dataObj, JsonObject::class.java) + val entries = requestDTO.getAsJsonArray("entries") + for (dto in entries) { + try { + val entry = Gson().fromJson(dto.toString(), PHCReviewDTO::class.java) + entry.phcReviewDate?.let { + val phcCache = database.vlfDao.getPHC(entry.id) + if (phcCache == null) { + database.vlfDao.saveRecord(entry.toCache()) + } + } + } catch (e: java.lang.Exception) { + Timber.d("cannot save entry $dto due to : $e") + } + } + } + + suspend fun getPHCFromServer(): Int { + return withContext(Dispatchers.IO) { + val user = + preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") + try { + val response = tmcNetworkApiService.getVLFData( + GetVHNDRequest( + "PHC", + user.userId, + + ) + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + + val errorMessage = jsonObj.optString("errorMessage", "") + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Pull from amrit hrp assess data : $responseStatusCode") + when (responseStatusCode) { + 200 -> { + try { + val dataObj = jsonObj.getString("data") + savePHC(dataObj) + } catch (e: Exception) { +// Timber.d("HRP Assess entries not synced $e") + return@withContext 0 + } + + return@withContext 1 + } + + 5002 -> { + if (userRepo.refreshTokenTmc( + user.userName, user.password + ) + ) throw SocketTimeoutException("Refreshed Token!") + else throw IllegalStateException("User Logged out!!") + } + + 5000 -> { + if (errorMessage == "No record found") return@withContext 0 + } + + else -> { + throw IllegalStateException("$responseStatusCode received, dont know what todo!?") + } + } + } + } + + } catch (e: SocketTimeoutException) { + Timber.e("get data error : $e") + return@withContext getPHCFromServer() + + } catch (e: java.lang.IllegalStateException) { + Timber.e("get data error : $e") + return@withContext -1 + } + -1 + } + } + + private fun updateSyncStatusPHC(entities: List?) { + entities?.let { + entities.forEach { + database.vlfDao.saveRecord(it) + } + } + } + + // RECORD-LEVEL ISOLATION: Records now sent in chunks of 20. + // Previously all-or-nothing batch. One bad chunk doesn't affect others. + // Also removed dangerous recursive retry on SocketTimeoutException. + private suspend fun pushUnSyncedRecordsPHC(): Int { + return withContext(Dispatchers.IO) { + val user = preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") + + val entities = database.vlfDao.getPHC(SyncState.UNSYNCED) + if (entities.isNullOrEmpty()) return@withContext 1 + + val CHUNK_SIZE = 20 + val chunks = entities.chunked(CHUNK_SIZE) + var successCount = 0 + var failCount = 0 + + for (chunk in chunks) { + try { + val chunkDtos = chunk.map { cache -> cache.toPHCDTODTO() } + + val response = tmcNetworkApiService.savePHCData( + UserDataDTO(userId = user.userId, entries = chunkDtos) + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Push to Amrit VLF PHC chunk: $responseStatusCode") + when (responseStatusCode) { + 200 -> { + chunk.forEach { it.syncState = SyncState.SYNCED } + updateSyncStatusPHC(chunk) + successCount += chunk.size + } + 401, 5002 -> { + if (userRepo.refreshTokenTmc(user.userName, user.password)) { + Timber.d("Token refreshed, VLF PHC chunk will retry next cycle") + } + failCount += chunk.size + } + else -> { + Timber.e("VLF PHC chunk failed with statusCode: $responseStatusCode") + failCount += chunk.size + } + } + } + } else { + Timber.e("VLF PHC chunk HTTP error: $statusCode") + failCount += chunk.size + } + } catch (e: Exception) { + Timber.e(e, "VLF PHC chunk push failed: ${chunk.size} records") + failCount += chunk.size + } + } + + Timber.d("VLF PHC push complete: $successCount succeeded, $failCount failed out of ${entities.size}") + return@withContext 1 + } + } + + //............................................................................. + + + //...................for AHD......................................... + suspend fun saveAHD(dataObj: String) { + val requestDTO = Gson().fromJson(dataObj, JsonObject::class.java) + val entries = requestDTO.getAsJsonArray("entries") + for (dto in entries) { + try { + val entry = Gson().fromJson(dto.toString(), AHDDTO::class.java) + entry.ahdDate?.let { + val ahdDate = database.vlfDao.getAHD(entry.id) + if (ahdDate == null) { + database.vlfDao.saveRecord(entry.toCache()) + } + } + } catch (e: java.lang.Exception) { + Timber.d("cannot save entry $dto due to : $e") + } + } + } + + suspend fun getAHDFromServer(): Int { + return withContext(Dispatchers.IO) { + val user = + preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") + try { + val response = tmcNetworkApiService.getVLFData( + GetVHNDRequest( + "AHD", + user.userId, + + ) + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + + val errorMessage = jsonObj.optString("errorMessage", "") + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Pull from amrit hrp assess data : $responseStatusCode") + when (responseStatusCode) { + 200 -> { + try { + val dataObj = jsonObj.getString("data") + saveAHD(dataObj) + } catch (e: Exception) { +// Timber.d("HRP Assess entries not synced $e") + return@withContext 0 + } + + return@withContext 1 + } + + 5002 -> { + if (userRepo.refreshTokenTmc( + user.userName, user.password + ) + ) throw SocketTimeoutException("Refreshed Token!") + else throw IllegalStateException("User Logged out!!") + } + + 5000 -> { + if (errorMessage == "No record found") return@withContext 0 + } + + else -> { + throw IllegalStateException("$responseStatusCode received, dont know what todo!?") + } + } + } + } + + } catch (e: SocketTimeoutException) { + Timber.e("get data error : $e") + return@withContext getAHDFromServer() + + } catch (e: java.lang.IllegalStateException) { + Timber.e("get data error : $e") + return@withContext -1 + } + -1 + } + } + + private suspend fun updateSyncStatusAHD(entities: List?) { + entities?.let { + entities.forEach { + database.vlfDao.saveRecord(it) + } + } + } + + // RECORD-LEVEL ISOLATION: Records now sent in chunks of 20. + // Previously all-or-nothing batch. One bad chunk doesn't affect others. + // Also removed dangerous recursive retry on SocketTimeoutException. + private suspend fun pushUnSyncedRecordsAHD(): Int { + return withContext(Dispatchers.IO) { + val user = preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") + + val entities = database.vlfDao.getAHD(SyncState.UNSYNCED) + if (entities.isNullOrEmpty()) return@withContext 1 + + val CHUNK_SIZE = 20 + val chunks = entities.chunked(CHUNK_SIZE) + var successCount = 0 + var failCount = 0 + + for (chunk in chunks) { + try { + val chunkDtos = chunk.map { cache -> cache.toDTO() } + + val response = tmcNetworkApiService.saveAHDData( + UserDataDTO(userId = user.userId, entries = chunkDtos) + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Push to Amrit VLF AHD chunk: $responseStatusCode") + when (responseStatusCode) { + 200 -> { + chunk.forEach { it.syncState = SyncState.SYNCED } + updateSyncStatusAHD(chunk) + successCount += chunk.size + } + 401, 5002 -> { + if (userRepo.refreshTokenTmc(user.userName, user.password)) { + Timber.d("Token refreshed, VLF AHD chunk will retry next cycle") + } + failCount += chunk.size + } + else -> { + Timber.e("VLF AHD chunk failed with statusCode: $responseStatusCode") + failCount += chunk.size + } + } + } + } else { + Timber.e("VLF AHD chunk HTTP error: $statusCode") + failCount += chunk.size + } + } catch (e: Exception) { + Timber.e(e, "VLF AHD chunk push failed: ${chunk.size} records") + failCount += chunk.size + } + } + + Timber.d("VLF AHD push complete: $successCount succeeded, $failCount failed out of ${entities.size}") + return@withContext 1 + } + } + + //............................................................................. + //...................for Deworming......................................... + suspend fun saveDeworming(dataObj: String) { + val requestDTO = Gson().fromJson(dataObj, JsonObject::class.java) + val entries = requestDTO.getAsJsonArray("entries") + for (dto in entries) { + try { + val entry = Gson().fromJson(dto.toString(), DewormingDTO::class.java) + entry.dewormingDone?.let { + val dewormingDate = database.vlfDao.getDeworming(entry.id) + if (dewormingDate == null) { + database.vlfDao.saveRecord(entry.toCache()) + } + } + } catch (e: java.lang.Exception) { + Timber.d("cannot save entry $dto due to : $e") + } + } + } + + suspend fun getDewormingFromServer(): Int { + return withContext(Dispatchers.IO) { + val user = + preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") + try { + val response = tmcNetworkApiService.getVLFData( + GetVHNDRequest( + "Deworming", + user.userId, + + ) + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + + val errorMessage = jsonObj.optString("errorMessage", "") + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Pull from amrit hrp assess data : $responseStatusCode") + when (responseStatusCode) { + 200 -> { + try { + val dataObj = jsonObj.getString("data") + saveDeworming(dataObj) + } catch (e: Exception) { + return@withContext 0 + } + + return@withContext 1 + } + + 5002 -> { + if (userRepo.refreshTokenTmc( + user.userName, user.password + ) + ) throw SocketTimeoutException("Refreshed Token!") + else throw IllegalStateException("User Logged out!!") + } + + 5000 -> { + if (errorMessage == "No record found") return@withContext 0 + } + + else -> { + throw IllegalStateException("$responseStatusCode received, dont know what todo!?") + } + } + } + } + + } catch (e: SocketTimeoutException) { + Timber.e("get data error : $e") + return@withContext getDewormingFromServer() + + } catch (e: java.lang.IllegalStateException) { + Timber.e("get data error : $e") + return@withContext -1 + } + -1 + } + } + + private suspend fun updateSyncStatusDeworming(entities: List?) { + entities?.let { + entities.forEach { + database.vlfDao.saveRecord(it) + } + } + } + + // RECORD-LEVEL ISOLATION: Records now sent in chunks of 20. + // Previously all-or-nothing batch. One bad chunk doesn't affect others. + // Also removed dangerous recursive retry on SocketTimeoutException. + private suspend fun pushUnSyncedRecordsDeworming(): Int { + return withContext(Dispatchers.IO) { + val user = preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") + + val entities = database.vlfDao.getDeworming(SyncState.UNSYNCED) + if (entities.isNullOrEmpty()) return@withContext 1 + + val CHUNK_SIZE = 20 + val chunks = entities.chunked(CHUNK_SIZE) + var successCount = 0 + var failCount = 0 + + for (chunk in chunks) { + try { + val chunkDtos = chunk.map { cache -> cache.toDTO() } + + val response = tmcNetworkApiService.saveDewormingData( + UserDataDTO(userId = user.userId, entries = chunkDtos) + ) + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Push to Amrit VLF Deworming chunk: $responseStatusCode") + when (responseStatusCode) { + 200 -> { + chunk.forEach { it.syncState = SyncState.SYNCED } + updateSyncStatusDeworming(chunk) + successCount += chunk.size + } + 401, 5002 -> { + if (userRepo.refreshTokenTmc(user.userName, user.password)) { + Timber.d("Token refreshed, VLF Deworming chunk will retry next cycle") + } + failCount += chunk.size + } + else -> { + Timber.e("VLF Deworming chunk failed with statusCode: $responseStatusCode") + failCount += chunk.size + } + } + } + } else { + Timber.e("VLF Deworming chunk HTTP error: $statusCode") + failCount += chunk.size + } + } catch (e: Exception) { + Timber.e(e, "VLF Deworming chunk push failed: ${chunk.size} records") + failCount += chunk.size + } + } + + Timber.d("VLF Deworming push complete: $successCount succeeded, $failCount failed out of ${entities.size}") + return@withContext 1 + } + } + + //............................................................................. + + + @RequiresApi(Build.VERSION_CODES.O) + fun isFormFilledForCurrentMonth(): Flow> { + + val current = YearMonth.now() + + val vhnd = vlfDao.getAllVHND() + val vhnc = vlfDao.getAllVHNC() + val phc = vlfDao.getAllPHC() + val ahd = vlfDao.getAllAHD() + val deworming = vlfDao.countDewormingInLastSixMonths() + + val formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy", Locale.ENGLISH) + + return combine(vhnd, vhnc, phc, ahd, deworming) { vhndList, vhncList, phcList, ahdList, dewormingCount -> + + fun isInCurrentMonth(dateStr: String?): Boolean { + return try { + val date = LocalDate.parse(dateStr, formatter) + YearMonth.from(date) == current + } catch (e: Exception) { + false + } + } + + mapOf( + "VHND" to vhndList.any { isInCurrentMonth(it.vhndDate) }, + "VHNC" to vhncList.any { isInCurrentMonth(it.vhncDate) }, + "PHC" to phcList.any { isInCurrentMonth(it.phcReviewDate) }, + "AHD" to ahdList.any { isInCurrentMonth(it.ahdDate) }, + "DEWORMING" to (dewormingCount > 0) + ) + } + } + suspend fun getFilariaMdaCampaignFromServer(): Int { + return withContext(Dispatchers.IO) { + val user = preferenceDao.getLoggedInUser() + ?: throw IllegalStateException("No user logged in!!") + try { + val response = tmcNetworkApiService.getFilariaMdaCampaign() + val statusCode = response.code() + if (statusCode == 200) { + val responseString = response.body()?.string() + if (responseString != null) { + val jsonObj = JSONObject(responseString) + val responseStatusCode = jsonObj.getInt("statusCode") + Timber.d("Pull Filaria Mda Campaign data : $responseStatusCode") + when (responseStatusCode) { + 200 -> { + try { + when (val dataValue = jsonObj.opt("data")) { + is JSONArray -> { + savefilariaMdaCampaignToServer(dataValue.toString()) + return@withContext if (dataValue.length() > 0) 1 else 0 + + } + + is String -> { + val element = JsonParser.parseString(dataValue) + val array = JsonArray() + + if (element.isJsonArray) { + savefilariaMdaCampaignToServer(element.toString()) + } else if (element.isJsonObject) { + array.add(element) + savefilariaMdaCampaignToServer(array.toString()) + } + + return@withContext 1 + } + + is JSONObject -> { + val array = JSONArray().apply { put(dataValue) } + savefilariaMdaCampaignToServer(array.toString()) + return@withContext 1 + } + + else -> { + Timber.e("Unexpected data format: ${dataValue?.javaClass}") + return@withContext 0 + } + } + } catch (e: Exception) { + Timber.e(e, "Filaria MDA Campaign entries not synced") + return@withContext 0 + } + } + 5002 -> { + if (userRepo.refreshTokenTmc(user.userName, user.password)) { + throw SocketTimeoutException("Refreshed Token!") + } else { + throw IllegalStateException("User Logged out!!") + } + } + else -> { + throw IllegalStateException("$responseStatusCode received") + } + } + } + } + } catch (e: SocketTimeoutException) { + Timber.e("get Filaria Mda Campaign data error : $e") + return@withContext getFilariaMdaCampaignFromServer() + } catch (e: IllegalStateException) { + Timber.e("get Filaria Mda Campaign data error : $e") + return@withContext -1 + } + -1 + } + } + + + suspend fun saveMdaFilariaCampaignToServer(filariaMdaCache: FilariaMDACampaignFormResponseJsonEntity): Boolean { + return withContext(Dispatchers.IO) { + try { + val formDataJson = filariaMdaCache.formDataJson ?: "" + val formDataObj = try { + JSONObject(formDataJson) + } catch (e: Exception) { + JSONObject() + } + + val fieldsObj = formDataObj.optJSONObject("fields") ?: JSONObject() + val campaignPhotosValue = fieldsObj.opt("mda_photos") ?: fieldsObj.opt("mda_photos") + + val photoUris = when { + campaignPhotosValue is String -> { + try { + Gson().fromJson(campaignPhotosValue as String, Array::class.java).toList() + } catch (e: Exception) { + listOf(campaignPhotosValue.toString()).filter { it.isNotEmpty() } + } + } + campaignPhotosValue is org.json.JSONArray -> { + (0 until (campaignPhotosValue as org.json.JSONArray).length()) + .mapNotNull { campaignPhotosValue.opt(it)?.toString() } + } + else -> emptyList() + } + + val imageParts = photoUris.mapNotNull { photoData -> + try { + val file: File? = when { + photoData.startsWith("data:image/") || photoData.contains(",") -> { + val base64Data = photoData.substringAfter(",", photoData) + val bytes = android.util.Base64.decode(base64Data, android.util.Base64.DEFAULT) + val tempFile = File.createTempFile("mda_photo_", ".jpg", appContext.cacheDir) + tempFile.writeBytes(bytes) + tempFile + } + photoData.startsWith("content://") || photoData.startsWith("file://") -> { + val uri = android.net.Uri.parse(photoData) + val name = HelperUtil.getFileName(uri, appContext) ?: "mda_photos" + val mime = appContext.contentResolver.getType(uri) ?: "image/jpeg" + if (mime.startsWith("image/")) { + compressImageToTemp(uri, name, appContext) + } else { + null + } + } + + else -> { + val filePath = File(photoData) + if (filePath.exists()) filePath else null + } + } + file?.let { + val mime = "image/jpeg" + val body = it.asRequestBody(mime.toMediaTypeOrNull()) + MultipartBody.Part.createFormData("mda_photos", it.name, body) + } + } catch (e: Exception) { + Timber.e(e, "Error processing image: $photoData") + null + } + } + + val multipartParts = mutableListOf() + + val formDataJsonBody = formDataJson.toRequestBody("application/json".toMediaTypeOrNull()) + multipartParts.add( + MultipartBody.Part.createFormData("formDataJson", null, formDataJsonBody) + ) + + multipartParts.addAll(imageParts) + + val response = tmcNetworkApiService.saveFilariaMdaCampaign( + campaignData = multipartParts + ) + + if (response.isSuccessful) { + filariaMdaCache.syncState = SyncState.SYNCED + database.vlfDao.saveRecord(filariaMdaCache) + true + } else { + false + } + } catch (e: Exception) { + Timber.e(e, "Error saving Filaria MDA Campaign to server") + false + } + } + } + + + suspend fun savefilariaMdaCampaignToServer(dataObj: String) { + + val jsonArray = Gson().fromJson(dataObj, JsonArray::class.java) + + for (dto in jsonArray) { + try { + val entry = dto.asJsonObject + + val id = entry.get("id")?.takeIf { !it.isJsonNull }?.asInt ?: 0 + + val fieldsObj = entry.getAsJsonObject("fields") + val startDate = fieldsObj?.get("start_date")?.asString + + if (fieldsObj != null) { + + val formDataJson = JsonObject().apply { + add("fields", fieldsObj) + }.toString() + + val existing = database.vlfDao.getFilariaMdaCampaign(id) + if (existing == null) { + val cache = FilariaMDACampaignFormResponseJsonEntity( + id = id, + formDataJson = formDataJson, + visitDate = startDate ?: "", + visitYear = toMonthKey(startDate), + formId = "LF_MDA_CAMPAIGN", + version = 1, + isSynced = true, + syncState = SyncState.SYNCED + ) + database.vlfDao.saveRecord(cache) + } + } + + } catch (e: Exception) { + Timber.e(e, "cannot save Filaria MDA Campaign entry $dto") + } + } + } + + private fun toMonthKey(dateStr: String?): String { + if (dateStr.isNullOrBlank()) return "" + val inputs = listOf("dd-MM-yyyy", "yyyy-MM-dd") + val out = SimpleDateFormat("yyyy", Locale.ENGLISH) + for (fmt in inputs) { + try { + val d = SimpleDateFormat(fmt, Locale.ENGLISH).parse(dateStr) + if (d != null) return out.format(d) + } catch (_: Exception) {} + } + return try { + if (Regex("\\d{2}-\\d{2}-\\d{4}").matches(dateStr)) { + val yyyy = dateStr.substring(6, 10) + val mm = dateStr.substring(3, 5) + "$yyyy-$mm" + } else "" + } catch (_: Exception) { "" } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/dynamicRepo/BenIfaFormRepository.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/dynamicRepo/BenIfaFormRepository.kt new file mode 100644 index 000000000..402bc5832 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/dynamicRepo/BenIfaFormRepository.kt @@ -0,0 +1,250 @@ +package org.piramalswasthya.sakhi.repositories.dynamicRepo + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.json.JSONObject +import org.piramalswasthya.sakhi.database.room.InAppDb +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao +import org.piramalswasthya.sakhi.helpers.dynamicMapper.FormSubmitRequestMapper +import org.piramalswasthya.sakhi.model.BottleItem +import org.piramalswasthya.sakhi.model.dynamicEntity.FormSchemaDto +import org.piramalswasthya.sakhi.model.dynamicEntity.FormSchemaEntity +import org.piramalswasthya.sakhi.model.dynamicEntity.ben_ifa.BenIfaFormResponseJsonEntity +import org.piramalswasthya.sakhi.model.dynamicModel.HBNCVisitListResponse +import org.piramalswasthya.sakhi.model.dynamicModel.HBNCVisitRequest +import org.piramalswasthya.sakhi.model.dynamicModel.HBNCVisitResponse +import org.piramalswasthya.sakhi.network.AmritApiService +import org.piramalswasthya.sakhi.utils.dynamicFormConstants.FormConstants +import java.text.SimpleDateFormat +import java.util.* +import javax.inject.Inject +import javax.inject.Singleton +import retrofit2.Response +import javax.inject.Named + +@Singleton +class BenIfaFormRepository @Inject constructor( + @ApplicationContext private val context: Context, + @Named("gsonAmritApi") private val amritApiService: AmritApiService, + private val pref: PreferenceDao, + private val db: InAppDb +) { + private val formSchemaDao = db.formSchemaDao() + private val jsonResponseDao = db.formResponseJsonDaoBenIfa() + + suspend fun getFormSchema(formId: String): FormSchemaDto? = withContext(Dispatchers.IO) { + var result: FormSchemaDto? = null + + try { + val response = amritApiService.fetchFormSchema(formId,pref.getCurrentLanguage().symbol) + + if (response.isSuccessful) { + val apiResponse = response.body() + if (apiResponse?.success == true) { + val apiSchema = apiResponse.data + if (apiSchema != null) { + val localSchema = getSavedSchema(apiSchema.formId) + if (localSchema == null || localSchema.version < apiSchema.version) { + saveFormSchemaToDb(apiSchema) + } + result = apiSchema + } + } + } + } catch (e: Exception) { + // ignored — fallback below will handle + } + + if (result == null) { + val dbSchema = formSchemaDao.getSchema(formId) + result = dbSchema?.let { FormSchemaDto.fromJson(it.schemaJson) } + ?: loadSchemaFromAssets() + } + result + } + + private fun loadSchemaFromAssets(): FormSchemaDto? { + return try { + val json = context.assets.open("hbnc_form_1stday.json") + .bufferedReader().use { it.readText() } + FormSchemaDto.fromJson(json) + } catch (e: Exception) { + null + } + } + + suspend fun saveFormSchemaToDb(schema: FormSchemaDto) { + val entity = FormSchemaEntity( + formId = schema.formId, + formName = schema.formName, + language = pref.getCurrentLanguage().symbol, + version = schema.version, + schemaJson = schema.toJson(), + ) + formSchemaDao.insertOrUpdate(entity) + } + + suspend fun getSavedSchema(formId: String) = formSchemaDao.getSchema(formId) + + suspend fun getSyncedVisitsByRchId(benId: Long): List = + jsonResponseDao.getSyncedVisitsByRchId(benId) + + suspend fun getAllFormVisits(formName: String, request: HBNCVisitRequest): Response { + return amritApiService.getAllEyeSurgeryFormVisits(formName, request) + } + + suspend fun canAddNewVisit(benId: Long): Boolean { + val jsonList = jsonResponseDao.getFormJsonList(benId, FormConstants.IFA_DISTRIBUTION_FORM_ID) + if (jsonList.isEmpty()) return true + + val dateFormat = SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH) + val currentCal = Calendar.getInstance() + val currentMonth = currentCal.get(Calendar.MONTH) + val currentYear = currentCal.get(Calendar.YEAR) + + // Parse all stored visit dates + val anyThisMonth = jsonList.any { json -> + try { + val obj = JSONObject(json) + val dateString = obj.optString("visitDate", "") + val date = dateFormat.parse(dateString) + if (date != null) { + val visitCal = Calendar.getInstance().apply { time = date } + visitCal.get(Calendar.MONTH) == currentMonth && + visitCal.get(Calendar.YEAR) == currentYear + } else false + } catch (e: Exception) { + false + } + } + + return !anyThisMonth + } + + + suspend fun getBottleList(benId: Long, formId: String): List { + val jsonList = jsonResponseDao.getFormJsonList(benId, formId) + + val result = mutableListOf() + val dateFormat = SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH) + + jsonList.forEachIndexed { index, formJson -> + try { + val root = JSONObject(formJson) + val fields = root.optJSONObject("fields") + val date = fields?.optString("visit_date", "-") ?: "-" + val count = fields?.optString("ifa_quantity", "-") ?: "-" + + result.add( + BottleItem( + srNo = index + 1, + bottleNumber = count.toString(), + dateOfProvision = date + ) + ) + } catch (e: Exception) { + e.printStackTrace() + } + } + + return result + .sortedByDescending { item -> + try { + dateFormat.parse(item.dateOfProvision)?.time ?: 0L + } catch (e: Exception) { + 0L + } + } + .take(3) + } + + + suspend fun saveDownloadedVisitList(list: List, formId: String) { + for ((index, item) in list.withIndex()) { + try { + if (item.fields == null) { + continue + } + + val visitDate = item.visitDate ?: "-" + val benId = item.beneficiaryId + val hhId = item.houseHoldId + + val fieldsJson = JSONObject() + item.fields.entrySet().forEach { (key, jsonElement) -> + val value = when { + jsonElement.isJsonNull -> JSONObject.NULL + jsonElement.isJsonPrimitive -> { + val prim = jsonElement.asJsonPrimitive + when { + prim.isBoolean -> prim.asBoolean + prim.isNumber -> prim.asNumber + prim.isString -> prim.asString + else -> prim.asString + } + } + + else -> jsonElement.toString() + } + fieldsJson.put(key, value) + } + + val fullJson = JSONObject().apply { + put("formId", formId) + put("beneficiaryId", benId) + put("houseHoldId", hhId) + put("visitDate", visitDate) + put("fields", fieldsJson) + } + + val entity = BenIfaFormResponseJsonEntity( + benId = benId, + hhId = hhId, + visitDate = visitDate, + formId = formId, + version = 1, + formDataJson = fullJson.toString(), + isSynced = true + ) + + insertOrUpdateFormResponse(entity) + + } catch (e: Exception) { + e.printStackTrace() + } + } + } + + + suspend fun insertOrUpdateFormResponse(entity: BenIfaFormResponseJsonEntity) { + val existing = jsonResponseDao.getFormResponse(entity.benId,entity.visitDate) + val updated = existing?.let { entity.copy(id = it.id) } ?: entity + jsonResponseDao.insertFormResponse(updated) + } + + suspend fun insertFormResponse(entity: BenIfaFormResponseJsonEntity) = + jsonResponseDao.insertFormResponse(entity) + + suspend fun loadFormResponseJson(benId: Long, visitDate: String): String? = + jsonResponseDao.getFormResponse(benId,visitDate)?.formDataJson + + suspend fun getUnsyncedForms(formName: String): List = + jsonResponseDao.getUnsyncedForms(formName) + + suspend fun syncFormToServer(userName: String,formName: String, form: BenIfaFormResponseJsonEntity): Boolean { + return try { + val request = FormSubmitRequestMapper.fromEntity(form,userName) ?: return false + val response = amritApiService.submitEyeSurgeryForm(formName,listOf(request)) + response.isSuccessful + } catch (e: Exception) { + false + } + } + + suspend fun markFormAsSynced(id: Int) { + val timestamp = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH).format(Date()) + jsonResponseDao.markAsSynced(id, timestamp) + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/dynamicRepo/CUFYFormRepository.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/dynamicRepo/CUFYFormRepository.kt new file mode 100644 index 000000000..0baad8879 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/dynamicRepo/CUFYFormRepository.kt @@ -0,0 +1,360 @@ +package org.piramalswasthya.sakhi.repositories.dynamicRepo + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.json.JSONArray +import org.json.JSONObject +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.database.room.InAppDb +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao +import org.piramalswasthya.sakhi.helpers.dynamicMapper.FormSubmitRequestMapper +import org.piramalswasthya.sakhi.model.BottleItem +import org.piramalswasthya.sakhi.model.dynamicEntity.CUFYFormResponseJsonEntity +import org.piramalswasthya.sakhi.model.dynamicEntity.FormSchemaDto +import org.piramalswasthya.sakhi.model.dynamicEntity.FormSchemaEntity +import org.piramalswasthya.sakhi.model.dynamicModel.HBNCVisitListResponse +import org.piramalswasthya.sakhi.model.dynamicModel.HBNCVisitRequest +import org.piramalswasthya.sakhi.model.dynamicModel.HBNCVisitResponse +import org.piramalswasthya.sakhi.network.AmritApiService +import org.piramalswasthya.sakhi.utils.HelperUtil +import org.piramalswasthya.sakhi.utils.dynamicFormConstants.FormConstants +import java.text.SimpleDateFormat +import java.util.* +import javax.inject.Inject +import javax.inject.Singleton +import retrofit2.Response +import timber.log.Timber +import javax.inject.Named + +@Singleton +class CUFYFormRepository @Inject constructor( + @ApplicationContext private val context: Context, + @Named("gsonAmritApi") private val amritApiService: AmritApiService, + private val pref : PreferenceDao, + private val db: InAppDb +) { + private val formSchemaDao = db.formSchemaDao() + private val jsonResponseDao = db.CUFYFormResponseJsonDao() + + suspend fun getFormSchema(formId: String): FormSchemaDto? = withContext(Dispatchers.IO) { + var result: FormSchemaDto? = null + + try { + val response = amritApiService.fetchFormSchema(formId,pref.getCurrentLanguage().symbol) + + if (response.isSuccessful) { + val apiResponse = response.body() + if (apiResponse?.success == true) { + val apiSchema = apiResponse.data + if (apiSchema != null) { + val localSchema = getSavedSchema(apiSchema.formId) + if (localSchema == null || localSchema.version < apiSchema.version) { + saveFormSchemaToDb(apiSchema) + } + result = apiSchema + } + } + } + } catch (e: Exception) { + // ignored — fallback below will handle + } + + if (result == null) { + val dbSchema = formSchemaDao.getSchema(formId) + result = dbSchema?.let { FormSchemaDto.fromJson(it.schemaJson) } + ?: loadSchemaFromAssets() + } + result + } + + private fun loadSchemaFromAssets(): FormSchemaDto? { + return try { + val json = context.assets.open("hbnc_form_1stday.json") + .bufferedReader().use { it.readText() } + FormSchemaDto.fromJson(json) + } catch (e: Exception) { + null + } + } + + suspend fun saveFormSchemaToDb(schema: FormSchemaDto) { + val entity = FormSchemaEntity( + formId = schema.formId, + formName = schema.formName, + language = pref.getCurrentLanguage().symbol, + version = schema.version, + schemaJson = schema.toJson() + ) + formSchemaDao.insertOrUpdate(entity) + } + + suspend fun getSavedSchema(formId: String) = formSchemaDao.getSchema(formId) + + suspend fun getSyncedVisitsByRchId(benId: Long): List = + jsonResponseDao.getSyncedVisitsByRchId(benId) + + suspend fun getAllFormVisits(formName: String, request: HBNCVisitRequest): Response { + return amritApiService.getAllFormVisits(formName, request) + } + + suspend fun getBottleList(benId: Long, formId: String): List { + val jsonList = jsonResponseDao.getFormJsonList(benId, formId) + + val result = mutableListOf() + + jsonList.forEachIndexed { index, formJson -> + try { + val root = JSONObject(formJson) + val fields = root.optJSONObject("fields") + val date = fields?.optString("ifa_provision_date", "-") ?: "-" + val count = fields?.optString("ifa_bottle_count", "-") ?: "-" + + result.add( + BottleItem( + srNo = index + 1, + bottleNumber = count.toString(), + dateOfProvision = date + ) + ) + } catch (e: Exception) { + e.printStackTrace() + } + } + return result + } + + suspend fun saveDownloadedVisitList(list: List, formId: String) { + for ((index, item) in list.withIndex()) { + try { + if (item.fields == null) { + continue + } + + val visitDate = item.visitDate ?: "-" + val benId = item.beneficiaryId + val hhId = item.houseHoldId + + val fieldsJson = JSONObject() + item.fields.entrySet().forEach { (key, jsonElement) -> + val value = when { + jsonElement.isJsonNull -> JSONObject.NULL + jsonElement.isJsonPrimitive -> { + val prim = jsonElement.asJsonPrimitive + when { + prim.isBoolean -> prim.asBoolean + prim.isNumber -> prim.asNumber + prim.isString -> prim.asString + prim.isJsonArray -> prim.isJsonArray + else -> prim.asString + } + } + + else -> jsonElement.toString() + } + fieldsJson.put(key, value) + } + + val fullJson = JSONObject().apply { + put("formId", formId) + put("beneficiaryId", benId) + put("houseHoldId", hhId) + put("visitDate", visitDate) + put("fields", fieldsJson) + } + + val entity = CUFYFormResponseJsonEntity( + benId = benId, + hhId = hhId, + visitDate = visitDate, + formId = formId, + version = 1, + formDataJson = fullJson.toString(), + isSynced = true + ) + + insertOrUpdateFormResponse(entity) + + } catch (e: Exception) { + Timber.tag("CUFYFormRepository").e(e, "Failed to save visit at index " + index) + } + } + } + + suspend fun insertOrUpdateFormResponse(entity: CUFYFormResponseJsonEntity) { + + val existing = jsonResponseDao.getFormResponse(entity.benId, entity.visitDate) + + val updated = existing?.let { + val result = entity.copy(id = it.id) + result + } ?: run { + entity + } + + jsonResponseDao.insertFormResponse(updated) + } + + + + + suspend fun insertFormResponse(entity: CUFYFormResponseJsonEntity) { + + if (entity.id > 0) { + val existingRecord = jsonResponseDao.getFormResponseById(entity.id) + + if (existingRecord != null) { + // val updatedEntity = entity.copy(createdAt = existingRecord.createdAt) + //val updateResult = jsonResponseDao.updateFormResponse(updatedEntity) + val mergedEntity = mergeFollowUpDates(existingRecord, entity) + val updateResult = jsonResponseDao.updateFormResponse(mergedEntity) + return + } else { + Timber.tag("CUFYFormRepository").w(" insertFormResponse: No existing record found for ID=${entity.id}, will INSERT as new") + } + } else { + Timber.tag("CUFYFormRepository").d(" insertFormResponse: ID=0, INSERTING as new record") + } + + jsonResponseDao.insertFormResponse(entity) + } + + suspend fun loadFormResponseJson(benId: Long, visitDay: String): String? = + jsonResponseDao.getFormResponse(benId, visitDay)?.formDataJson + + suspend fun getUnsyncedForms(formId: String): List = + jsonResponseDao.getUnsyncedForms(formId) + + suspend fun getSavedDataByFormId(formId: String, benId: Long ): List = + jsonResponseDao.getFormsDataByFormID(formId, benId) + + + private fun mergeFollowUpDates(existingEntity: CUFYFormResponseJsonEntity, newEntity: CUFYFormResponseJsonEntity): CUFYFormResponseJsonEntity { + + try { + val existingJson = JSONObject(existingEntity.formDataJson) + val existingFields = existingJson.optJSONObject("fields") ?: JSONObject() + val existingFollowUpArray = existingFields.optJSONArray("follow_up_visit_date") + + val newJson = JSONObject(newEntity.formDataJson) + val newFields = newJson.optJSONObject("fields") ?: JSONObject() + val newFollowUpArray = newFields.optJSONArray("follow_up_visit_date") + + + val mergedFollowUpArray = JSONArray() + + if (existingFollowUpArray != null) { + for (i in 0 until existingFollowUpArray.length()) { + val date = existingFollowUpArray.optString(i) + if (date.isNotBlank()) { + mergedFollowUpArray.put(date) + } + } + } + + if (newFollowUpArray != null) { + for (i in 0 until newFollowUpArray.length()) { + val newDate = newFollowUpArray.optString(i) + if (newDate.isNotBlank()) { + var exists = false + for (j in 0 until mergedFollowUpArray.length()) { + if (mergedFollowUpArray.optString(j) == newDate) { + exists = true + break + } + } + if (!exists) { + mergedFollowUpArray.put(newDate) + } + } + } + } + + + val mergedFields = JSONObject() + + newFields.keys().forEach { key -> + mergedFields.put(key, newFields.get(key)) + } + + mergedFields.put("follow_up_visit_date", mergedFollowUpArray) + + val mergedJson = JSONObject().apply { + put("formId", newJson.optString("formId")) + put("beneficiaryId", newJson.optLong("beneficiaryId")) + put("houseHoldId", newJson.optLong("houseHoldId")) + put("visitDate", newJson.optString("visitDate")) + put("fields", mergedFields) + } + + + return newEntity.copy( + formDataJson = mergedJson.toString(), + updatedAt = System.currentTimeMillis() + ) + + } catch (e: Exception) { + return newEntity.copy(updatedAt = System.currentTimeMillis()) + } + } + + + + suspend fun syncFormToServer(userName: String,formName: String, form: CUFYFormResponseJsonEntity): Boolean { + return try { + val request = FormSubmitRequestMapper.fromEntity(form,userName) ?: return false + val response = amritApiService.submitChildCareForm(formName,listOf(request)) + response.isSuccessful + } catch (e: Exception) { + false + } + } + + suspend fun markFormAsSynced(id: Int) { + val timestamp = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH).format(Date()) + jsonResponseDao.markAsSynced(id, timestamp) + } + + + suspend fun getCurrentSamStatus(benId: Long): String { + Timber.tag("CUFYFormRepository").d("🔍 getCurrentSamStatus: benId=$benId") + + val localizedRes = HelperUtil.getLocalizedResources(context, pref.getCurrentLanguage()) + + val samForms = jsonResponseDao.getFormsDataByFormID( + FormConstants.CHILDREN_UNDER_FIVE_SAM_FORM_ID, + benId + ).sortedByDescending { it.visitDate } + + + if (samForms.isEmpty()) { + return localizedRes.getString(R.string.check_sam_) + } + + val latestForm = samForms.first() + + return try { + val json = JSONObject(latestForm.formDataJson) + val fields = json.optJSONObject("fields") ?: JSONObject() + + val isReferredToNRC = fields.optString(context.getString(R.string.is_child_referred_nrc)) == "Yes" + val isAdmittedToNRC = fields.optString(context.getString(R.string.is_child_admitted_nrc)) == "Yes" + val isDischargedFromNRC = fields.optString(context.getString(R.string.is_child_discharged_nrc)) == "Yes" + val samStatus = fields.optString(context.getString(R.string.sam_status)) + + + + when { + isDischargedFromNRC -> { + if (samStatus == "Improved") localizedRes.getString(R.string.check_sam_) else localizedRes.getString(R.string.follow_up_sam) + } + isAdmittedToNRC -> localizedRes.getString(R.string.nrc_admitted) + isReferredToNRC -> localizedRes.getString(R.string.referred_to_nrc) + else -> localizedRes.getString(R.string.check_sam_) + } + } catch (e: Exception) { + localizedRes.getString(R.string.check_sam_) + } + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/dynamicRepo/EyeSurgeryFormRepository.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/dynamicRepo/EyeSurgeryFormRepository.kt new file mode 100644 index 000000000..62a54e35c --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/dynamicRepo/EyeSurgeryFormRepository.kt @@ -0,0 +1,211 @@ +package org.piramalswasthya.sakhi.repositories.dynamicRepo + +import android.content.Context +import androidx.annotation.WorkerThread +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.json.JSONObject +import org.piramalswasthya.sakhi.database.room.InAppDb +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao +import org.piramalswasthya.sakhi.helpers.dynamicMapper.FormSubmitRequestMapper +import org.piramalswasthya.sakhi.model.dynamicEntity.eye_surgery.EyeSurgeryFormResponseJsonEntity +import org.piramalswasthya.sakhi.model.dynamicEntity.FormSchemaDto +import org.piramalswasthya.sakhi.model.dynamicEntity.FormSchemaEntity +import org.piramalswasthya.sakhi.model.dynamicModel.HBNCVisitListResponse +import org.piramalswasthya.sakhi.model.dynamicModel.HBNCVisitRequest +import org.piramalswasthya.sakhi.model.dynamicModel.HBNCVisitResponse +import org.piramalswasthya.sakhi.network.AmritApiService +import java.text.SimpleDateFormat +import java.util.* +import javax.inject.Inject +import javax.inject.Singleton +import retrofit2.Response +import javax.inject.Named + +@Singleton +class EyeSurgeryFormRepository @Inject constructor( + @ApplicationContext private val context: Context, + @Named("gsonAmritApi") private val amritApiService: AmritApiService, + private val pref: PreferenceDao, + private val db: InAppDb +) { + private val formSchemaDao = db.formSchemaDao() + private val jsonResponseDao = db.formResponseJsonDaoEyeSurgery() + + suspend fun getFormSchema(formId: String): FormSchemaDto? = withContext(Dispatchers.IO) { + var result: FormSchemaDto? = null + + try { + val response = amritApiService.fetchFormSchema(formId,pref.getCurrentLanguage().symbol) + + if (response.isSuccessful) { + val apiResponse = response.body() + if (apiResponse?.success == true) { + val apiSchema = apiResponse.data + if (apiSchema != null) { + val localSchema = getSavedSchema(apiSchema.formId) + if (localSchema == null || localSchema.version < apiSchema.version) { + saveFormSchemaToDb(apiSchema) + } + result = apiSchema + } + } + } + } catch (e: Exception) { + } + + if (result == null) { + val dbSchema = formSchemaDao.getSchema(formId) + result = dbSchema?.let { FormSchemaDto.fromJson(it.schemaJson) } + ?: loadSchemaFromAssets() + } + result + } + + private fun loadSchemaFromAssets(): FormSchemaDto? { + return try { + val json = context.assets.open("hbnc_form_1stday.json") + .bufferedReader().use { it.readText() } + FormSchemaDto.fromJson(json) + } catch (e: Exception) { + null + } + } + + suspend fun saveFormSchemaToDb(schema: FormSchemaDto) { + val entity = FormSchemaEntity( + formId = schema.formId, + formName = schema.formName, + language = pref.getCurrentLanguage().symbol, + version = schema.version, + schemaJson = schema.toJson() + ) + formSchemaDao.insertOrUpdate(entity) + } + + suspend fun getSavedSchema(formId: String) = formSchemaDao.getSchema(formId) + + suspend fun getSyncedVisitsByRchId(benId: Long): List = + jsonResponseDao.getSyncedVisitsByRchId(benId) + + suspend fun getAllFormVisits(formName: String, request: HBNCVisitRequest): Response { + return amritApiService.getAllEyeSurgeryFormVisits(formName, request) + } + + suspend fun getAllBenIds(): List { + return jsonResponseDao.getAllUniqueBenIds() + } + + private fun toMonthKey(dateStr: String?): String { + if (dateStr.isNullOrBlank()) return "" + val inputs = listOf("dd-MM-yyyy", "yyyy-MM-dd") + val out = SimpleDateFormat("yyyy-MM", Locale.ENGLISH) + for (fmt in inputs) { + try { + val d = SimpleDateFormat(fmt, Locale.ENGLISH).parse(dateStr) + if (d != null) return out.format(d) + } catch (_: Exception) {} + } + return try { + if (Regex("\\d{2}-\\d{2}-\\d{4}").matches(dateStr)) { + val yyyy = dateStr.substring(6, 10) + val mm = dateStr.substring(3, 5) + "$yyyy-$mm" + } else "" + } catch (_: Exception) { "" } + } + + suspend fun saveDownloadedVisitList(list: List, formId: String) { + try { + if (list.isEmpty()) return + + val entityList = list.mapNotNull { item -> + try { + if (item.fields == null) return@mapNotNull null + + val visitDate = item.visitDate ?: "-" + val visitMonth = toMonthKey(visitDate) + val benId = item.beneficiaryId + val hhId = item.houseHoldId + + val fieldsJson = JSONObject() + item.fields.entrySet().forEach { (key, jsonElement) -> + val value = when { + jsonElement.isJsonNull -> JSONObject.NULL + jsonElement.isJsonPrimitive -> { + val prim = jsonElement.asJsonPrimitive + when { + prim.isBoolean -> prim.asBoolean + prim.isNumber -> prim.asNumber + prim.isString -> prim.asString + else -> prim.asString + } + } + else -> jsonElement.toString() + } + fieldsJson.put(key, value) + } + + val fullJson = JSONObject().apply { + put("formId", formId) + put("beneficiaryId", benId) + put("houseHoldId", hhId) + put("visitDate", visitDate) + put("fields", fieldsJson) + } + + EyeSurgeryFormResponseJsonEntity( + benId = benId, + hhId = hhId, + visitDate = visitDate, + visitMonth = visitMonth, + formId = formId, + version = 1, + formDataJson = fullJson.toString(), + isSynced = true + ) + } catch (e: Exception) { + e.printStackTrace() + null + } + } + + if (entityList.isNotEmpty()) { + insertAllByMonth(entityList) + } + + } catch (e: Exception) { + e.printStackTrace() + } + } + + @WorkerThread + private suspend fun insertAllByMonth(list: List) { + list.forEach { jsonResponseDao.upsertByMonth(it) } + } + + suspend fun insertFormResponse(entity: EyeSurgeryFormResponseJsonEntity) = + jsonResponseDao.upsertByMonth(entity) + + suspend fun loadFormResponseJson(benId: Long, formId: String): String? = + jsonResponseDao.getLatestForBenForm(benId, formId)?.formDataJson + + suspend fun getUnsyncedForms(formName: String): List = + jsonResponseDao.getUnsyncedForms(formName) + + suspend fun syncFormToServer(userName: String, formName: String, form: EyeSurgeryFormResponseJsonEntity): Boolean { + return try { + val request = FormSubmitRequestMapper.fromEntity(form, userName) ?: return false + val response = amritApiService.submitEyeSurgeryForm(formName, listOf(request)) + response.isSuccessful + } catch (e: Exception) { + false + } + } + + suspend fun markFormAsSynced(id: Int) { + val timestamp = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH).format(Date()) + jsonResponseDao.markAsSynced(id, timestamp) + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/dynamicRepo/FilariaMDAFormRepository.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/dynamicRepo/FilariaMDAFormRepository.kt new file mode 100644 index 000000000..f691020f3 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/dynamicRepo/FilariaMDAFormRepository.kt @@ -0,0 +1,234 @@ +package org.piramalswasthya.sakhi.repositories.dynamicRepo + +import android.content.Context +import androidx.annotation.WorkerThread +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.json.JSONObject +import org.piramalswasthya.sakhi.database.room.InAppDb +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao +import org.piramalswasthya.sakhi.helpers.dynamicMapper.FormSubmitRequestMapper +import org.piramalswasthya.sakhi.model.BottleItem +import org.piramalswasthya.sakhi.model.dynamicEntity.FilariaMDA.FilariaMDAFormResponseJsonEntity +import org.piramalswasthya.sakhi.model.dynamicEntity.FormSchemaDto +import org.piramalswasthya.sakhi.model.dynamicEntity.FormSchemaEntity +import org.piramalswasthya.sakhi.model.dynamicModel.HBNCVisitListResponse +import org.piramalswasthya.sakhi.model.dynamicModel.HBNCVisitRequest +import org.piramalswasthya.sakhi.model.dynamicModel.HBNCVisitResponse +import org.piramalswasthya.sakhi.network.AmritApiService +import java.text.SimpleDateFormat +import java.util.* +import javax.inject.Inject +import javax.inject.Singleton +import retrofit2.Response +import javax.inject.Named + +@Singleton +class FilariaMDAFormRepository @Inject constructor( + @ApplicationContext private val context: Context, + @Named("gsonAmritApi") private val amritApiService: AmritApiService, + private val pref : PreferenceDao, + private val db: InAppDb +) { + private val formSchemaDao = db.formSchemaDao() + private val jsonResponseDao = db.formResponseFilariaMDAJsonDao() + + suspend fun getFormSchema(formId: String): FormSchemaDto? = withContext(Dispatchers.IO) { + var result: FormSchemaDto? = null + + try { + val response = amritApiService.fetchFormSchema(formId,pref.getCurrentLanguage().symbol) + + if (response.isSuccessful) { + val apiResponse = response.body() + if (apiResponse?.success == true) { + val apiSchema = apiResponse.data + if (apiSchema != null) { + val localSchema = getSavedSchema(apiSchema.formId) + if (localSchema == null || localSchema.version < apiSchema.version) { + saveFormSchemaToDb(apiSchema) + } + result = apiSchema + } + } + } + } catch (e: Exception) { + } + + if (result == null) { + val dbSchema = formSchemaDao.getSchema(formId) + result = dbSchema?.let { FormSchemaDto.fromJson(it.schemaJson) } + ?: loadSchemaFromAssets() + } + result + } + + private fun loadSchemaFromAssets(): FormSchemaDto? { + return try { + val json = context.assets.open("hbnc_form_1stday.json") + .bufferedReader().use { it.readText() } + FormSchemaDto.fromJson(json) + } catch (e: Exception) { + null + } + } + + suspend fun saveFormSchemaToDb(schema: FormSchemaDto) { + val entity = FormSchemaEntity( + formId = schema.formId, + formName = schema.formName, + language = pref.getCurrentLanguage().symbol, + version = schema.version, + schemaJson = schema.toJson() + ) + formSchemaDao.insertOrUpdate(entity) + } + + suspend fun getSavedSchema(formId: String) = formSchemaDao.getSchema(formId) + + suspend fun getSyncedVisitsByRchId(hhId: Long): List = + jsonResponseDao.getSyncedVisitsByRchId(hhId) + + suspend fun getAllFormVisits(formName: String, request: HBNCVisitRequest): Response { + return amritApiService.getAllEyeSurgeryFormVisits(formName, request) + } + + suspend fun getBottleList(hhId: Long): List { + val jsonList = jsonResponseDao.getFormJsonList(hhId) + + val result = mutableListOf() + + jsonList.forEach { formJson -> + val root = JSONObject(formJson) + val fields = root.optJSONObject("fields") + val date = fields?.optString("mda_distribution_date", "-") ?: "-" + val count = fields?.optString("is_medicine_distributed", "-") ?: "-" + + result.add( + BottleItem( + srNo = 0, + bottleNumber = count, + dateOfProvision = date + ) + ) + } + + return result.mapIndexed { index, item -> + item.copy(srNo = index + 1) + } + } + + private fun toMonthKey(dateStr: String?): String { + if (dateStr.isNullOrBlank()) return "" + val inputs = listOf("dd-MM-yyyy", "yyyy-MM-dd") + val out = SimpleDateFormat("yyyy-MM", Locale.ENGLISH) + for (fmt in inputs) { + try { + val d = SimpleDateFormat(fmt, Locale.ENGLISH).parse(dateStr) + if (d != null) return out.format(d) + } catch (_: Exception) {} + } + return try { + if (Regex("\\d{2}-\\d{2}-\\d{4}").matches(dateStr)) { + val yyyy = dateStr.substring(6, 10) + val mm = dateStr.substring(3, 5) + "$yyyy-$mm" + } else "" + } catch (_: Exception) { "" } + } + + suspend fun saveDownloadedVisitList(list: List, formId: String) { + try { + if (list.isEmpty()) return + + val entityList = list.mapNotNull { item -> + try { + if (item.fields == null) return@mapNotNull null + + val visitDate = item.visitDate ?: "-" + val visitMonth = toMonthKey(visitDate) + val hhId = item.houseHoldId + + // Convert all fields to JSON + val fieldsJson = JSONObject() + item.fields.entrySet().forEach { (key, jsonElement) -> + val value = when { + jsonElement.isJsonNull -> JSONObject.NULL + jsonElement.isJsonPrimitive -> { + val prim = jsonElement.asJsonPrimitive + when { + prim.isBoolean -> prim.asBoolean + prim.isNumber -> prim.asNumber + prim.isString -> prim.asString + else -> prim.asString + } + } + else -> jsonElement.toString() + } + fieldsJson.put(key, value) + } + + val fullJson = JSONObject().apply { + put("formId", formId) + put("houseHoldId", hhId) + put("visitDate", visitDate) + put("fields", fieldsJson) + } + + // Build entity + FilariaMDAFormResponseJsonEntity( + hhId = hhId, + visitDate = visitDate, + visitMonth = visitMonth, + formId = formId, + version = 1, + formDataJson = fullJson.toString(), + isSynced = true + ) + } catch (e: Exception) { + e.printStackTrace() + null + } + } + + if (entityList.isNotEmpty()) { + insertAllByMonth(entityList) + } + + } catch (e: Exception) { + e.printStackTrace() + } + } + + @WorkerThread + private suspend fun insertAllByMonth(list: List) { + list.forEach { jsonResponseDao.upsertByMonth(it) } + } + + suspend fun insertFormResponse(entity: FilariaMDAFormResponseJsonEntity): Boolean { + return jsonResponseDao.insertOncePerMonth(entity) + } + + + suspend fun loadFormResponseJson(hhId: Long, formId: String): String? = + jsonResponseDao.getLatestForBenForm(hhId, formId)?.formDataJson + + suspend fun getUnsyncedForms(formName: String): List = + jsonResponseDao.getUnsyncedForms(formName) + + suspend fun syncFormToServer(userName: String, formName: String, form: FilariaMDAFormResponseJsonEntity): Boolean { + return try { + val request = FormSubmitRequestMapper.fromEntity(form, userName) ?: return false + val response = amritApiService.submitEyeSurgeryForm(formName, listOf(request)) + response.isSuccessful + } catch (e: Exception) { + false + } + } + + suspend fun markFormAsSynced(id: Int) { + val timestamp = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH).format(Date()) + jsonResponseDao.markAsSynced(id, timestamp) + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/dynamicRepo/FilariaMdaCampaignRepository.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/dynamicRepo/FilariaMdaCampaignRepository.kt new file mode 100644 index 000000000..9ea81dfa8 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/dynamicRepo/FilariaMdaCampaignRepository.kt @@ -0,0 +1,222 @@ +package org.piramalswasthya.sakhi.repositories.dynamicRepo + +import android.content.Context +import androidx.annotation.WorkerThread +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.json.JSONObject +import org.piramalswasthya.sakhi.database.room.InAppDb +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao +import org.piramalswasthya.sakhi.helpers.dynamicMapper.FormSubmitRequestMapper +import org.piramalswasthya.sakhi.model.dynamicEntity.FilariaMDA.FilariaMDAFormResponseJsonEntity +import org.piramalswasthya.sakhi.model.dynamicEntity.FormSchemaDto +import org.piramalswasthya.sakhi.model.dynamicEntity.FormSchemaEntity +import org.piramalswasthya.sakhi.model.dynamicEntity.filariaaMdaCampaign.FilariaMDACampaignFormResponseJsonEntity +import org.piramalswasthya.sakhi.model.dynamicModel.HBNCVisitListResponse +import org.piramalswasthya.sakhi.model.dynamicModel.HBNCVisitRequest +import org.piramalswasthya.sakhi.model.dynamicModel.HBNCVisitResponse +import org.piramalswasthya.sakhi.model.dynamicModel.MDACampaignItem +import org.piramalswasthya.sakhi.network.AmritApiService +import retrofit2.Response +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale +import javax.inject.Inject +import javax.inject.Named +import javax.inject.Singleton +import kotlin.collections.component1 +import kotlin.collections.component2 +import kotlin.collections.forEach + +@Singleton +class FilariaMdaCampaignRepository @Inject constructor( + @ApplicationContext private val context: Context, + @Named("gsonAmritApi") private val amritApiService: AmritApiService, + private val pref : PreferenceDao, + private val db: InAppDb +) { + private val formSchemaDao = db.formSchemaDao() + private val jsonResponseDao = db.formResponseFilariaMDACampaignJsonDao() + + suspend fun getFormSchema(formId: String): FormSchemaDto? = withContext(Dispatchers.IO) { + var result: FormSchemaDto? = null + + try { + val response = amritApiService.fetchFormSchema(formId,"en") + + if (response.isSuccessful) { + val apiResponse = response.body() + if (apiResponse?.success == true) { + val apiSchema = apiResponse.data + if (apiSchema != null) { + val localSchema = getSavedSchema(apiSchema.formId) + if (localSchema == null || localSchema.version < apiSchema.version) { + saveFormSchemaToDb(apiSchema) + } + result = apiSchema + } + } + } + } catch (e: Exception) { + + } + result + } + + + suspend fun saveFormSchemaToDb(schema: FormSchemaDto) { + val entity = FormSchemaEntity( + formId = schema.formId, + formName = schema.formName, + language = pref.getCurrentLanguage().symbol, + version = schema.version, + schemaJson = schema.toJson() + ) + formSchemaDao.insertOrUpdate(entity) + } + + suspend fun getSavedSchema(formId: String) = formSchemaDao.getSchema(formId) + + suspend fun getSyncedVisitsByRchId(): List = + jsonResponseDao.getCampaignSyncedVisitsByRchId() + + suspend fun getBottleList(): List { + val jsonList = jsonResponseDao.getCampaignFormJsonList() + + val result = mutableListOf() + + jsonList.forEach { formJson -> + val root = JSONObject(formJson) + val fields = root.optJSONObject("fields") + val startDate = fields?.optString("start_date", "-") ?: "-" + val endDate = fields?.optString("end_date", "-") ?: "-" + val noofFamiles = fields?.optString("no_of_families", "0")?.takeIf { it != "null" } ?: "0" + val noofIndividuals = fields?.optString("no_of_individuals", "0")?.takeIf { it != "null" } ?: "0" + + result.add( + MDACampaignItem( + srNo = 0, + startDate = startDate, + endDate = endDate, + noOfIndividuals = noofIndividuals, + noOffamilies = noofFamiles + ) + ) + } + + return result.mapIndexed { index, item -> + item.copy(srNo = index + 1) + } + } + + private fun toYearKey(dateStr: String?): String { + if (dateStr.isNullOrBlank()) return "" + val inputs = listOf("dd-MM-yyyy", "yyyy-MM-dd") + val out = SimpleDateFormat("yyyy", Locale.ENGLISH) + for (fmt in inputs) { + try { + val d = SimpleDateFormat(fmt, Locale.ENGLISH).parse(dateStr) + if (d != null) return out.format(d) + } catch (_: Exception) {} + } + return try { + if (Regex("\\d{2}-\\d{2}-\\d{4}").matches(dateStr)) { + dateStr.substring(6, 10) + } else "" + } catch (_: Exception) { "" } + } + + suspend fun saveDownloadedVisitList(list: List, formId: String) { + try { + if (list.isEmpty()) return + + val entityList = list.mapNotNull { item -> + try { + if (item.fields == null) return@mapNotNull null + + val visitDate = item.visitDate ?: "-" + val visitYear = toYearKey(visitDate) + val hhId = item.houseHoldId + + // Convert all fields to JSON + val fieldsJson = JSONObject() + item.fields.entrySet().forEach { (key, jsonElement) -> + val value = when { + jsonElement.isJsonNull -> JSONObject.NULL + jsonElement.isJsonPrimitive -> { + val prim = jsonElement.asJsonPrimitive + when { + prim.isBoolean -> prim.asBoolean + prim.isNumber -> prim.asNumber + prim.isString -> prim.asString + else -> prim.asString + } + } + else -> jsonElement.toString() + } + fieldsJson.put(key, value) + } + + val fullJson = JSONObject().apply { + put("formId", formId) + put("houseHoldId", hhId) + put("visitDate", visitDate) + put("fields", fieldsJson) + } + + // Build entity + FilariaMDACampaignFormResponseJsonEntity( + visitDate = visitDate, + visitYear = visitYear, + formId = formId, + version = 1, + formDataJson = fullJson.toString(), + isSynced = true + ) + } catch (e: Exception) { + e.printStackTrace() + null + } + } + + if (entityList.isNotEmpty()) { + insertAllByMonth(entityList) + } + + } catch (e: Exception) { + e.printStackTrace() + } + } + + @WorkerThread + private suspend fun insertAllByMonth(list: List) { + list.forEach { jsonResponseDao.upsertByYear(it) } + } + + suspend fun insertFormResponse(entity: FilariaMDACampaignFormResponseJsonEntity): Boolean { + return jsonResponseDao.insertOncePerYear(entity) + } + + + suspend fun loadFormResponseJson( date: String): String? = + jsonResponseDao.getCampaignLatestForBenForm( date)?.formDataJson + + suspend fun getUnsyncedForms(formName: String): List = + jsonResponseDao.getUnsyncedCampaignForms(formName) + + suspend fun syncFormToServer(userName: String, formName: String, form: FilariaMDAFormResponseJsonEntity): Boolean { + return try { + val request = FormSubmitRequestMapper.fromEntity(form, userName) ?: return false + val response = amritApiService.submitEyeSurgeryForm(formName, listOf(request)) + response.isSuccessful + } catch (e: Exception) { + false + } + } + + suspend fun markFormAsSynced(id: Int) { + val timestamp = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH).format(Date()) + jsonResponseDao.markCampaignAsSynced(id, timestamp) + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/dynamicRepo/FormRepository.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/dynamicRepo/FormRepository.kt new file mode 100644 index 000000000..c7efcb90d --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/dynamicRepo/FormRepository.kt @@ -0,0 +1,419 @@ +package org.piramalswasthya.sakhi.repositories.dynamicRepo + +import android.content.Context +import android.util.Log +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.json.JSONObject +import org.piramalswasthya.sakhi.database.room.InAppDb +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao +import org.piramalswasthya.sakhi.helpers.dynamicMapper.FormSubmitRequestMapper +import org.piramalswasthya.sakhi.model.dynamicEntity.FormResponseJsonEntity +import org.piramalswasthya.sakhi.model.dynamicEntity.hbyc.FormResponseJsonEntityHBYC +import org.piramalswasthya.sakhi.model.dynamicEntity.FormSchemaDto +import org.piramalswasthya.sakhi.model.dynamicEntity.FormSchemaEntity +import org.piramalswasthya.sakhi.model.dynamicEntity.anc.ANCFormResponseJsonEntity +import org.piramalswasthya.sakhi.model.dynamicModel.HBNCVisitListResponse +import org.piramalswasthya.sakhi.model.dynamicModel.HBNCVisitRequest +import org.piramalswasthya.sakhi.model.dynamicModel.HBNCVisitResponse +import org.piramalswasthya.sakhi.network.AmritApiService +import org.piramalswasthya.sakhi.utils.dynamicFormConstants.FormConstants +import org.piramalswasthya.sakhi.utils.dynamicFormConstants.FormConstants.ANC_FORM_ID +import org.piramalswasthya.sakhi.utils.dynamicFormConstants.FormConstants.HBNC_FORM_ID +import org.piramalswasthya.sakhi.utils.dynamicFormConstants.FormConstants.HBYC_FORM_ID +import org.piramalswasthya.sakhi.utils.dynamicFormConstants.FormConstants.LF_MDA_CAMPAIGN +import retrofit2.Response +import timber.log.Timber +import java.text.SimpleDateFormat +import java.util.* +import javax.inject.Inject +import javax.inject.Named +import javax.inject.Singleton + +@Singleton +class FormRepository @Inject constructor( + @ApplicationContext private val context: Context, + private val preferenceDao: PreferenceDao, + @Named("gsonAmritApi") private val amritApiService: AmritApiService, + private val db: InAppDb +) { + private val formSchemaDao = db.formSchemaDao() + private val jsonResponseDao = db.formResponseJsonDao() + private val jsonResponseDaoHBYC = db.formResponseJsonDaoHBYC() + + private val jsonResponseDaoANC = db.formResponseJsonDaoANC() + + val ALL_FORM_IDS = listOf( + FormConstants.HBNC_FORM_ID, + FormConstants.CHILDREN_UNDER_FIVE_ORS_FORM_ID, + FormConstants.CHILDREN_UNDER_FIVE_IFA_FORM_ID, + FormConstants.CHILDREN_UNDER_FIVE_SAM_FORM_ID, + FormConstants.IFA_DISTRIBUTION_FORM_ID, + FormConstants.EYE_SURGERY_FORM_ID, + FormConstants.HBYC_FORM_ID, + FormConstants.MOSQUITO_NET_FORM_ID, + FormConstants.MDA_DISTRIBUTION_FORM_ID, + FormConstants.CDTF_001, + FormConstants.ANC_FORM_ID, + FormConstants.ORS_CAMPAIGN_FORM_ID, + FormConstants.PULSE_POLIO_CAMPAIGN_FORM_ID, + LF_MDA_CAMPAIGN + ) + + + suspend fun downloadAllFormsSchemas(lang: String) = withContext(Dispatchers.IO) { + ALL_FORM_IDS.forEach { formId -> + try { + val response = amritApiService.fetchFormSchema(formId, lang) + + if (response.isSuccessful) { + val apiSchema = response.body()?.data ?: return@forEach + + val local = getSavedSchema(formId) + + if (local == null || + local.version < apiSchema.version || + local.language != lang + ) { + saveFormSchemaToDb(apiSchema, lang) + Log.d("FORM_SYNC", "Updated schema → $formId") + } else { + Log.d("FORM_SYNC", "Already latest → $formId") + } + + } else { + Log.e("FORM_SYNC", "Server error → $formId") + } + + } catch (e: Exception) { + Log.e("FORM_SYNC", "Exception → $formId", e) + } + } + } + + + + suspend fun getFormSchema(formId: String, lang: String): FormSchemaDto? = withContext(Dispatchers.IO) { + try { + val response = amritApiService.fetchFormSchema(formId, lang) + + if (response.isSuccessful) { + val apiResponse = response.body() + val apiSchema = apiResponse?.data + + apiSchema?.let { + val localSchema = getSavedSchema(it.formId) + if (localSchema == null || localSchema.version < it.version || localSchema.language != lang) { + saveFormSchemaToDb(it,lang) + } + return@withContext it + } + } else { + } + } catch (e: Exception) { + } + + val local = formSchemaDao.getSchema(formId)?.let { FormSchemaDto.fromJson(it.schemaJson) } + return@withContext local + } + + + suspend fun saveFormSchemaToDb(schema: FormSchemaDto, lang: String) { + val entity = FormSchemaEntity( + formId = schema.formId, + formName = schema.formName, + language = lang, + version = schema.version, + schemaJson = schema.toJson() + ) + formSchemaDao.insertOrUpdate(entity) + } + + suspend fun getSavedSchema(formId: String) = formSchemaDao.getSchema(formId) + suspend fun getInfantByRchId(benId: Long) = jsonResponseDao.getSyncedVisitsByRchId(benId) + suspend fun getSyncedVisitsByRchId(benId: Long): List = + jsonResponseDao.getSyncedVisitsByRchId(benId) + + suspend fun insertOrUpdateFormResponse(entity: FormResponseJsonEntity) { + val existing = jsonResponseDao.getFormResponse(entity.benId, entity.visitDay) + val updated = existing?.let { entity.copy(id = it.id) } ?: entity + jsonResponseDao.insertFormResponse(updated) + } + + suspend fun insertFormResponse(entity: FormResponseJsonEntity) = + jsonResponseDao.insertFormResponse(entity) + + suspend fun loadFormResponseJson(benId: Long, visitDay: String): String? = + jsonResponseDao.getFormResponse(benId, visitDay)?.formDataJson + + suspend fun getUnsyncedForms(): List = + jsonResponseDao.getUnsyncedForms() + + suspend fun syncFormToServer(form: FormResponseJsonEntity): Boolean { + return try { + + val request = FormSubmitRequestMapper.fromEntity(form,preferenceDao.getLoggedInUser()!!.userName) ?: return false + val response = amritApiService.submitForm(listOf(request)) + response.isSuccessful + } catch (e: Exception) { false } + } + + suspend fun markFormAsSynced(id: Int) { + val timestamp = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH).format(Date()) + jsonResponseDao.markAsSynced(id, timestamp) + } + + suspend fun getAllHbncVisits(request: HBNCVisitRequest): Response { + return amritApiService.getAllHbncVisits(request) + } + suspend fun getAllHbycVisits(request: HBNCVisitRequest): Response { + return amritApiService.getAllHbycVisits(request) + } + + suspend fun getAllAncVisits(request: HBNCVisitRequest): Response { + Log.d("anc_visit", "getAllAncVisits: called api") + return amritApiService.getAllAncVisits(request)} + + + suspend fun saveDownloadedVisitList(list: List) { + for (item in list) { + try { + if (item.fields == null) continue + val visitDay = item.fields["visit_day"]?.asString ?: continue + val visitDate = item.visitDate ?: "-" + val benId = item.beneficiaryId + val hhId = item.houseHoldId + + val fieldsJson = JSONObject() + item.fields.entrySet().forEach { (key, jsonElement) -> + val value = if (jsonElement.isJsonNull) JSONObject.NULL else jsonElement.asString + fieldsJson.put(key, value) + } + + val fullJson = JSONObject().apply { + put("formId", HBNC_FORM_ID) + put("beneficiaryId", benId) + put("houseHoldId", hhId) + put("visitDate", visitDate) + put("fields", fieldsJson) + } + + val entity = FormResponseJsonEntity( + benId = benId, + hhId = hhId, + visitDay = visitDay, + visitDate = visitDate, + formId = HBNC_FORM_ID, + version = 1, + formDataJson = fullJson.toString(), + isSynced = true + ) + insertOrUpdateFormResponse(entity) + } catch (e: Exception) { + Timber.e(e, "Failed to save HBNC visit") + } + } + } + + suspend fun getInfantByRchIdHBYC(benId: Long) = jsonResponseDaoHBYC.getSyncedVisitsByRchId(benId) + suspend fun getSyncedVisitsByRchIdHBYC(benId: Long): List = + jsonResponseDaoHBYC.getSyncedVisitsByRchId(benId) + + suspend fun insertOrUpdateFormResponseHBYC(entity: FormResponseJsonEntityHBYC) { + val existing = jsonResponseDaoHBYC.getFormResponse(entity.benId, entity.visitDay) + val updated = existing?.let { entity.copy(id = it.id) } ?: entity + jsonResponseDaoHBYC.insertFormResponse(updated) + } + + suspend fun insertFormResponseHBYC(entity: FormResponseJsonEntityHBYC) = + jsonResponseDaoHBYC.insertFormResponse(entity) + + suspend fun loadFormResponseJsonHBYC(benId: Long, visitDay: String): String? = + jsonResponseDaoHBYC.getFormResponse(benId, visitDay)?.formDataJson + + suspend fun getUnsyncedFormsHBYC(): List = + jsonResponseDaoHBYC.getUnsyncedForms() + + suspend fun syncFormToServerHBYC(form: FormResponseJsonEntityHBYC): Boolean { + return try { + val request = FormSubmitRequestMapper.fromEntity(form,preferenceDao.getLoggedInUser()!!.userName) ?: return false + val response = amritApiService.submitFormhbyc(listOf(request)) + response.isSuccessful + } catch (e: Exception) { false } + } + + + + + + suspend fun markFormAsSyncedHBYC(id: Int) { + val timestamp = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH).format(Date()) + jsonResponseDaoHBYC.markAsSynced(id, timestamp) + } + + + suspend fun saveDownloadedVisitListHBYC(list: List) { + for (item in list) { + try { + if (item.fields == null) continue + val visitDay = item.fields["visit_day"]?.asString ?: continue + val visitDate = item.visitDate ?: "-" + val benId = item.beneficiaryId + val hhId = item.houseHoldId + + val fieldsJson = JSONObject() + item.fields.entrySet().forEach { (key, jsonElement) -> + val value = if (jsonElement.isJsonNull) JSONObject.NULL else jsonElement.asString + fieldsJson.put(key, value) + } + + val fullJson = JSONObject().apply { + put("formId", HBYC_FORM_ID) + put("beneficiaryId", benId) + put("houseHoldId", hhId) + put("visitDate", visitDate) + put("fields", fieldsJson) + } + + val entity = FormResponseJsonEntityHBYC( + benId = benId, + hhId = hhId, + visitDay = visitDay, + visitDate = visitDate, + formId = HBYC_FORM_ID, + version = 1, + formDataJson = fullJson.toString(), + isSynced = true + ) + insertOrUpdateFormResponseHBYC(entity) + } catch (e: Exception) { + Timber.e(e, "Failed to save HBYC visit") + } + } + } + + suspend fun saveDownloadedVisitListANC(list: List) { + list.forEachIndexed { index, item -> + try { + val fields = item.fields ?: return@forEachIndexed + + val benId = item.beneficiaryId + val visitDate = item.visitDate + + val fieldsJson = JSONObject() + fields.entrySet().forEach { (key, jsonElement) -> + val value = if (jsonElement.isJsonNull) JSONObject.NULL else jsonElement.asString + fieldsJson.put(key, value) + } + + val visitDay = "Visit-${index + 1}" + + val fullJson = JSONObject().apply { + put("formId", ANC_FORM_ID) + put("beneficiaryId", benId) + put("visitDate", visitDate) + put("fields", fieldsJson) + } + + val entity = ANCFormResponseJsonEntity( + benId = benId, + visitDay = visitDay, + visitDate = visitDate, + formId = ANC_FORM_ID, + version = 1, + formDataJson = fullJson.toString(), + isSynced = true + ) + + insertFormResponseANC(entity) + + } catch (e: Exception) { + Timber.e(e, "Failed to save ANC visit ") + } + } + } + + + /*suspend fun saveDownloadedVisitListANC(list: List) { + for (item in list) { + try { + if (item.fields == null) continue + val visitDay = item.fields["visit_day"]?.asString ?: continue + val visitDate = item.visitDate ?: "-" + val benId = item.beneficiaryId + + val fieldsJson = JSONObject() + item.fields.entrySet().forEach { (key, jsonElement) -> + val value = if (jsonElement.isJsonNull) JSONObject.NULL else jsonElement.asString + fieldsJson.put(key, value) + } + + val fullJson = JSONObject().apply { + put("formId", HBYC_FORM_ID) + put("beneficiaryId", benId) + put("visitDate", visitDate) + put("fields", fieldsJson) + } + + val entity = ANCFormResponseJsonEntity( + benId = benId, + visitDay = visitDay, + visitDate = visitDate, + formId = ANC_FORM_ID, + version = 1, + formDataJson = fullJson.toString(), + isSynced = true + ) + insertFormResponseANC(entity) + } catch (e: Exception) { + Timber.e(e, "Failed to save ANC visit") + } + } + }*/ + + suspend fun getInfantByRchIdANC(benId: Long) = jsonResponseDaoANC.getSyncedVisitsByRchId(benId) + suspend fun getSyncedVisitsByRchIdANC(benId: Long): List = + jsonResponseDaoANC.getSyncedVisitsByRchId(benId) + + suspend fun insertOrUpdateFormResponseANC(entity: ANCFormResponseJsonEntity) { + val existing = jsonResponseDaoANC.getFormResponse(entity.benId, entity.visitDate) + val updated = existing?.let { entity.copy(id = it.id) } ?: entity + jsonResponseDaoANC.insertFormResponse(updated) + } + + suspend fun insertFormResponseANC(entity: ANCFormResponseJsonEntity) = + jsonResponseDaoANC.insertFormResponse(entity) + + suspend fun loadFormResponseJsonANC(benId: Long, visitDate: String): String? = + jsonResponseDaoANC.getFormResponse(benId, visitDate)?.formDataJson + + suspend fun getUnsyncedFormsANC(): List = + jsonResponseDaoANC.getUnsyncedForms() + + suspend fun syncFormToServerANC(form: ANCFormResponseJsonEntity): Boolean { + return try { + val request = FormSubmitRequestMapper.formEntity(form,preferenceDao.getLoggedInUser()!!.userName) ?: return false + val response = amritApiService.submitFromANC(listOf(request)) + response.isSuccessful + } catch (e: Exception) { false } + } + + suspend fun markFormAsSyncedANC(id: Int) { + val timestamp = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH).format(Date()) + jsonResponseDaoANC.markAsSynced(id, timestamp) + } + + suspend fun getLastVisitForBenANC(benId: Long): ANCFormResponseJsonEntity? { + return try { + val visits = jsonResponseDaoANC.getVisitsForBen(benId) + visits.maxByOrNull { + SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH).parse(it.visitDate)?.time ?: 0L + } + } catch (e: Exception) { + null + } + } + + +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/dynamicRepo/MosquitoNetFormRepository.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/dynamicRepo/MosquitoNetFormRepository.kt new file mode 100644 index 000000000..7ff676ffe --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/dynamicRepo/MosquitoNetFormRepository.kt @@ -0,0 +1,208 @@ +package org.piramalswasthya.sakhi.repositories.dynamicRepo + +import android.content.Context +import androidx.annotation.WorkerThread +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.json.JSONObject +import org.piramalswasthya.sakhi.database.room.InAppDb +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao +import org.piramalswasthya.sakhi.helpers.dynamicMapper.FormSubmitRequestMapper +import org.piramalswasthya.sakhi.model.BottleItem +import org.piramalswasthya.sakhi.model.dynamicEntity.FormSchemaDto +import org.piramalswasthya.sakhi.model.dynamicEntity.FormSchemaEntity +import org.piramalswasthya.sakhi.model.dynamicEntity.mosquitonetEntity.MosquitoNetFormResponseJsonEntity +import org.piramalswasthya.sakhi.model.dynamicModel.HBNCVisitListResponse +import org.piramalswasthya.sakhi.model.dynamicModel.HBNCVisitRequest +import org.piramalswasthya.sakhi.model.dynamicModel.HBNCVisitResponse +import org.piramalswasthya.sakhi.network.AmritApiService +import retrofit2.Response +import java.text.SimpleDateFormat +import java.util.* +import javax.inject.Inject +import javax.inject.Named +import javax.inject.Singleton + +@Singleton +class MosquitoNetFormRepository @Inject constructor( + @ApplicationContext private val context: Context, + @Named("gsonAmritApi") private val amritApiService: AmritApiService, + private val pref : PreferenceDao, + private val db: InAppDb +) { + private val formSchemaDao = db.formSchemaDao() + private val jsonResponseDao = db.formResponseMosquitoNetJsonDao() + + suspend fun getFormSchema(formId: String): FormSchemaDto? = withContext(Dispatchers.IO) { + var result: FormSchemaDto? = null + try { + val response = amritApiService.fetchFormSchema(formId, pref.getCurrentLanguage().symbol) + if (response.isSuccessful) { + val apiResponse = response.body() + if (apiResponse?.success == true) { + val apiSchema = apiResponse.data + if (apiSchema != null) { + val local = getSavedSchema(apiSchema.formId) + if (local == null || local.version < apiSchema.version) { + saveFormSchemaToDb(apiSchema) + } + result = apiSchema + } + } + } + } catch (e : Exception) { + e.printStackTrace() + } + + if (result == null) { + val dbSchema = formSchemaDao.getSchema(formId) + result = dbSchema?.let { FormSchemaDto.fromJson(it.schemaJson) } ?: loadSchemaFromAssets() + } + result + } + + private fun loadSchemaFromAssets(): FormSchemaDto? { + return try { + val json = context.assets.open("hbnc_form_1stday.json").bufferedReader().use { it.readText() } + FormSchemaDto.fromJson(json) + } catch (_: Exception) { null } + } + + suspend fun saveFormSchemaToDb(schema: FormSchemaDto) { + val entity = FormSchemaEntity( + formId = schema.formId, + formName = schema.formName, + language = pref.getCurrentLanguage().symbol, + version = schema.version, + schemaJson = schema.toJson() + ) + formSchemaDao.insertOrUpdate(entity) + } + + suspend fun getSavedSchema(formId: String) = formSchemaDao.getSchema(formId) + + + suspend fun getAllFormVisits( + formName: String, + request: HBNCVisitRequest + ): Response = + amritApiService.getAllDiseaseMosquitoFormVisits(formName, request) + suspend fun getAllByHhId(hhId: Long): List = + jsonResponseDao.getAllByHhId(hhId) + + + suspend fun getBottleList(hhId: Long): List { + val jsonList = jsonResponseDao.getFormJsonList(hhId) + + val result = mutableListOf() + + jsonList.forEach { formJson -> + val root = JSONObject(formJson) + val fields = root.optJSONObject("fields") + val date = fields?.optString("visit_date", "-") ?: "-" + val count = fields?.optString("is_net_distributed", "-") ?: "-" + + result.add( + BottleItem( + srNo = 0, + bottleNumber = count, + dateOfProvision = date + ) + ) + } + + return result.mapIndexed { index, item -> + item.copy(srNo = index + 1) + } + } + + suspend fun loadFormResponseJson(hhId: Long, formId: String): String? = + jsonResponseDao.getLatestForHhForm(hhId, formId)?.formDataJson + + suspend fun getUnsyncedForms(formId: String): List = + jsonResponseDao.getUnsyncedForms(formId) + + suspend fun saveDownloadedVisitList(list: List, formId: String) { + if (list.isEmpty()) return + val entityList = list.mapNotNull { item -> + try { + val visitDate = item.visitDate ?: return@mapNotNull null + val hhId = item.houseHoldId + val id = item.id + + val fieldsJson = JSONObject() + item.fields?.entrySet()?.forEach { (key, jsonElement) -> + val value = when { + jsonElement.isJsonNull -> JSONObject.NULL + jsonElement.isJsonPrimitive -> { + val p = jsonElement.asJsonPrimitive + when { + p.isBoolean -> p.asBoolean + p.isNumber -> p.asNumber + p.isString -> p.asString + else -> p.asString + } + } + else -> jsonElement.toString() + } + fieldsJson.put(key, value) + } + + val fullJson = JSONObject().apply { + put("id", id) + put("formId", formId) + put("houseHoldId", hhId) + put("visitDate", visitDate) + put("beneficiaryId", item.beneficiaryId) + put("fields", fieldsJson) + } + + MosquitoNetFormResponseJsonEntity( + id = id, + hhId = hhId, + formId = formId, + visitDate = visitDate, + formDataJson = fullJson.toString(), + isSynced = true, + version = 1 + ) + } catch (e: Exception) { + e.printStackTrace() + null + } + } + + if (entityList.isNotEmpty()) { + insertAllWithLimit(entityList) + } + } + + @WorkerThread + private suspend fun insertAllWithLimit(list: List) { + list.forEach { jsonResponseDao.insertWithLimit(it) } + } + + suspend fun insertFormResponse(entity: MosquitoNetFormResponseJsonEntity): Boolean { + return jsonResponseDao.insertWithLimit(entity) + } + + suspend fun syncFormToServer( + userName: String, + formName: String, + form: MosquitoNetFormResponseJsonEntity + ): Boolean { + return try { + val request = FormSubmitRequestMapper.fromEntity(form, userName) ?: return false + val response = amritApiService.submitDiseaseMosquitoForm(formName, listOf(request)) + response.isSuccessful + } catch (_: Exception) { + false + } + } + + suspend fun markFormAsSynced(id: Int) { + val timestamp = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH).format(Date()) + jsonResponseDao.markAsSynced(id, timestamp) + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/repositories/dynamicRepo/NCDFollowUpFormRepository.kt b/app/src/main/java/org/piramalswasthya/sakhi/repositories/dynamicRepo/NCDFollowUpFormRepository.kt new file mode 100644 index 000000000..7a2a905e7 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/repositories/dynamicRepo/NCDFollowUpFormRepository.kt @@ -0,0 +1,238 @@ +package org.piramalswasthya.sakhi.repositories.dynamicRepo + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.json.JSONObject +import org.piramalswasthya.sakhi.database.room.InAppDb +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao +import org.piramalswasthya.sakhi.helpers.Konstants +import org.piramalswasthya.sakhi.model.dynamicEntity.* +import org.piramalswasthya.sakhi.model.dynamicModel.HBNCVisitRequest +import org.piramalswasthya.sakhi.network.AmritApiService +import org.piramalswasthya.sakhi.utils.HelperUtil +import timber.log.Timber +import java.io.IOException +import javax.inject.Inject +import javax.inject.Named +import javax.inject.Singleton + +@Singleton +class NCDFollowUpFormRepository @Inject constructor( + @ApplicationContext private val context: Context, + @Named("gsonAmritApi") private val amritApiService: AmritApiService, + private val pref: PreferenceDao, + private val db: InAppDb +) { + + private val jsonResponseDao = db.NCDReferalFormResponseJsonDao() + private val formSchemaDao = db.formSchemaDao() + + /* ---------------- SCHEMA ---------------- */ + suspend fun getSavedSchema(formId: String): FormSchemaEntity? = formSchemaDao.getSchema(formId) + + suspend fun getFormSchema(formId: String): FormSchemaDto? = + withContext(Dispatchers.IO) { + + val localEntity = getSavedSchema(formId) + val localSchema = localEntity?.let { + FormSchemaDto.fromJson(it.schemaJson) + } + + try { + val response = amritApiService.fetchFormSchema( + formId, + pref.getCurrentLanguage().symbol + ) + + if (response.isSuccessful && response.body()?.success == true) { + response.body()?.data?.let { apiSchema -> + val local = formSchemaDao.getSchema(apiSchema.formId) + if (local == null || local.version < apiSchema.version) { + saveFormSchemaToDb(apiSchema) + } + return@withContext apiSchema + } + } + } catch (_: Exception) { + } + + // fallback (same as pehle local return) + return@withContext localSchema + } + + private suspend fun saveFormSchemaToDb(schema: FormSchemaDto) { + formSchemaDao.insertOrUpdate( + FormSchemaEntity( + formId = schema.formId, + formName = schema.formName, + language = pref.getCurrentLanguage().symbol, + version = schema.version, + schemaJson = schema.toJson() + ) + ) + } + + /* ---------------- DOWNSYNC ---------------- */ + suspend fun fetchFormsFromServer( + formId: String, + userName: String + ): List { + return try { + + val user = pref.getLoggedInUser() + ?: throw IllegalStateException("No user logged in") + + val hbncRequest = HBNCVisitRequest( + fromDate = HelperUtil.getCurrentDate(Konstants.defaultTimeStamp), + toDate = HelperUtil.getCurrentDate(), + pageNo = 0, + ashaId = user.userId, + userName = user.userName + ) + + val response = amritApiService.getAllFormNCDFollowUp(hbncRequest) + + if (response.isSuccessful) { + val responseBody = response.body() + val bodyList: List = responseBody?.data ?: emptyList() + + bodyList.map { + NCDReferalFormResponseJsonEntity( + id = 0, + benId = it.benId, + hhId = it.hhId, + visitNo = it.visitNo, + followUpNo = it.followUpNo, + treatmentStartDate = it.treatmentStartDate, + followUpDate = it.followUpDate, + diagnosisCodes = it.diagnosisCodes, + formId = it.formId, + version = it.version, + formDataJson = it.formDataJson, + isSynced = true + ) + } + } else emptyList() + } catch (e: IOException) { + Timber.w(e, "fetchFormsFromServer failed") + throw e + } + } + + + suspend fun saveDownloadedForms(forms: List) { + forms.forEach { entity -> + val existing = jsonResponseDao.getFormResponse(entity.benId, entity.visitNo, entity.followUpNo) + if (existing == null || existing.updatedAt < entity.updatedAt) { + jsonResponseDao.insertFormResponse(entity) + Timber.d("📥 Saved form id=${entity.id} benId=${entity.benId}") + } + } + } + + /* ---------------- LOCAL SAVE ---------------- */ + suspend fun saveVisitOrFollowUp( + benId: Long, + hhId: Long, + visitNo: Int, + followUpNo: Int, + treatmentStartDate: String, + followUpDate: String? = null, + diagnosisList: List, + formId: String, + formJson: String, + version: Int = 1 + ) { + val entity = NCDReferalFormResponseJsonEntity( + benId = benId, + hhId = hhId, + visitNo = visitNo, + followUpNo = followUpNo, + treatmentStartDate = treatmentStartDate, + followUpDate = followUpDate, + diagnosisCodes = diagnosisList.joinToString(","), + formId = formId, + version = version, + formDataJson = formJson, + isSynced = false + ) + insertOrUpdateFormResponse(entity) + } + + private suspend fun insertOrUpdateFormResponse(entity: NCDReferalFormResponseJsonEntity) { + val existing = jsonResponseDao.getFormResponse(entity.benId, entity.visitNo, entity.followUpNo) + val updated = existing?.let { mergeFollowUp(existing, entity) } ?: entity + jsonResponseDao.insertFormResponse(updated) + } + + private fun mergeFollowUp( + existing: NCDReferalFormResponseJsonEntity, + newEntity: NCDReferalFormResponseJsonEntity + ): NCDReferalFormResponseJsonEntity { + return try { + val existingJson = JSONObject(existing.formDataJson) + val newJson = JSONObject(newEntity.formDataJson) + + val mergedFields = JSONObject() + + existingJson.optJSONObject("fields")?.let { existingFields -> + existingFields.keys().forEach { key -> + mergedFields.put(key, existingFields.get(key)) + } + } + + newJson.optJSONObject("fields")?.let { newFields -> + newFields.keys().forEach { key -> + mergedFields.put(key, newFields.get(key)) + } + } + + val mergedJson = JSONObject().apply { + put("formId", newJson.optString("formId")) + put("beneficiaryId", newJson.optLong("beneficiaryId")) + put("houseHoldId", newJson.optLong("houseHoldId")) + put("visitNo", newJson.optInt("visitNo")) + put("followUpNo", newJson.optInt("followUpNo")) + put("fields", mergedFields) + } + + newEntity.copy( + formDataJson = mergedJson.toString(), + updatedAt = System.currentTimeMillis() + ) + + } catch (e: Exception) { + Timber.e(e, "mergeFollowUp failed") + newEntity.copy(updatedAt = System.currentTimeMillis()) + } + } + + + /* ---------------- HISTORY ---------------- */ + suspend fun getAllVisitsByBeneficiary(benId: Long, formId: String): List = + jsonResponseDao.getAllVisitsByBeneficiary(benId, formId) + + /* ---------------- SYNC ---------------- */ + suspend fun getUnsyncedForms(formId: String): List = + jsonResponseDao.getUnsyncedForms(formId) + + suspend fun markFormAsSynced(id: Int) { + jsonResponseDao.markAsSynced(id, System.currentTimeMillis()) + } + + suspend fun syncFormToServer( + userName: String, + formName: String, + request: FormNCDFollowUpSubmitRequest + ): Boolean { + return try { + val response = amritApiService.submitNCDFollowUp( listOf(request)) + response.isSuccessful + } catch (e: Exception) { + Timber.e(e, "syncFormToServer failed") + false + } + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/ui/BindingUtils.kt b/app/src/main/java/org/piramalswasthya/sakhi/ui/BindingUtils.kt index 2f8708e65..3b643e94a 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/ui/BindingUtils.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/ui/BindingUtils.kt @@ -1,9 +1,12 @@ package org.piramalswasthya.sakhi.ui +import android.content.Context import android.net.Uri import android.os.Build +import android.provider.OpenableColumns import android.text.Html import android.text.InputType +import android.util.Log import android.view.View import android.view.ViewGroup import android.view.animation.Animation @@ -12,10 +15,14 @@ import android.view.animation.RotateAnimation import android.widget.* import android.widget.RadioGroup.LayoutParams import androidx.annotation.RequiresApi +import androidx.annotation.StringRes import androidx.cardview.widget.CardView import androidx.constraintlayout.widget.ConstraintLayout import androidx.databinding.BindingAdapter import com.bumptech.glide.Glide +import com.bumptech.glide.load.engine.DiskCacheStrategy +import com.bumptech.glide.signature.ObjectKey +import com.google.android.material.button.MaterialButton import com.google.android.material.color.MaterialColors import com.google.android.material.divider.MaterialDivider import com.google.android.material.textfield.TextInputEditText @@ -24,6 +31,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.launch import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.configuration.IconDataset import org.piramalswasthya.sakhi.database.room.SyncState import org.piramalswasthya.sakhi.helpers.Konstants import org.piramalswasthya.sakhi.model.AncFormState @@ -34,17 +42,34 @@ import org.piramalswasthya.sakhi.model.Gender import org.piramalswasthya.sakhi.model.VaccineState import org.piramalswasthya.sakhi.model.VaccineState.* import timber.log.Timber +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale +fun IconDataset.Disease.getTitle(context: Context): String { + return context.getString(getTitleRes()) +} +@StringRes +fun IconDataset.Disease.getTitleRes(): Int { + return when (this) { + IconDataset.Disease.MALARIA -> R.string.icon_title_maleria + IconDataset.Disease.KALA_AZAR -> R.string.icon_title_ka + IconDataset.Disease.AES_JE -> R.string.icon_title_aes + IconDataset.Disease.FILARIA -> R.string.icon_title_filaria + IconDataset.Disease.LEPROSY -> R.string.icon_title_leprosy + IconDataset.Disease.DEWARMING -> R.string.deworming_title + } +} @BindingAdapter("vaccineState") fun ImageView.setVaccineState(syncState: VaccineState?) { syncState?.let { // visibility = View.VISIBLE val drawable = when (it) { - DONE -> R.drawable.ic_check_circle - MISSED -> R.drawable.ic_close + DONE -> R.drawable.ic_check_circle_green + MISSED -> R.drawable.ic_crossed_circle PENDING -> R.drawable.ic_add_circle - OVERDUE -> R.drawable.ic_overdue + OVERDUE -> R.drawable.ic_event_available UNAVAILABLE -> null } drawable?.let { it1 -> setImageResource(it1) } @@ -57,13 +82,16 @@ fun Button.setVaccineState(syncState: VaccineState?) { syncState?.let { visibility = View.VISIBLE when (it) { - PENDING, + PENDING -> { + text = resources.getString(R.string.vaccine_fill) + } + OVERDUE -> { - text = "FILL" + text = resources.getString(R.string.vaccine_fill) } DONE -> { - text = "VIEW" + text = resources.getString(R.string.view) } MISSED, @@ -74,6 +102,23 @@ fun Button.setVaccineState(syncState: VaccineState?) { } } +@BindingAdapter("formattedDate") +fun setFormattedDate(view: TextView, timestamp: Long?) { + timestamp?.let { + val sdf = SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH) + view.text = sdf.format(Date(it)) + } +} + + +@BindingAdapter("formattedDatewitheMonth") +fun setFormattedDateWithMonth(view: TextView, timestamp: Long?) { + timestamp?.let { + val sdf = SimpleDateFormat("dd-MM-yyyy , MMM", Locale.ENGLISH) + view.text = sdf.format(Date(it)) + } +} + @BindingAdapter("scope", "recordCount") fun TextView.setRecordCount(scope: CoroutineScope, count: Flow?) { @@ -160,7 +205,7 @@ fun LinearLayout.showRchIdOrNot(ben: BenBasicDomain?) { @BindingAdapter("textBasedOnNumMembers") fun TextView.textBasedOnNumMembers(numMembers: Int?) { numMembers?.let { - text = if (it > 0) "Add Member" else "Add Head of Family" + text = if (it > 0) resources.getString(R.string.str_add_member) else resources.getString(R.string.add_family_member) } } @@ -303,12 +348,34 @@ fun ImageView.setSyncState(syncState: SyncState?) { } } +@BindingAdapter("syncStateForBen") +fun ImageView.setSyncStateForBen(syncState: SyncState?) { + syncState?.let { + + val drawable = when (it) { + SyncState.UNSYNCED -> R.drawable.ic_unsynced + SyncState.SYNCING -> R.drawable.ic_syncing + SyncState.SYNCED -> R.drawable.ic_synced + } + setImageResource(drawable) + isClickable = it == SyncState.UNSYNCED + if (it == SyncState.SYNCING) startAnimation(rotate) + } ?: run { + visibility = View.INVISIBLE + } +} + + @BindingAdapter("benImage") fun ImageView.setBenImage(uriString: String?) { if (uriString == null) setImageResource(R.drawable.ic_person) else { - Glide.with(this).load(Uri.parse(uriString)).placeholder(R.drawable.ic_person).circleCrop() + Glide.with(this).load(Uri.parse(uriString)) + .signature(ObjectKey(System.currentTimeMillis() / 1000)) + .skipMemoryCache(true) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .placeholder(R.drawable.ic_person).circleCrop() .into(this) } } @@ -346,19 +413,26 @@ fun ImageView.setAncState(ancFormState: AncFormState?) { } @RequiresApi(Build.VERSION_CODES.N) -@BindingAdapter("cbac_name", "asteriskColor") -fun TextView.setAsteriskText(fieldName: String?, numAsterisk: Int?) { - +@BindingAdapter("cbac_name", "asteriskColor", "asteriskColorAlt", requireAll = false) +fun TextView.setAsteriskText(fieldName: String?, numAsterisk: Int?, numAsteriskAlt: Int?) { fieldName?.let { - numAsterisk?.let { - text = if (numAsterisk == 1) { + val count = numAsteriskAlt ?: numAsterisk + val useAlt = numAsteriskAlt != null + count?.let { + text = if (count == 1) { Html.fromHtml( - resources.getString(R.string.radio_title_cbac, fieldName), + resources.getString( + if (useAlt) R.string.radio_title_cbac_alt else R.string.radio_title_cbac, + fieldName + ), Html.FROM_HTML_MODE_LEGACY ) - } else if (numAsterisk == 2) { + } else if (count == 2) { Html.fromHtml( - resources.getString(R.string.radio_title_cbac_ds, fieldName), + resources.getString( + if (useAlt) R.string.radio_title_cbac_ds_alt else R.string.radio_title_cbac_ds, + fieldName + ), Html.FROM_HTML_MODE_LEGACY ) } else { @@ -385,7 +459,35 @@ fun TextInputLayout.setAsteriskFormText(required: Boolean?, title: String?) { } } } +fun checkFileSize(uri: Uri,context: Context) : Boolean { + val size = getFileSize(uri, context) + return size > 5 * 1024 * 1024 + +} +fun getFileSize(uri: Uri,context: Context): Long { + val cursor = context.contentResolver.query(uri, null, null, null, null) + return cursor?.use { + val sizeIndex = it.getColumnIndex(android.provider.OpenableColumns.SIZE) + if (sizeIndex != -1 && it.moveToFirst()) { + it.getLong(sizeIndex) + } else { + 0L + } + } ?: 0L +} +fun getByteArrayFromUri(uri: Uri,context: Context): ByteArray { + val inputStream = context.contentResolver.openInputStream(uri) + return inputStream?.readBytes() ?: byteArrayOf() +} + fun getFileName(uri: Uri,context: Context): String { + val cursor = context.contentResolver.query(uri, null, null, null, null) + cursor?.use { + val index = it.getColumnIndex(OpenableColumns.DISPLAY_NAME) + if (it.moveToFirst()) return it.getString(index) + } + return "file_${System.currentTimeMillis()}" +} @RequiresApi(Build.VERSION_CODES.N) @BindingAdapter("asteriskRequired", "hintText") fun TextView.setAsteriskTextView(required: Boolean?, title: String?) { @@ -402,6 +504,57 @@ fun TextView.setAsteriskTextView(required: Boolean?, title: String?) { } } } + } +@BindingAdapter(value = ["formattedSessionDate"], requireAll = false) +fun setFormattedSessionDate(textView: TextView, timestamp: Long?) { + if (timestamp == null) { + textView.text =textView.context.getString(R.string.session_date_n_a) + return + } + + val date = Date(timestamp) + val formatType = textView.tag as? String ?: "default" + + val format = SimpleDateFormat("dd MMM yyyy", Locale.ENGLISH) + val formattedDate = format.format(date) + + textView.text = when (formatType) { + "default" -> textView.context.getString(R.string.session_date_format, formattedDate) + "monthYear" -> { + val monthFormat = SimpleDateFormat("MMMM - yyyy", Locale.ENGLISH) + val monthYear = monthFormat.format(date) + textView.context.getString(R.string.uwin_session_format, monthYear) + } + else -> textView.context.getString(R.string.session_date_format, formattedDate) + } +} + +@BindingAdapter(value = ["visibleIfAgeAbove30AndAliveAge", "isDeath"], requireAll = true) +fun Button.visibleIfAgeAbove30AndAlive(age: Int?, isDeath: String?) { + val shouldShow = (age ?: 0) >= 30 && isDeath.equals("false", ignoreCase = true) + visibility = if (shouldShow) View.VISIBLE else View.GONE +} + +@BindingAdapter(value = ["visibleIfEligibleFemale", "isDeath", "reproductiveStatusId", "gender"], requireAll = true) +fun Button.visibleIfEligibleFemale(age: Int?, isDeath: String?, reproductiveStatusId: Int?, gender: String?) { + + val shouldShow = + (gender.equals("female", ignoreCase = true)) && + ((age ?: 0) in 20..49) && + (reproductiveStatusId == 1 || reproductiveStatusId == 2) && + (isDeath == null || isDeath.equals("false", ignoreCase = true)) + + visibility = if (shouldShow) View.VISIBLE else View.GONE +} + +@BindingAdapter("dynamicBackground") +fun setDynamicBackground(view: View, isEligible: Boolean) { + if (isEligible) { + view.setBackgroundResource(R.color.md_theme_light_error) + } else { + view.background = null + } +} diff --git a/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/AbhaIdActivity.kt b/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/AbhaIdActivity.kt index f96af0f5d..f4c78c6ae 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/AbhaIdActivity.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/AbhaIdActivity.kt @@ -3,6 +3,7 @@ package org.piramalswasthya.sakhi.ui.abha_id_activity import android.content.Context import android.os.Bundle import android.os.CountDownTimer +import android.view.MotionEvent import android.view.View import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity @@ -14,10 +15,12 @@ import dagger.hilt.InstallIn import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.EntryPointAccessors import dagger.hilt.components.SingletonComponent +import org.piramalswasthya.sakhi.BuildConfig import org.piramalswasthya.sakhi.R import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao import org.piramalswasthya.sakhi.databinding.ActivityAbhaIdBinding import org.piramalswasthya.sakhi.helpers.MyContextWrapper +import org.piramalswasthya.sakhi.helpers.TapjackingProtectionHelper import org.piramalswasthya.sakhi.network.interceptors.TokenInsertAbhaInterceptor import org.piramalswasthya.sakhi.ui.abha_id_activity.AbhaIdViewModel.State import timber.log.Timber @@ -38,10 +41,12 @@ class AbhaIdActivity : AppCompatActivity() { private var countDownTimer: CountDownTimer? = null override fun onCreate(savedInstanceState: Bundle?) { + TapjackingProtectionHelper.applyWindowSecurity(this) super.onCreate(savedInstanceState) Timber.d("onCreate Called") _binding = ActivityAbhaIdBinding.inflate(layoutInflater) setContentView(binding.root) + TapjackingProtectionHelper.enableTouchFiltering(this) setUpActionBar() mainViewModel.state.observe(this) { state -> @@ -52,32 +57,37 @@ class AbhaIdActivity : AppCompatActivity() { // Hide other views (if any) binding.navHostFragmentAbhaId.visibility = View.GONE binding.clError.visibility = View.GONE + } State.SUCCESS -> { binding.progressBarAbhaActivity.visibility = View.GONE binding.clError.visibility = View.GONE binding.navHostFragmentAbhaId.visibility = View.VISIBLE + } State.ERROR_NETWORK -> { binding.clError.visibility = View.VISIBLE binding.progressBarAbhaActivity.visibility = View.GONE binding.navHostFragmentAbhaId.visibility = View.GONE + } State.ERROR_SERVER -> { binding.clError.visibility = View.VISIBLE binding.progressBarAbhaActivity.visibility = View.GONE binding.navHostFragmentAbhaId.visibility = View.GONE + } } } mainViewModel.errorMessage.observe(this) { binding.textView5.text = it } + binding.btnTryAgain.setOnClickListener { - mainViewModel.generateAccessToken() + mainViewModel.generateAmritToken() } binding.toolbarMenuHome.setOnClickListener { @@ -95,6 +105,16 @@ class AbhaIdActivity : AppCompatActivity() { }.start() } + override fun onPause() { + super.onPause() + window.decorView.alpha = 0f + } + + override fun onResume() { + super.onResume() + window.decorView.alpha = 1f + } + override fun onSupportNavigateUp(): Boolean { return when (navController.currentDestination?.id) { R.id.generateMobileOtpFragment -> { @@ -115,8 +135,6 @@ class AbhaIdActivity : AppCompatActivity() { } fun updateActionBar(logoResource: Int, title: String? = null) { -// binding.ivToolbarAbha.setImageResource(logoResource) -// binding.toolbar.setLogo(logoResource) title?.let { binding.toolbar.title = null binding.tvToolbarAbha.text = it @@ -158,14 +176,14 @@ class AbhaIdActivity : AppCompatActivity() { private fun setUpActionBar() { setSupportActionBar(binding.toolbar) - supportActionBar?.setDisplayHomeAsUpEnabled(true) -// NavigationUI.setupWithNavController(binding.toolbar, navController) + supportActionBar?.setDisplayHomeAsUpEnabled(false) NavigationUI.setupActionBarWithNavController(this, navController) } override fun onDestroy() { super.onDestroy() TokenInsertAbhaInterceptor.setToken("") + TokenInsertAbhaInterceptor.setXToken("") intent.removeExtra("benId") intent.removeExtra("benRegId") countDownTimer?.cancel() @@ -192,4 +210,12 @@ class AbhaIdActivity : AppCompatActivity() { ) ) } + + override fun dispatchTouchEvent(ev: MotionEvent?): Boolean { + return if (TapjackingProtectionHelper.isTouchAllowed(this, ev)) { + super.dispatchTouchEvent(ev) + } else { + false + } + } } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/AbhaIdViewModel.kt b/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/AbhaIdViewModel.kt index ecacaebf0..86d8c5ec0 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/AbhaIdViewModel.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/AbhaIdViewModel.kt @@ -12,6 +12,7 @@ import org.piramalswasthya.sakhi.network.NetworkResult import org.piramalswasthya.sakhi.network.interceptors.TokenInsertAbhaInterceptor import org.piramalswasthya.sakhi.repositories.AbhaIdRepo import org.piramalswasthya.sakhi.repositories.UserRepo +import org.piramalswasthya.sakhi.utils.Log import javax.inject.Inject @HiltViewModel @@ -19,8 +20,7 @@ class AbhaIdViewModel @Inject constructor( private val abhaIdRepo: AbhaIdRepo, private val prefDao: PreferenceDao, private val userRepo: UserRepo -) : - ViewModel() { +) : ViewModel() { enum class State { LOADING_TOKEN, @@ -52,7 +52,7 @@ class AbhaIdViewModel @Inject constructor( get() = _authCert!! - private fun generateAmritToken() { + fun generateAmritToken() { _state.value = State.LOADING_TOKEN val user = prefDao.getLoggedInUser() viewModelScope.launch { @@ -61,6 +61,7 @@ class AbhaIdViewModel @Inject constructor( generateAccessToken() } else { _state.value = State.ERROR_SERVER + Log.e("Error","Server error ${userRepo.refreshTokenTmc(user.userName, user.password)}") } } } @@ -72,9 +73,9 @@ class AbhaIdViewModel @Inject constructor( when (val result = abhaIdRepo.getAccessToken()) { is NetworkResult.Success -> { _accessToken = result.data + TokenInsertAbhaInterceptor.setToken(accessToken.accessToken) generatePublicKey() _state.value = State.SUCCESS - TokenInsertAbhaInterceptor.setToken(accessToken.accessToken) } is NetworkResult.Error -> { diff --git a/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_id/AadhaarIdFragment.kt b/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_id/AadhaarIdFragment.kt index 4e1181077..63fdd7415 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_id/AadhaarIdFragment.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_id/AadhaarIdFragment.kt @@ -5,6 +5,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.activity.OnBackPressedCallback +import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.navigation.NavController @@ -24,12 +25,13 @@ class AadhaarIdFragment : Fragment() { private val binding: FragmentAadhaarIdBinding get() = _binding!! - private val viewModel: AadhaarIdViewModel by viewModels({ requireActivity() }) + private lateinit var abhaMode: AadhaarIdViewModel.Abha + val viewModel: AadhaarIdViewModel by viewModels({ requireActivity() }) private lateinit var navController: NavController private val aadhaarNavController by lazy { val navHostFragment: NavHostFragment = - childFragmentManager.findFragmentById(R.id.nav_host_fragment_aadhaar_id) as NavHostFragment + childFragmentManager.findFragmentById(R.id.nav_host_fragment_find_abha) as NavHostFragment navHostFragment.navController } @@ -58,6 +60,37 @@ class AadhaarIdFragment : Fragment() { onBackPressedCallback ) + binding.createToggle.setOnClickListener { + binding.searchToggle.setTextColor(ContextCompat.getColor(requireContext(), R.color.md_theme_dark_shadow)) + binding.createToggle.setTextColor(ContextCompat.getColor(requireContext(), R.color.md_theme_light_onSecondary)) + binding.createToggle.setBackgroundResource(R.drawable.background_rectangle_lightest_grey_20) + binding.tilAadhaarVerifyDropdown.visibility = View.VISIBLE + binding.actvAadharVerificationDropdown.visibility = View.VISIBLE + binding.searchToggle.setBackgroundResource(0) + binding.createToggle.setTypeface(resources.getFont(R.font.opensans_semibold)) + binding.searchToggle.setTypeface(resources.getFont(R.font.opensans_regular)) + binding.navHostFragmentFindAbha.visibility = View.GONE + binding.navHostFragmentAadhaarId.visibility = View.VISIBLE + + viewModel.selectedNavToggle = "navHostFragmentAadhaarId" + + } + binding.searchToggle.setOnClickListener { + binding.createToggle.setBackgroundResource(0) + binding.searchToggle.setTextColor(ContextCompat.getColor(requireContext(), R.color.md_theme_light_onSecondary)) + binding.createToggle.setTextColor(ContextCompat.getColor(requireContext(), R.color.md_theme_dark_shadow)) + binding.tilAadhaarVerifyDropdown.visibility = View.GONE + binding.actvAadharVerificationDropdown.visibility = View.GONE + + binding.searchToggle.setBackgroundResource(R.drawable.background_rectangle_lightest_grey_20) + binding.createToggle.setTypeface(resources.getFont(R.font.opensans_regular)) + binding.searchToggle.setTypeface(resources.getFont(R.font.opensans_semibold)) + binding.navHostFragmentAadhaarId.visibility = View.GONE + binding.navHostFragmentFindAbha.visibility = View.VISIBLE + viewModel.selectedNavToggle = "navHostFragmentFindAbha" + + } + binding.rgGovAsha.setOnCheckedChangeListener { _, id -> when (id) { R.id.rb_asha -> { @@ -87,6 +120,10 @@ class AadhaarIdFragment : Fragment() { } } + viewModel.abhaMode.observe(viewLifecycleOwner) { mode-> + abhaMode = mode + } + viewModel.state.observe(viewLifecycleOwner) { state -> when (state!!) { State.IDLE -> {} @@ -95,10 +132,10 @@ class AadhaarIdFragment : Fragment() { binding.pbLoadingAadharId.visibility = View.VISIBLE binding.clError.visibility = View.INVISIBLE } - State.SUCCESS -> { if (viewModel.userType.value == "ASHA") { viewModel.resetState() + if (viewModel.verificationType.value == "OTP") { findNavController().navigate( AadhaarIdFragmentDirections.actionAadhaarIdFragmentToAadhaarOtpFragment( @@ -108,7 +145,7 @@ class AadhaarIdFragment : Fragment() { } else if (viewModel.verificationType.value == "FP") { findNavController().navigate( AadhaarIdFragmentDirections.actionAadhaarIdFragmentToGenerateMobileOtpFragment( - viewModel.txnId + viewModel.txnId, viewModel.mobileNumber ) ) } @@ -135,12 +172,21 @@ class AadhaarIdFragment : Fragment() { State.ABHA_GENERATED_SUCCESS -> { findNavController().navigate( AadhaarIdFragmentDirections.actionAadhaarIdFragmentToCreateAbhaFragment( - viewModel.txnId + viewModel.txnId, "", "", "","" ) ) } } } + + viewModel.navigateToAadhaarConsent.observe(viewLifecycleOwner){ + if (it==true){ + findNavController().navigate( + AadhaarIdFragmentDirections.actionAadhaarIdFragmentToAadhaarConsentFragment() + ) + viewModel.navigateToAadhaarConsent(false) + } + } } override fun onStart() { @@ -158,5 +204,41 @@ class AadhaarIdFragment : Fragment() { _binding = null } + override fun onResume() { + super.onResume() + + + if (viewModel.selectedNavToggle == "navHostFragmentAadhaarId"){ + binding.searchToggle.setTextColor(ContextCompat.getColor(requireContext(), R.color.md_theme_dark_shadow)) + binding.createToggle.setTextColor(ContextCompat.getColor(requireContext(), R.color.md_theme_light_onSecondary)) + binding.createToggle.setBackgroundResource(R.drawable.background_rectangle_lightest_grey_20) + binding.tilAadhaarVerifyDropdown.visibility = View.VISIBLE + binding.actvAadharVerificationDropdown.visibility = View.VISIBLE + binding.searchToggle.setBackgroundResource(0) + binding.createToggle.setTypeface(resources.getFont(R.font.opensans_semibold)) + binding.searchToggle.setTypeface(resources.getFont(R.font.opensans_regular)) + binding.navHostFragmentFindAbha.visibility = View.GONE + binding.navHostFragmentAadhaarId.visibility = View.VISIBLE + + viewModel.selectedNavToggle = "navHostFragmentAadhaarId" + }else{ + binding.createToggle.setBackgroundResource(0) + binding.searchToggle.setTextColor(ContextCompat.getColor(requireContext(), R.color.md_theme_light_onSecondary)) + binding.createToggle.setTextColor(ContextCompat.getColor(requireContext(), R.color.md_theme_dark_shadow)) + binding.tilAadhaarVerifyDropdown.visibility = View.GONE + binding.actvAadharVerificationDropdown.visibility = View.GONE + + binding.searchToggle.setBackgroundResource(R.drawable.background_rectangle_lightest_grey_20) + binding.createToggle.setTypeface(resources.getFont(R.font.opensans_regular)) + binding.searchToggle.setTypeface(resources.getFont(R.font.opensans_semibold)) + binding.navHostFragmentAadhaarId.visibility = View.GONE + binding.navHostFragmentFindAbha.visibility = View.VISIBLE + viewModel.selectedNavToggle = "navHostFragmentFindAbha" + + + + } + } + } diff --git a/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_id/AadhaarIdViewModel.kt b/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_id/AadhaarIdViewModel.kt index ee62f9d2e..c7b2cbd03 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_id/AadhaarIdViewModel.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_id/AadhaarIdViewModel.kt @@ -23,8 +23,14 @@ AadhaarIdViewModel @Inject constructor( ABHA_GENERATED_SUCCESS } + enum class Abha { + NONE, + CREATE, + SEARCH + } + // val aadhaarVerificationTypeValues = arrayOf("Aadhaar ID", "Fingerprint") - val aadhaarVerificationTypeValues = arrayOf("Aadhaar ID") + val aadhaarVerificationTypeValues = arrayOf("Aadhaar No") private val _aadhaarVerificationTypes = MutableLiveData(aadhaarVerificationTypeValues[0]) val aadhaarVerificationTypes: LiveData get() = _aadhaarVerificationTypes @@ -45,6 +51,10 @@ AadhaarIdViewModel @Inject constructor( val userType: LiveData get() = _userType + private var _abhaMode = MutableLiveData(Abha.NONE) + val abhaMode: LiveData + get() = _abhaMode + private var _verificationType = MutableLiveData("OTP") val verificationType: LiveData get() = _verificationType @@ -55,16 +65,47 @@ AadhaarIdViewModel @Inject constructor( private var _txnId: String? = null val txnId: String - get() = _txnId!! + get() = _txnId?:"" + + private var _otpTxnId: String? = null + val otpTxnId: String + get() = _otpTxnId?:"" private var _mobileNumber: String? = null val mobileNumber: String - get() = _mobileNumber!! + get() = _mobileNumber?:"" + + private var _selectedAbhaIndex: String? = null + val selectedAbhaIndex: String + get() = _selectedAbhaIndex?:"" + + private var _aadhaarNumber: String? = null + val aadhaarNumber: String + get() = _aadhaarNumber?:"" private val _errorMessage = MutableLiveData(null) val errorMessage: LiveData get() = _errorMessage + private val _beneficiaryName = MutableLiveData(null) + val beneficiaryName: LiveData + get() = _beneficiaryName + + private val _navigateToAadhaarConsent = MutableLiveData(false) + val navigateToAadhaarConsent: LiveData + get() = _navigateToAadhaarConsent + + + private val _consentChecked = MutableLiveData(false) + val consentChecked: LiveData + get() = _consentChecked + + private var _otpMobileNumberMessage: String? = null + val otpMobileNumberMessage: String + get() = _otpMobileNumberMessage?:"" + + var selectedNavToggle:String = "navHostFragmentAadhaarId" + fun resetState() { _state.value = State.IDLE } @@ -77,17 +118,49 @@ AadhaarIdViewModel @Inject constructor( _abhaResponse = abha } + fun setBeneficiaryName(name: String) { + _beneficiaryName.value = name + } + + fun setConsentChecked(value: Boolean) { + _consentChecked.value = value + } + fun setState(state: State) { _state.value = state } + fun setAbhaMode(abha: Abha) { + _abhaMode.value = abha + } + + fun navigateToAadhaarConsent(value: Boolean) { + _navigateToAadhaarConsent.value = value + } + fun setMobileNumber(mobileNumber: String) { _mobileNumber = mobileNumber } + fun setAadhaarNumber(aadhaarNumber: String) { + _aadhaarNumber = aadhaarNumber + } + + fun setSelectedAbhaIndex(abhaIndex: String) { + _selectedAbhaIndex = abhaIndex + } + + fun setOtpTxnId(txnId: String) { + _otpTxnId = txnId + } + fun setTxnId(txnId: String) { _txnId = txnId } + fun setOTPMsg(msg: String) { + _otpMobileNumberMessage = msg + } + fun setUserType(userType: String) { _userType.value = userType diff --git a/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_id/aadhaar_consent/AbhaConsentFragment.kt b/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_id/aadhaar_consent/AbhaConsentFragment.kt new file mode 100644 index 000000000..059b6e4fd --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_id/aadhaar_consent/AbhaConsentFragment.kt @@ -0,0 +1,156 @@ +package org.piramalswasthya.sakhi.ui.abha_id_activity.aadhaar_id.aadhaar_consent + +import android.graphics.Typeface +import android.os.Bundle +import android.text.Spannable +import android.text.SpannableString +import android.text.style.ForegroundColorSpan +import android.text.style.StyleSpan +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.navigation.NavController +import androidx.navigation.Navigation.findNavController +import androidx.recyclerview.widget.LinearLayoutManager +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.adapters.AadhaarConsentAdapter +import org.piramalswasthya.sakhi.databinding.FragmentAbhaConsentBinding +import org.piramalswasthya.sakhi.model.AadhaarConsentModel +import org.piramalswasthya.sakhi.ui.abha_id_activity.AbhaIdActivity +import org.piramalswasthya.sakhi.ui.abha_id_activity.aadhaar_id.AadhaarIdViewModel + + +class AbhaConsentFragment : Fragment() { + + private var _binding: FragmentAbhaConsentBinding? = null + private val binding: FragmentAbhaConsentBinding + get() = _binding!! + + val viewModel: AbhaConsentViewModel by viewModels({ requireActivity() }) + private val parentViewModel: AadhaarIdViewModel by viewModels({ requireActivity() }) + private var count: Int = 0 + private lateinit var navController: NavController + + lateinit var recyclerList:List + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + _binding = FragmentAbhaConsentBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + navController = findNavController(view) + setUpAadhaarConsentRvAdapter() + binding.btnAccept.isEnabled = true + binding.btnAccept.setOnClickListener { + parentViewModel.setConsentChecked(true) + navController.navigateUp() + } + } + + private fun setUpAadhaarConsentRvAdapter() { + recyclerList = getConsentList() + val rvLayoutManager = LinearLayoutManager(context) + binding.recyclerView.layoutManager = rvLayoutManager + val rvAdapter = + AadhaarConsentAdapter(AadhaarConsentAdapter.ConsentClickListener { item, position -> + + if (position == 0){ + if (item.checked){ + recyclerList.forEach { + it.checked = true + } + binding.recyclerView.adapter?.notifyDataSetChanged() + binding.btnAccept.isEnabled = true + }else{ + recyclerList.forEach { + it.checked = false + } + binding.recyclerView.adapter?.notifyDataSetChanged() + binding.btnAccept.isEnabled = false + + } + }else{ + if (item.checked) { + count += 1 + if (count >= 7) { + binding.btnAccept.isEnabled = true + } + } else { + count -= 1 + binding.btnAccept.isEnabled = false + } + } + + }) + binding.recyclerView.adapter = rvAdapter + (binding.recyclerView.adapter as AadhaarConsentAdapter?)?.submitList(recyclerList) + } + + + fun getConsentList(): List { + val consent6 = resources.getString(R.string.str_aadhaar_consent_6) + val userName = viewModel.currentUser?.name ?: "" + val updatedText = consent6.replace("@ashaName", userName) + var usernameBoldItalic = formatUserNameBoldItalic(updatedText, userName) + + var consent7 = resources.getString(R.string.str_aadhaar_consent_7) + val beneficiaryName = parentViewModel.beneficiaryName.value ?: "" + val updatedText1 = consent7.replace("@beneficiaryName", beneficiaryName) + var beneficiaryNameBoldItalic = formatUserNameBoldItalic(updatedText1, beneficiaryName) + + return listOf( + AadhaarConsentModel(resources.getString(R.string.str_aadhaar_consent_1)), + AadhaarConsentModel(resources.getString(R.string.str_aadhaar_consent_2)), + AadhaarConsentModel(resources.getString(R.string.str_aadhaar_consent_3)), + AadhaarConsentModel(resources.getString(R.string.str_aadhaar_consent_4)), + AadhaarConsentModel(resources.getString(R.string.str_aadhaar_consent_5)), + AadhaarConsentModel(usernameBoldItalic), + AadhaarConsentModel(beneficiaryNameBoldItalic) + ) + } + + + override fun onStart() { + super.onStart() + activity?.let { + (it as AbhaIdActivity).updateActionBar( + R.drawable.ic__abha_logo_v1_24, + "Consent" + ) + } + } + + fun formatUserNameBoldItalic(fullText: String, userName: String): SpannableString { + val spannable = SpannableString(fullText) + val startIndex = fullText.indexOf(userName) + val endIndex = startIndex + userName.length + if (startIndex != -1) { + spannable.setSpan( + StyleSpan(Typeface.BOLD_ITALIC), + startIndex, + endIndex, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + + spannable.setSpan( + ForegroundColorSpan( + ContextCompat.getColor( + requireContext(), + R.color.md_theme_light_primary + ) + ), + startIndex, + endIndex, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + } + return spannable + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_id/aadhaar_consent/AbhaConsentViewModel.kt b/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_id/aadhaar_consent/AbhaConsentViewModel.kt new file mode 100644 index 000000000..c9b54948b --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_id/aadhaar_consent/AbhaConsentViewModel.kt @@ -0,0 +1,15 @@ +package org.piramalswasthya.sakhi.ui.abha_id_activity.aadhaar_id.aadhaar_consent + +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import org.piramalswasthya.sakhi.database.shared_preferences.PreferenceDao +import javax.inject.Inject + +@HiltViewModel +class AbhaConsentViewModel @Inject constructor(private val pref: PreferenceDao) : ViewModel() { + + val currentUser = pref.getLoggedInUser() + + + +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_id/aadhaar_num_asha/AadhaarNumberAshaFragment.kt b/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_id/aadhaar_num_asha/AadhaarNumberAshaFragment.kt index 0e3b9aab5..737369c61 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_id/aadhaar_num_asha/AadhaarNumberAshaFragment.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_id/aadhaar_num_asha/AadhaarNumberAshaFragment.kt @@ -1,6 +1,5 @@ package org.piramalswasthya.sakhi.ui.abha_id_activity.aadhaar_id.aadhaar_num_asha -import android.app.AlertDialog import android.os.Bundle import android.text.Editable import android.text.TextWatcher @@ -8,22 +7,31 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Toast +import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import com.google.gson.Gson import dagger.hilt.android.AndroidEntryPoint import org.piramalswasthya.sakhi.R import org.piramalswasthya.sakhi.activity_contracts.RDServiceCapturePIDContract -import org.piramalswasthya.sakhi.activity_contracts.RDServiceInfoContract -import org.piramalswasthya.sakhi.activity_contracts.RDServiceInitContract import org.piramalswasthya.sakhi.databinding.FragmentAadhaarNumberAshaBinding +import org.piramalswasthya.sakhi.helpers.AadhaarValidationUtils import org.piramalswasthya.sakhi.network.AadhaarVerifyBioRequest import org.piramalswasthya.sakhi.ui.abha_id_activity.aadhaar_id.AadhaarIdViewModel +import org.piramalswasthya.sakhi.utils.HelperUtil +import java.util.regex.Matcher +import java.util.regex.Pattern @AndroidEntryPoint class AadhaarNumberAshaFragment : Fragment() { + private var isPasswordVisible:Boolean = false + + var isValidAadhaar = false + var isValidMobile = false + var isValidBenName = false + private var _binding: FragmentAadhaarNumberAshaBinding? = null private val binding: FragmentAadhaarNumberAshaBinding get() = _binding!! @@ -32,14 +40,6 @@ class AadhaarNumberAshaFragment : Fragment() { private val viewModel: AadhaarNumberAshaViewModel by viewModels() - private val aadhaarDisclaimer by lazy { - AlertDialog.Builder(requireContext()) - .setTitle(resources.getString(R.string.individual_s_consent_for_creation_of_abha_number)) - .setMessage(resources.getString(R.string.aadhar_disclaimer_consent_text)) - .setPositiveButton(resources.getString(R.string.ok)) { dialog, _ -> dialog.dismiss() } - .create() - } - private val rdServiceCapturePIDContract = registerForActivityResult(RDServiceCapturePIDContract()) { Toast.makeText(requireContext(), "pid captured $it", Toast.LENGTH_SHORT).show() @@ -53,18 +53,6 @@ class AadhaarNumberAshaFragment : Fragment() { binding.pid.text = Gson().toJson(viewModel.responseData) } - private val rdServiceDeviceInfoContract = registerForActivityResult(RDServiceInfoContract()) { - binding.pid.text = Gson().toJson( - AadhaarVerifyBioRequest( - binding.tietAadhaarNumber.toString(), - "FMR", it.toString() - ) - ) - viewModel.verifyBio(binding.tietAadhaarNumber.text.toString(), it) - } - private val rdServiceInitContract = registerForActivityResult(RDServiceInitContract()) { - - } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? @@ -75,13 +63,11 @@ class AadhaarNumberAshaFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - var isValidAadhaar = false - + binding.tietAadhaarNumber.disableCopyPaste() parentViewModel.verificationType.observe(viewLifecycleOwner) { when (it) { "OTP" -> binding.btnVerifyAadhaar.text = resources.getString(R.string.generate_otp) "FP" -> { - checkApp() binding.btnVerifyAadhaar.text = resources.getString(R.string.validate_fp) } } @@ -90,7 +76,6 @@ class AadhaarNumberAshaFragment : Fragment() { val intent = requireActivity().intent val benId = intent.getLongExtra("benId", 0) - val benRegId = intent.getLongExtra("benRegId", 0) if (benId > 0) { viewModel.getBen(benId) @@ -100,17 +85,24 @@ class AadhaarNumberAshaFragment : Fragment() { } viewModel.ben.observe(viewLifecycleOwner) { - it?.let { + if(it!=null){ + binding.benNameTitle.visibility = View.VISIBLE binding.benName.visibility = View.VISIBLE - binding.benName.text = - String.format("%s%s%s", getString(R.string.generating_abha_for), " ", it) + binding.benName.text =it + parentViewModel.setBeneficiaryName(it) + binding.clBenName.visibility = View.GONE + + }else{ + binding.clBenName.visibility = View.VISIBLE } + } viewModel.state.observe(viewLifecycleOwner) { if (it == AadhaarIdViewModel.State.SUCCESS) { viewModel.resetState() } + parentViewModel.setAbhaMode(AadhaarIdViewModel.Abha.CREATE) parentViewModel.setState(it) } @@ -125,31 +117,127 @@ class AadhaarNumberAshaFragment : Fragment() { parentViewModel.setTxnId(it) } } - - binding.aadharConsentCheckBox.setOnCheckedChangeListener { _, ischecked -> - binding.btnVerifyAadhaar.isEnabled = isValidAadhaar && ischecked + viewModel.otpMobileNumberMessage.observe(viewLifecycleOwner) { + it?.let { + parentViewModel.setOTPMsg(it) + } } - binding.aadharDisclaimer.setOnClickListener { - aadhaarDisclaimer.show() + binding.clickview.setOnClickListener { + if(parentViewModel.beneficiaryName.value!=null && !parentViewModel.beneficiaryName.value.isNullOrBlank()) { + viewModel.aadhaarNumber.value = binding.tietAadhaarNumber.text.toString() + parentViewModel.navigateToAadhaarConsent(true) + }else{ + Toast.makeText(requireContext(),"Please Enter Beneficiary Name",Toast.LENGTH_SHORT).show() + } } + binding.tietAadhaarNumber.setEdiTextBackground(ContextCompat.getDrawable(requireContext(), R.drawable.selector_edittext_round_border_line)) + + binding.tietAadhaarNumber.setTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { +/* + Empty by design; required override with no behavior needed in this implementation. +*/ + } + + override fun onTextChanged(s: CharSequence?, p1: Int, p2: Int, p3: Int) { + + if(s.toString().isNullOrBlank()){ + binding.tvErrorText.visibility = View.GONE + binding.tvErrorText.text = "" + binding.ivValidAadhaar.setImageResource(R.drawable.ic_check_circle_grey) + }else if(AadhaarValidationUtils.isValidAadhaar(s.toString())){ + binding.tvErrorText.visibility = View.GONE + binding.tvErrorText.text = "" + binding.ivValidAadhaar.setImageResource(R.drawable.ic_check_circle_green) + binding.tietAadhaarNumber.setEdiTextBackground(ContextCompat.getDrawable(requireContext(), R.drawable.edit_text_round_border_line_green)) + + }else{ + binding.tvErrorText.visibility = View.VISIBLE + binding.tvErrorText.text = getString(R.string.str_invalid_aadhaar_no) + binding.ivValidAadhaar.setImageResource(R.drawable.ic_check_circle_grey) + binding.tietAadhaarNumber.setEdiTextBackground(ContextCompat.getDrawable(requireContext(), R.drawable.edit_text_round_border_line)) + } + + isValidAadhaar = (s != null) && AadhaarValidationUtils.isValidAadhaar(s.toString()) + binding.btnVerifyAadhaar.isEnabled = isValidAadhaar && isValidMobile + && (parentViewModel.consentChecked.value==true)//binding.aadharConsentCheckBox.isChecked + } + + override fun afterTextChanged(s: Editable?) { +/* + Empty by design; required override with no behavior needed in this implementation. +*/ + } + + }) + - binding.tietAadhaarNumber.addTextChangedListener(object : TextWatcher { + binding.tietMobileNumber.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { + //Currently Implementation is not required } override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { + /* + Empty by design; required override with no behavior needed in this implementation. + */ + } + + override fun afterTextChanged(s: Editable?) { + if((s != null) && isValidMobileNumber(s.toString())){ + binding.tilMobileNumber.error = null + binding.ivValidMobile.setImageResource(R.drawable.ic_check_circle_green) + + }else{ + binding.tilMobileNumber.error = getString(R.string.str_invalid_mobile_no) + binding.ivValidMobile.setImageResource(R.drawable.ic_check_circle_grey) + } + if(s.isNullOrEmpty()){ + binding.tilMobileNumber.error = null + } + isValidMobile = (s != null) && isValidMobileNumber(s.toString()) + if (isValidMobile) + parentViewModel.setMobileNumber(s.toString()) + binding.btnVerifyAadhaar.isEnabled = isValidAadhaar && isValidMobile + && (parentViewModel.consentChecked.value==true) // binding.aadharConsentCheckBox.isChecked } + }) + + binding.tietBenName.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { + /* + Empty by design; required override with no behavior needed in this implementation. + */ + } + + override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { + /* + Empty by design; required override with no behavior needed in this implementation. + */ } + override fun afterTextChanged(s: Editable?) { - isValidAadhaar = (s != null) && (s.length == 12) - binding.btnVerifyAadhaar.isEnabled = isValidAadhaar - && binding.aadharConsentCheckBox.isChecked + if((s != null) && HelperUtil.isValidName(s.toString())){ + binding.tvErrorTextBenName.visibility = View.GONE + binding.tvErrorTextBenName.text = "" + binding.ivValidBenName.setImageResource(R.drawable.ic_check_circle_green) + + }else{ + binding.tvErrorTextBenName.visibility = View.VISIBLE + binding.tvErrorTextBenName.text = getString(R.string.str_invalid_ben_name) + binding.ivValidBenName.setImageResource(R.drawable.ic_check_circle_grey) + } + isValidBenName = (s != null && s.length >= 3 && HelperUtil.isValidName(s.toString())) + if (isValidBenName) + parentViewModel.setBeneficiaryName(s.toString()) + binding.btnVerifyAadhaar.isEnabled = isValidAadhaar && isValidMobile + && (parentViewModel.consentChecked.value==true) } }) - // observing error message from parent and updating error text field + viewModel.errorMessage.observe(viewLifecycleOwner) { it?.let { binding.tvErrorText.visibility = View.VISIBLE @@ -157,18 +245,50 @@ class AadhaarNumberAshaFragment : Fragment() { viewModel.resetErrorMessage() } } + + binding.tietAadhaarNumber.setInputType(android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_PASSWORD) + binding.ivShowText.setBackgroundResource(R.drawable.ic_hide) + binding.ivShowText.setOnClickListener { + if (isPasswordVisible) { + binding.tietAadhaarNumber.setInputType(android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) + binding.ivShowText.setBackgroundResource(R.drawable.ic_show) + }else{ + binding.tietAadhaarNumber.setInputType(android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_PASSWORD) + binding.ivShowText.setBackgroundResource(R.drawable.ic_hide) + } + isPasswordVisible = !isPasswordVisible + binding.tietAadhaarNumber.setSelection(binding.tietAadhaarNumber.text?.length!!) + + } + + parentViewModel.consentChecked.observe(viewLifecycleOwner){ + if (it ==true){ + binding.tietAadhaarNumber.text= viewModel.aadhaarNumber.value + binding.btnVerifyAadhaar.isEnabled = isValidAadhaar && isValidMobile + && (parentViewModel.consentChecked.value==true) + binding.aadharDisclaimer.isChecked = true + } + } } private fun verifyAadhaar() { Toast.makeText(requireContext(), parentViewModel.verificationType.value, Toast.LENGTH_SHORT) .show() + parentViewModel.setAadhaarNumber(binding.tietAadhaarNumber.text.toString()) when (parentViewModel.verificationType.value) { "OTP" -> viewModel.generateOtpClicked(binding.tietAadhaarNumber.text.toString()) "FP" -> rdServiceCapturePIDContract.launch(Unit) } } - private fun checkApp() { - + fun isValidMobileNumber(str: String?): Boolean { + val regex = "^(\\+91[\\-\\s]?|0)?[6-9]\\d{9}$" + val p: Pattern = Pattern.compile(regex) + if (str == null) { + return false + } + val m: Matcher = p.matcher(str) + return m.matches() } + } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_id/aadhaar_num_asha/AadhaarNumberAshaViewModel.kt b/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_id/aadhaar_num_asha/AadhaarNumberAshaViewModel.kt index a95fc6365..386ceb611 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_id/aadhaar_num_asha/AadhaarNumberAshaViewModel.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_id/aadhaar_num_asha/AadhaarNumberAshaViewModel.kt @@ -40,14 +40,16 @@ class AadhaarNumberAshaViewModel @Inject constructor( val mobileNumber: LiveData get() = _mobileNumber - private val _aadhaarNumber = MutableLiveData(null) - val aadhaarNumber: LiveData - get() = _aadhaarNumber + val aadhaarNumber = MutableLiveData("") private val _errorMessage = MutableLiveData(null) val errorMessage: LiveData get() = _errorMessage + private val _otpMobileNumberMessage = MutableLiveData(null) + val otpMobileNumberMessage: LiveData + get() = _otpMobileNumberMessage + fun resetState() { _state.value = AadhaarIdViewModel.State.IDLE } @@ -64,10 +66,16 @@ class AadhaarNumberAshaViewModel @Inject constructor( private fun generateAadhaarOtp(aadhaarNo: String) { viewModelScope.launch { when (val result = - abhaIdRepo.generateOtpForAadhaarV2(AbhaGenerateAadhaarOtpRequest(aadhaarNo))) { + abhaIdRepo.generateAadhaarOtpV3(AbhaGenerateAadhaarOtpRequest( + "", + listOf("abha-enrol"), + "aadhaar", + aadhaarNo, + "aadhaar" + ))) { is NetworkResult.Success -> { _txnId.value = result.data.txnId - _mobileNumber.value = result.data.mobileNumber + _otpMobileNumberMessage.value = result.data.message _state.value = AadhaarIdViewModel.State.SUCCESS } diff --git a/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_id/aadhaar_num_gov/AadhaarNumberGovFragment.kt b/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_id/aadhaar_num_gov/AadhaarNumberGovFragment.kt index da9244d21..5d59099f7 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_id/aadhaar_num_gov/AadhaarNumberGovFragment.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_id/aadhaar_num_gov/AadhaarNumberGovFragment.kt @@ -14,7 +14,10 @@ import com.google.gson.Gson import dagger.hilt.android.AndroidEntryPoint import org.piramalswasthya.sakhi.databinding.FragmentAadhaarNumberGovBinding import org.piramalswasthya.sakhi.ui.abha_id_activity.aadhaar_id.AadhaarIdViewModel +import org.piramalswasthya.sakhi.utils.HelperUtil +import org.piramalswasthya.sakhi.utils.HelperUtil.findFragmentActivity import java.util.Calendar +import java.util.Locale @AndroidEntryPoint class AadhaarNumberGovFragment : Fragment() { @@ -95,6 +98,11 @@ class AadhaarNumberGovFragment : Fragment() { val today = Calendar.getInstance() binding.dateEt.setOnClickListener { + val activity = binding.dateEt.context.findFragmentActivity() + ?: return@setOnClickListener + val originalLocale = Locale.getDefault() + HelperUtil.setEnLocaleForDatePicker(activity) + val datePickerDialog = DatePickerDialog( it.context, { _, year, month, day -> @@ -108,6 +116,9 @@ class AadhaarNumberGovFragment : Fragment() { ) binding.tilEditText.error = null datePickerDialog.show() + datePickerDialog.setOnDismissListener { + HelperUtil.setOriginalLocaleForDatePicker(activity,originalLocale) + } checkValidity() } diff --git a/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_id/find_abha/FindAbhaFragment.kt b/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_id/find_abha/FindAbhaFragment.kt new file mode 100644 index 000000000..5aeaca712 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_id/find_abha/FindAbhaFragment.kt @@ -0,0 +1,275 @@ +package org.piramalswasthya.sakhi.ui.abha_id_activity.aadhaar_id.find_abha + +import android.app.AlertDialog +import android.content.Context +import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.inputmethod.InputMethodManager +import android.widget.AdapterView.OnItemClickListener +import android.widget.ArrayAdapter +import android.widget.AutoCompleteTextView +import android.widget.Toast +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import dagger.hilt.android.AndroidEntryPoint +import org.piramalswasthya.sakhi.R +import org.piramalswasthya.sakhi.databinding.FragmentFindAbhaBinding +import org.piramalswasthya.sakhi.network.Abha +import org.piramalswasthya.sakhi.ui.abha_id_activity.aadhaar_id.AadhaarIdViewModel +import org.piramalswasthya.sakhi.utils.HelperUtil +import java.util.regex.Matcher +import java.util.regex.Pattern + + +@AndroidEntryPoint +class FindAbhaFragment : Fragment() { + + private var _binding: FragmentFindAbhaBinding? = null + private val binding: FragmentFindAbhaBinding + get() = _binding!! + + private val parentViewModel: AadhaarIdViewModel by viewModels({ requireActivity() }) + + private val viewModel: FindAbhaViewModel by viewModels() + + private lateinit var adapterType: ArrayAdapter + + private var abhaList = mutableListOf() + private var abhaData = mutableListOf() + + private var selectedAbhaIndex = 0 + + var isValidMobile = false + var isValidAbha = false + var isConsent = false + var isValidBenName = false + private val aadhaarDisclaimer by lazy { + AlertDialog.Builder(requireContext()) + .setTitle(resources.getString(R.string.individual_s_consent_for_creation_of_abha_number)) + .setMessage(resources.getString(R.string.aadhar_disclaimer_consent_text)) + .setPositiveButton(resources.getString(R.string.ok)) { dialog, _ -> dialog.dismiss() } + .create() + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + _binding = FragmentFindAbhaBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val intent = requireActivity().intent + + val benId = intent.getLongExtra("benId", 0) + val benRegId = intent.getLongExtra("benRegId", 0) + + if (benId > 0) { + viewModel.getBen(benId) + } + + binding.btnSearchAbha.setOnClickListener { + binding.abhaDropdown.setText("") + binding.abhaDropdown.setAdapter(null) + abhaList.clear() + abhaData.clear() + selectedAbhaIndex = 0 + val imm = + activity?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager + imm?.hideSoftInputFromWindow(view.windowToken, 0) + searchAbha() + } + + binding.btnGenerateOtp.setOnClickListener { + viewModel.generateOtpClicked(selectedAbhaIndex.toString()) + val imm = + activity?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager + imm?.hideSoftInputFromWindow(view.windowToken, 0) + } + + viewModel.state.observe(viewLifecycleOwner) { + if (it == AadhaarIdViewModel.State.SUCCESS) { + viewModel.resetState() + } +// parentViewModel.setState(it) + } + + viewModel.fnlState.observe(viewLifecycleOwner) { + if (it == AadhaarIdViewModel.State.SUCCESS) { + viewModel.resetState() + } + parentViewModel.setAbhaMode(AadhaarIdViewModel.Abha.SEARCH) + parentViewModel.setState(it) + } + + viewModel.fnlTxnId.observe(viewLifecycleOwner) { + it?.let { + parentViewModel.setTxnId(it) + } + } + + viewModel.txnId.observe(viewLifecycleOwner) { + it?.let { + parentViewModel.setOtpTxnId(it) + } + } + + viewModel.abha.observe(viewLifecycleOwner) { + binding.tvErrorTextAbha.visibility = View.GONE + it?.let { + binding.tilSelectAbha.isEnabled = true + abhaData.addAll(it) + it.forEach { abha -> + abhaList.add(abha.name + " : " + abha.ABHANumber) + } + adapterType = ArrayAdapter( + requireContext(), + R.layout.dropdown_item_abha, + abhaList + ) + binding.abhaDropdown.setAdapter(adapterType) + } + } + + binding.aadharConsentCheckBox.setOnCheckedChangeListener { _, ischecked -> + isConsent = ischecked + enableButton() +// binding.btnGenerateOtp.isEnabled = isValidAbha && isValidMobile && ischecked + } + + binding.aadharDisclaimer.setOnClickListener { + if(parentViewModel.beneficiaryName.value!=null && !parentViewModel.beneficiaryName.value.isNullOrBlank()) { + parentViewModel.navigateToAadhaarConsent(true) + }else{ + Toast.makeText(requireContext(),"Please Enter Beneficiary Name", Toast.LENGTH_SHORT).show() + } + // aadhaarDisclaimer.show() + } + + binding.tietMobileNumber.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { + } + + override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { + } + + override fun afterTextChanged(s: Editable?) { + isValidMobile = (s != null) && isValidMobileNumber(s.toString()) + + if (isValidMobile) { + parentViewModel.setMobileNumber(s.toString()) + binding.tilMobileNumber.error = null + binding.btnSearchAbha.isEnabled = isValidMobile + enableButton() + binding.ivValidMobile.setImageResource(R.drawable.ic_check_circle_green) + + }else{ + binding.tilMobileNumber.error = getString(R.string.str_invalid_mobile_no) + binding.btnSearchAbha.isEnabled = isValidMobile + binding.ivValidMobile.setImageResource(R.drawable.ic_check_circle_grey) + } + if(s.isNullOrEmpty()){ + binding.tilMobileNumber.error = null + } + binding.tvErrorTextAbha.visibility = View.GONE + } + + }) + + (binding.tilSelectAbha.getEditText() as AutoCompleteTextView).onItemClickListener = + OnItemClickListener { adapterView, view, position, id -> + selectedAbhaIndex = abhaData[position].index + parentViewModel.setSelectedAbhaIndex(selectedAbhaIndex.toString()) + isValidAbha = true + enableButton() + } + + // observing error message from parent and updating error text field + viewModel.errorMessage.observe(viewLifecycleOwner) { + it?.let { + binding.tvErrorTextAbha.visibility = View.VISIBLE + binding.tvErrorTextAbha.text = it + viewModel.resetErrorMessage() + } + } + + viewModel.ben.observe(viewLifecycleOwner) { + if(it!=null){ + parentViewModel.setBeneficiaryName(it) + binding.clBenName.visibility = View.GONE + + }else{ + // binding.clBenName.visibility = View.VISIBLE + } + + } + binding.tietBenName.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { + } + + override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { + } + + override fun afterTextChanged(s: Editable?) { + if((s != null&& HelperUtil.isValidName(s.toString()))){ + binding.tvErrorTextBenName.visibility = View.GONE + binding.tvErrorTextBenName.text = "" + binding.ivValidBenName.setImageResource(R.drawable.ic_check_circle_green) + + }else{ + binding.tvErrorTextBenName.visibility = View.VISIBLE + binding.tvErrorTextBenName.text = getString(R.string.str_invalid_mobile_no) + binding.ivValidBenName.setImageResource(R.drawable.ic_check_circle_grey) + } + + isValidBenName = (s != null && s.length >= 3 && HelperUtil.isValidName(s.toString())) + if (isValidBenName) + parentViewModel.setBeneficiaryName(s.toString()) + binding.btnGenerateOtp.isEnabled = isValidAbha && isValidMobile //&& (parentViewModel.consentChecked.value==true) + + } + + }) + + viewModel.otpMobileNumberMessage.observe(viewLifecycleOwner) { + it?.let { + parentViewModel.setOTPMsg(it) + } + } + + parentViewModel.consentChecked.observe(viewLifecycleOwner){ + if (it ==true){ + binding.btnGenerateOtp.isEnabled = isValidAbha && isValidMobile && (parentViewModel.consentChecked.value==true) + } + } + } + + private fun searchAbha() { + viewModel.searchAbhaClicked(binding.tietMobileNumber.text.toString()) + } + + fun isValidMobileNumber(str: String?): Boolean { + val regex = "^(\\+91[\\-\\s]?|0)?[6-9]\\d{9}$" + val p: Pattern = Pattern.compile(regex) + if (str == null) { + return false + } + val m: Matcher = p.matcher(str) + return m.matches() + } + + private fun enableButton() { + if (isValidAbha && isValidMobile && isConsent) { + binding.btnGenerateOtp.isEnabled = true + } else { + binding.btnGenerateOtp.isEnabled = false + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_id/find_abha/FindAbhaViewModel.kt b/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_id/find_abha/FindAbhaViewModel.kt new file mode 100644 index 000000000..6dc0761a9 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_id/find_abha/FindAbhaViewModel.kt @@ -0,0 +1,166 @@ +package org.piramalswasthya.sakhi.ui.abha_id_activity.aadhaar_id.find_abha + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import org.piramalswasthya.sakhi.network.AadhaarVerifyBioRequest +import org.piramalswasthya.sakhi.network.Abha +import org.piramalswasthya.sakhi.network.AbhaGenerateAadhaarOtpRequest +import org.piramalswasthya.sakhi.network.CreateAbhaIdResponse +import org.piramalswasthya.sakhi.network.LoginGenerateOtpRequest +import org.piramalswasthya.sakhi.network.NetworkResult +import org.piramalswasthya.sakhi.network.SearchAbhaRequest +import org.piramalswasthya.sakhi.repositories.AbhaIdRepo +import org.piramalswasthya.sakhi.repositories.BenRepo +import org.piramalswasthya.sakhi.ui.abha_id_activity.aadhaar_id.AadhaarIdViewModel +import timber.log.Timber +import javax.inject.Inject + +@HiltViewModel +class FindAbhaViewModel @Inject constructor( + private var abhaIdRepo: AbhaIdRepo, + private var benRepo: BenRepo +) : ViewModel() { + + private val _state = MutableLiveData(AadhaarIdViewModel.State.IDLE) + val state: LiveData + get() = _state + + private val _fnlState = MutableLiveData(AadhaarIdViewModel.State.IDLE) + val fnlState: LiveData + get() = _fnlState + + private var _txnId = MutableLiveData(null) + val txnId: LiveData + get() = _txnId + + private var _fnlTxnId = MutableLiveData(null) + val fnlTxnId: LiveData + get() = _fnlTxnId + + private var _abha = MutableLiveData?>(null) + val abha: LiveData?> + get() = _abha + + private var _ben = MutableLiveData(null) + val ben: LiveData + get() = _ben + + private val _errorMessage = MutableLiveData(null) + val errorMessage: LiveData + get() = _errorMessage + + private val _otpMobileNumberMessage = MutableLiveData(null) + val otpMobileNumberMessage: LiveData + get() = _otpMobileNumberMessage + + fun resetState() { + _state.value = AadhaarIdViewModel.State.IDLE + } + + fun resetErrorMessage() { + _errorMessage.value = null + } + + fun searchAbhaClicked(mobileNo: String) { + _state.value = AadhaarIdViewModel.State.LOADING + searchAbha(mobileNo) + } + + private fun searchAbha(mobileNo: String) { + viewModelScope.launch { + when (val result = + abhaIdRepo.searchAbha( + SearchAbhaRequest( + listOf("search-abha"), + mobileNo + ) + )) { + is NetworkResult.Success -> { + _txnId.value = result.data.txnId + _abha.value = result.data.ABHA + _state.value = AadhaarIdViewModel.State.SUCCESS + } + + is NetworkResult.Error -> { + if (result.code == -1) { + _errorMessage.value = "Internet connectivity issue, try again later" + Timber.i(result.toString()) + _state.value = AadhaarIdViewModel.State.ERROR_NETWORK + } else if (result.code == -4) { + _errorMessage.value = "Some error occurred" +// _errorMessage.value = result.message + _state.value = AadhaarIdViewModel.State.ERROR_SERVER + } else { + _errorMessage.value = "No ABHA Found" +// _errorMessage.value = result.message + _state.value = AadhaarIdViewModel.State.ERROR_SERVER + } + } + + is NetworkResult.NetworkError -> { + _errorMessage.value = "Network Issue" + Timber.i(result.toString()) + _state.value = AadhaarIdViewModel.State.ERROR_NETWORK + } + } + } + } + + fun generateOtpClicked(index: String) { + _state.value = AadhaarIdViewModel.State.LOADING + generateAbhaOtp(index) + } + + private fun generateAbhaOtp(index: String) { + viewModelScope.launch { + when (val result = + abhaIdRepo.generateAbhaOtp( + LoginGenerateOtpRequest( + listOf("abha-login", "search-abha", "mobile-verify"), + "index", + index, + "abdm", + txnId.value!! + ) + )) { + is NetworkResult.Success -> { + _fnlTxnId.value = result.data.txnId + _fnlState.value = AadhaarIdViewModel.State.SUCCESS + _otpMobileNumberMessage.value = result.data.message + } + + is NetworkResult.Error -> { + _errorMessage.value = result.message + _fnlState.value = AadhaarIdViewModel.State.ERROR_SERVER + } + + is NetworkResult.NetworkError -> { + Timber.i(result.toString()) + _fnlState.value = AadhaarIdViewModel.State.ERROR_NETWORK + } + } + } + } + + fun getBen(benId: Long) { + viewModelScope.launch { + val beneficiary = benRepo.getBenFromId(benId) + if (beneficiary == null) { + _errorMessage.value = "Beneficiary not found" + return@launch + } + beneficiary.let { + it.firstName?.let { first -> + _ben.value = first + } + it.lastName?.let { last -> + _ben.value += " $last" + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_otp/AadhaarOtpFragment.kt b/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_otp/AadhaarOtpFragment.kt index 59bb70401..d8929370c 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_otp/AadhaarOtpFragment.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_otp/AadhaarOtpFragment.kt @@ -14,8 +14,12 @@ import androidx.navigation.fragment.findNavController import dagger.hilt.android.AndroidEntryPoint import org.piramalswasthya.sakhi.R import org.piramalswasthya.sakhi.databinding.FragmentAadhaarOtpBinding +import org.piramalswasthya.sakhi.helpers.AnalyticsHelper import org.piramalswasthya.sakhi.ui.abha_id_activity.AbhaIdActivity +import org.piramalswasthya.sakhi.ui.abha_id_activity.aadhaar_id.AadhaarIdViewModel import org.piramalswasthya.sakhi.ui.abha_id_activity.aadhaar_otp.AadhaarOtpViewModel.State +import javax.inject.Inject +import org.piramalswasthya.sakhi.ui.home_activity.all_ben.AllBenFragmentArgs @AndroidEntryPoint class AadhaarOtpFragment : Fragment() { @@ -26,20 +30,25 @@ class AadhaarOtpFragment : Fragment() { private val viewModel: AadhaarOtpViewModel by viewModels() + @Inject lateinit var analyticsHelper: AnalyticsHelper + + private val parentViewModel: AadhaarIdViewModel by viewModels({ requireActivity() }) + val args: AadhaarOtpFragmentArgs by lazy { AadhaarOtpFragmentArgs.fromBundle(requireArguments()) } - private var timer = object : CountDownTimer(30000, 1000) { + private var timer = object : CountDownTimer(60000, 1000) { override fun onTick(millisUntilFinished: Long) { val sec = millisUntilFinished / 1000 % 60 - binding.timerResendOtp.text = sec.toString() + binding.timerCount.text = "$sec" } - // When the task is over it will print 00:00:00 there override fun onFinish() { binding.resendOtp.isEnabled = true binding.timerResendOtp.visibility = View.INVISIBLE + binding.timerCount.visibility = View.INVISIBLE + binding.timerSeconds.visibility = View.INVISIBLE } } @@ -56,24 +65,47 @@ class AadhaarOtpFragment : Fragment() { super.onViewCreated(view, savedInstanceState) startResendTimer() binding.btnVerifyOTP.setOnClickListener { - viewModel.verifyOtpClicked(binding.tietAadhaarOtp.text.toString()) + if (parentViewModel.abhaMode.value == AadhaarIdViewModel.Abha.SEARCH) { + viewModel.verifyLoginOtpClicked(binding.otpView.text.toString()) + } else { + viewModel.verifyOtpClicked( + binding.otpView.text.toString(), + parentViewModel.mobileNumber + ) + } } binding.resendOtp.setOnClickListener { - viewModel.resendOtp() - startResendTimer() - } + binding.tvErrorText.visibility = View.GONE + if (parentViewModel.abhaMode.value == AadhaarIdViewModel.Abha.CREATE){ + viewModel.setMobileNumber(parentViewModel.mobileNumber) + } + if (parentViewModel.abhaMode.value == AadhaarIdViewModel.Abha.CREATE && parentViewModel.mobileNumber == viewModel.mobileNumber) { + viewModel.resendCreateAadhaarOtp(parentViewModel.aadhaarNumber) + startResendTimer() + + } else { + viewModel.resendOtpForSearchAbha(parentViewModel.selectedAbhaIndex,parentViewModel.otpTxnId) + startResendTimer() - binding.tietAadhaarOtp.addTextChangedListener(object : TextWatcher { - override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { } + } - override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { + if (parentViewModel.abhaMode.value == AadhaarIdViewModel.Abha.SEARCH) { + binding.textView6.text = "Verify ABHA OTP" + } + + + binding.otpView.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { } - override fun afterTextChanged(p0: Editable?) { - binding.btnVerifyOTP.isEnabled = p0 != null && p0.length == 6 + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { + binding.btnVerifyOTP.isEnabled = s != null && s.length == 6 + binding.tvErrorText.visibility = View.GONE } + override fun afterTextChanged(s: Editable) { + } }) @@ -91,15 +123,31 @@ class AadhaarOtpFragment : Fragment() { requireActivity().finish() } + viewModel.state2.observe(viewLifecycleOwner) { + if (it == AadhaarIdViewModel.State.SUCCESS) { + Toast.makeText( + context, + resources.getString(R.string.otp_resent), + Toast.LENGTH_SHORT + ).show() + } + } + viewModel.state.observe(viewLifecycleOwner) { state -> when (state!!) { State.IDLE -> { - binding.tvOtpMsg.text = String.format( - "%s%s", - resources.getString(R.string.otp_sent_to), - args.mobileNumber - ) binding.tvOtpMsg.visibility = View.VISIBLE + var string = getMobileNumber(parentViewModel.otpMobileNumberMessage) ?: "" + + if (parentViewModel.abhaMode.value == AadhaarIdViewModel.Abha.SEARCH) { + binding.tvOtpMsg.text = getString(R.string.str_abha_otp_number).replace("@mobileNumber", string) + binding.tvOTPNote.visibility = View.VISIBLE + }else{ + binding.tvOtpMsg.text = getString(R.string.str_aadhaar_otp_number).replace("@mobileNumber", string) + binding.tvOTPNote.visibility = View.VISIBLE + } + + } State.LOADING -> { @@ -109,9 +157,31 @@ class AadhaarOtpFragment : Fragment() { } State.OTP_VERIFY_SUCCESS -> { + if (parentViewModel.abhaMode.value == AadhaarIdViewModel.Abha.SEARCH) { + findNavController().navigate( + AadhaarOtpFragmentDirections.actionAadhaarOtpFragmentToCreateAbhaFragment( + viewModel.txnId, viewModel.name, viewModel.phrAddress, viewModel.abhaNumber,"" + ) + ) + } else if (parentViewModel.abhaMode.value == AadhaarIdViewModel.Abha.CREATE && + parentViewModel.mobileNumber == viewModel.mobileNumber) { + val timestamp = System.currentTimeMillis() + analyticsHelper.logCustomTimestampEvent("create_abha_reponse",timestamp) + findNavController().navigate( + AadhaarOtpFragmentDirections.actionAadhaarOtpFragmentToCreateAbhaFragment( + viewModel.txnId, viewModel.name, viewModel.phrAddress, viewModel.abhaNumber,viewModel.abhaResponse + ) + ) + } else { + viewModel.generateOtpClicked() + } + viewModel.resetState() + } + + State.SUCCESS -> { findNavController().navigate( - AadhaarOtpFragmentDirections.actionAadhaarOtpFragmentToGenerateMobileOtpFragment( - viewModel.txnId + AadhaarOtpFragmentDirections.actionAadhaarOtpFragmentToVerifyMobileOtpFragment( + viewModel.txnId, viewModel.mobileNumber, viewModel.mobileFromArgs,viewModel.name, viewModel.phrAddress, viewModel.abhaNumber,viewModel.abhaResponse ) ) viewModel.resetState() @@ -139,23 +209,39 @@ class AadhaarOtpFragment : Fragment() { activity, resources.getString(R.string.otp_was_resent), Toast.LENGTH_LONG - ) - .show() + ).show() } } } viewModel.errorMessage.observe(viewLifecycleOwner) { it?.let { + binding.otpView.setText("") binding.tvErrorText.text = it viewModel.resetErrorMessage() } } + viewModel.otpMobileNumberMessage.observe(viewLifecycleOwner) { + it?.let { + parentViewModel.setOTPMsg(it) + } + } + + } + + private fun getMobileNumber(input: String): String { + val regex = Regex("""\*+\d+""") + val matches = regex.findAll(input).toList() + val lastMatch = matches.lastOrNull()?.value + println("Extracted: $lastMatch") // Output: ******0180 + return lastMatch?:"" } private fun startResendTimer() { binding.resendOtp.isEnabled = false binding.timerResendOtp.visibility = View.VISIBLE + binding.timerCount.visibility = View.VISIBLE + binding.timerSeconds.visibility = View.VISIBLE timer.start() } diff --git a/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_otp/AadhaarOtpViewModel.kt b/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_otp/AadhaarOtpViewModel.kt index ada7d81c7..590658b26 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_otp/AadhaarOtpViewModel.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/aadhaar_otp/AadhaarOtpViewModel.kt @@ -5,12 +5,23 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.google.gson.Gson import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch -import org.piramalswasthya.sakhi.network.AbhaResendAadhaarOtpRequest +import org.piramalswasthya.sakhi.network.AbhaGenerateAadhaarOtpRequest import org.piramalswasthya.sakhi.network.AbhaVerifyAadhaarOtpRequest +import org.piramalswasthya.sakhi.network.AuthData +import org.piramalswasthya.sakhi.network.AuthData3 +import org.piramalswasthya.sakhi.network.Consent +import org.piramalswasthya.sakhi.network.LoginGenerateOtpRequest +import org.piramalswasthya.sakhi.network.LoginVerifyOtpRequest import org.piramalswasthya.sakhi.network.NetworkResult +import org.piramalswasthya.sakhi.network.Otp +import org.piramalswasthya.sakhi.network.Otp3 +import org.piramalswasthya.sakhi.network.interceptors.TokenInsertAbhaInterceptor import org.piramalswasthya.sakhi.repositories.AbhaIdRepo +import org.piramalswasthya.sakhi.ui.abha_id_activity.aadhaar_id.AadhaarIdViewModel +import timber.log.Timber import javax.inject.Inject @HiltViewModel @@ -24,15 +35,22 @@ class AadhaarOtpViewModel @Inject constructor( LOADING, ERROR_SERVER, ERROR_NETWORK, + SUCCESS, OTP_VERIFY_SUCCESS, OTP_GENERATED_SUCCESS } private var txnIdFromArgs = AadhaarOtpFragmentArgs.fromSavedStateHandle(savedStateHandle).txnId + var mobileFromArgs = + AadhaarOtpFragmentArgs.fromSavedStateHandle(savedStateHandle).mobileNumber private val _state = MutableLiveData(State.IDLE) val state: LiveData get() = _state + private val _state2 = MutableLiveData(AadhaarIdViewModel.State.IDLE) + val state2: LiveData + get() = _state2 + private val _errorMessage = MutableLiveData(null) val errorMessage: LiveData get() = _errorMessage @@ -43,32 +61,92 @@ class AadhaarOtpViewModel @Inject constructor( private var _txnId: String? = null val txnId: String - get() = _txnId!! + get() = _txnId?:"" + + private var _name: String? = null + val name: String + get() = _name!! + + private var _abhaNumber: String? = null + val abhaNumber: String + get() = _abhaNumber!! + + private var _abhaResponse: String = "" + val abhaResponse: String + get() = _abhaResponse + + private var _phrAddress: String = "" + val phrAddress: String + get() = _phrAddress - fun verifyOtpClicked(otp: String) { + private var _mobileNumber: String? = null + val mobileNumber: String + get() = _mobileNumber ?: "" + + private val _otpMobileNumberMessage = MutableLiveData(null) + val otpMobileNumberMessage: LiveData + get() = _otpMobileNumberMessage + + + fun verifyOtpClicked(otp: String, mobile: String) { _state.value = State.LOADING - verifyAadhaarOtp(otp) + verifyAadhaarOtp(otp, mobile) } fun resetState() { _state.value = State.IDLE } + fun setMobileNumber(number: String) { + _mobileNumber = number + } + fun resetErrorMessage() { _errorMessage.value = null } - private fun verifyAadhaarOtp(otp: String) { + private fun verifyAadhaarOtp(otp: String, mobile: String) { viewModelScope.launch { val result = abhaIdRepo.verifyOtpForAadhaar( AbhaVerifyAadhaarOtpRequest( - otp, - txnIdFromArgs + AuthData( + listOf("otp"), + Otp( + "", + txnIdFromArgs, + otp, + mobile + ) + ), + Consent( + "abha-enrollment", + "1.4" + ) ) ) when (result) { is NetworkResult.Success -> { + TokenInsertAbhaInterceptor.setXToken(result.data.tokens.token) _txnId = result.data.txnId + result.data.ABHAProfile.mobile?.let { mobile -> + if (mobile.isNotEmpty()) { + setMobileNumber(mobile) + } + } + + if (result.data.ABHAProfile.middleName.isNotEmpty()) { + _name = + result.data.ABHAProfile.firstName + " " + result.data.ABHAProfile.middleName + " " + result.data.ABHAProfile.lastName + } else { + _name = + result.data.ABHAProfile.firstName + " " + result.data.ABHAProfile.lastName + } + _phrAddress = result.data.ABHAProfile.phrAddress!![0] + _abhaNumber = result.data.ABHAProfile.ABHANumber + + val gsonData = Gson().toJson(result.data) + _abhaResponse = gsonData + _state.value = State.OTP_VERIFY_SUCCESS } @@ -88,29 +166,153 @@ class AadhaarOtpViewModel @Inject constructor( } } - fun resendOtp() { + fun verifyLoginOtpClicked(otp: String) { _state.value = State.LOADING + verifyLoginOtp(otp) + } + + private fun verifyLoginOtp(otp: String) { viewModelScope.launch { - when (val result = - abhaIdRepo.resendOtpForAadhaar(AbhaResendAadhaarOtpRequest(txnIdFromArgs))) { + val result = abhaIdRepo.verifyAbhaOtp( + LoginVerifyOtpRequest( + listOf("abha-login", "mobile-verify"), + AuthData3( + listOf("otp"), + Otp3( + txnIdFromArgs, + otp + ) + ) + ) + ) + when (result) { is NetworkResult.Success -> { - txnIdFromArgs = result.data.txnId - _state.value = State.OTP_GENERATED_SUCCESS + if (result.data?.token?.isNullOrEmpty() == false){ + TokenInsertAbhaInterceptor.setXToken(result.data.token) + _txnId = result.data.txnId + _name = result.data.accounts[0].name + _abhaNumber = result.data.accounts[0].ABHANumber + _state.value = State.OTP_VERIFY_SUCCESS + _phrAddress = result.data.accounts[0].preferredAbhaAddress + setMobileNumber("") + }else{ + _errorMessage.value = result.data.message + _state.value = State.ERROR_SERVER + } } is NetworkResult.Error -> { _errorMessage.value = result.message - if (result.message.contains("exit", true)) { + if (result.message.contains("exit your browser", true)) { _showExit.value = true } _state.value = State.ERROR_SERVER } is NetworkResult.NetworkError -> { + _showExit.value = true + _state.value = State.ERROR_NETWORK + } + } + } + } + + fun generateOtpClicked() { + _state.value = State.LOADING + generateAadhaarOtp() + } + + private fun generateAadhaarOtp() { + viewModelScope.launch { + when (val result = + abhaIdRepo.generateAadhaarOtpV3( + AbhaGenerateAadhaarOtpRequest( + txnId, + listOf("abha-enrol", "mobile-verify"), + "mobile", + mobileFromArgs, + "abdm" + ) + )) { + is NetworkResult.Success -> { + _txnId = result.data.txnId + _state.value = State.SUCCESS + _otpMobileNumberMessage.value = result.data.message + } + + is NetworkResult.Error -> { + _errorMessage.value = result.message + _state.value = State.ERROR_SERVER + } + + is NetworkResult.NetworkError -> { + Timber.i(result.toString()) + _state.value = State.ERROR_NETWORK + } + } + } + } + + + fun resendCreateAadhaarOtp(aadhaarNo:String) { + viewModelScope.launch { + when (val result = abhaIdRepo.generateAadhaarOtpV3( + AbhaGenerateAadhaarOtpRequest( + "", + listOf("abha-enrol"), + "aadhaar", + aadhaarNo, + "aadhaar" + ) + )) { + is NetworkResult.Success -> { + _txnId = result.data.txnId + txnIdFromArgs = result.data.txnId + _otpMobileNumberMessage.value = result.data.message + } + + is NetworkResult.Error -> { + _errorMessage.value = result.message + _state.value = State.ERROR_SERVER + } + + is NetworkResult.NetworkError -> { + Timber.i(result.toString()) _state.value = State.ERROR_NETWORK } } } } + fun resendOtpForSearchAbha(selectedAbhaIndex:String,txnId:String){ + viewModelScope.launch { + when (val result = + abhaIdRepo.generateAbhaOtp( + LoginGenerateOtpRequest( + listOf("abha-login", "search-abha", "mobile-verify"), + "index", + selectedAbhaIndex, + "abdm", + txnId + ) + )) { + is NetworkResult.Success -> { + _txnId = result.data.txnId + txnIdFromArgs = result.data.txnId + } + + is NetworkResult.Error -> { + _errorMessage.value = result.message + _state.value = State.ERROR_SERVER + } + + is NetworkResult.NetworkError -> { + Timber.i(result.toString()) + _state.value =State.ERROR_NETWORK + } + } + } + + } + } \ No newline at end of file diff --git a/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/create_abha_id/CreateAbhaFragment.kt b/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/create_abha_id/CreateAbhaFragment.kt index 922cb03cb..1c622ad86 100644 --- a/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/create_abha_id/CreateAbhaFragment.kt +++ b/app/src/main/java/org/piramalswasthya/sakhi/ui/abha_id_activity/create_abha_id/CreateAbhaFragment.kt @@ -1,11 +1,14 @@ package org.piramalswasthya.sakhi.ui.abha_id_activity.create_abha_id import android.app.AlertDialog +import android.app.Dialog import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent import android.content.Context import android.content.Intent +import android.graphics.Color +import android.graphics.drawable.ColorDrawable import android.media.MediaScannerConnection import android.net.Uri import android.os.Build @@ -14,30 +17,40 @@ import android.os.CountDownTimer import android.os.Environment import android.text.Editable import android.text.TextWatcher -import android.util.Base64 +import android.util.Log +import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.view.WindowManager +import android.widget.Button +import android.widget.TextView import android.widget.Toast import androidx.activity.OnBackPressedCallback import androidx.core.app.NotificationCompat import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.lifecycle.LiveData +import androidx.lifecycle.lifecycleScope import androidx.navigation.NavController import androidx.navigation.fragment.findNavController import androidx.work.Operation import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar +import com.google.firebase.analytics.FirebaseAnalytics import dagger.hilt.android.AndroidEntryPoint +import okhttp3.ResponseBody import org.piramalswasthya.sakhi.R import org.piramalswasthya.sakhi.databinding.FragmentCreateAbhaBinding +import org.piramalswasthya.sakhi.helpers.AnalyticsHelper import org.piramalswasthya.sakhi.ui.abha_id_activity.AbhaIdActivity -import org.piramalswasthya.sakhi.ui.abha_id_activity.create_abha_id.CreateAbhaViewModel.State +import org.piramalswasthya.sakhi.ui.abha_id_activity.aadhaar_id.AadhaarIdViewModel import org.piramalswasthya.sakhi.work.WorkerUtils import timber.log.Timber import java.io.File import java.io.FileOutputStream +import javax.inject.Inject + @AndroidEntryPoint class CreateAbhaFragment : Fragment() { @@ -50,8 +63,18 @@ class CreateAbhaFragment : Fragment() { get() = _binding!! private val viewModel: CreateAbhaViewModel by viewModels() + private val parentViewModel: AadhaarIdViewModel by viewModels({ requireActivity() }) + private val channelId = "download abha card" + private var benId: Long = 0 + + @Inject lateinit var analyticsHelper: AnalyticsHelper + + val args: CreateAbhaFragmentArgs by lazy { + CreateAbhaFragmentArgs.fromBundle(requireArguments()) + } + private var timer = object : CountDownTimer(30000, 1000) { override fun onTick(millisUntilFinished: Long) { @@ -78,7 +101,7 @@ class CreateAbhaFragment : Fragment() { private val exitAlert by lazy { MaterialAlertDialogBuilder(requireContext()) - .setTitle(resources.getString(R.string.exit)) + .setTitle(resources.getString(R.string.exit)).setCancelable(false) .setMessage(resources.getString(R.string.do_you_want_to_go_back)) .setPositiveButton(resources.getString(R.string.yes)) { _, _ -> activity?.finish() @@ -117,17 +140,50 @@ class CreateAbhaFragment : Fragment() { val intent = requireActivity().intent - val benId = intent.getLongExtra("benId", 0) + benId = intent.getLongExtra("benId", 0) val benRegId = intent.getLongExtra("benRegId", 0) - viewModel.createHID(benId, benRegId) + if (parentViewModel.abhaMode.value == AadhaarIdViewModel.Abha.SEARCH) { + binding.imageView.setImageResource(R.drawable.ic_exclamation_circle_green) + binding.textView7.text = getString(R.string.str_here_abha_no) + binding.clDownloadAbha.visibility = View.INVISIBLE + + }else{ + val timestamp = System.currentTimeMillis() + analyticsHelper.logCustomTimestampEvent("map_ben_to_health_id_request",timestamp) + viewModel.mapBeneficiaryToHealthId(benId, benRegId) + WorkerUtils.triggerAmritPushWorker(requireContext()) + } + + + + + binding.textView2.text = args.name + binding.textView4.text = args.abhaNumber + + viewModel.abhaResponseLiveData.observe(viewLifecycleOwner) { + if (it != null) { + if (!it.isNew) { + binding.imageView.setImageResource(R.drawable.ic_exclamation_circle) + binding.textView7.text = getString(R.string.str_abha_already_exist) + binding.clDownloadAbha.visibility = View.VISIBLE + binding.abhBenMappedTxt.text = resources.getString(R.string.not_linked_to_beneficiary) + } else { + binding.imageView.setImageResource(R.drawable.ic_check_circle) + binding.textView7.text = getString(R.string.str_abha_successfully_created) + binding.clDownloadAbha.visibility = View.VISIBLE + binding.llAbhaBenMapped.visibility = View.VISIBLE + + } + } + } viewModel.benMapped.observe(viewLifecycleOwner) { it?.let { binding.abhBenMappedTxt.text = String.format( "%s%s%s", resources.getString(R.string.linked_to_beneficiary), - " ", + "\n", it ) binding.llAbhaBenMapped.visibility = View.VISIBLE @@ -152,74 +208,13 @@ class CreateAbhaFragment : Fragment() { binding.btnDownloadAbhaNo.setOnClickListener { binding.txtDownloadAbha.visibility = View.INVISIBLE binding.clDownloadAbha.visibility = View.GONE + requireActivity().finish() } requireActivity().onBackPressedDispatcher.addCallback( viewLifecycleOwner, onBackPressedCallback ) - viewModel.state.observe(viewLifecycleOwner) { state -> - when (state) { - State.IDLE -> {} - State.LOADING -> { - binding.pbCai.visibility = View.VISIBLE - binding.clCreateAbhaId.visibility = View.INVISIBLE - binding.clError.visibility = View.INVISIBLE - } - - State.ERROR_NETWORK -> { - binding.pbCai.visibility = View.INVISIBLE - binding.clCreateAbhaId.visibility = View.INVISIBLE - binding.clError.visibility = View.VISIBLE - } - - State.ERROR_SERVER -> { - binding.pbCai.visibility = View.INVISIBLE - binding.clError.visibility = View.INVISIBLE - binding.tvErrorText.visibility = View.VISIBLE - } - - State.ABHA_GENERATE_SUCCESS -> { - binding.pbCai.visibility = View.INVISIBLE - binding.clCreateAbhaId.visibility = View.VISIBLE - binding.clVerifyMobileOtp.visibility = View.INVISIBLE - binding.clError.visibility = View.INVISIBLE - } - - State.OTP_GENERATE_SUCCESS -> { - binding.clVerifyMobileOtp.visibility = View.VISIBLE - binding.clDownloadAbha.visibility = View.GONE - binding.clError.visibility = View.INVISIBLE - startResendTimer() - } - - State.OTP_VERIFY_SUCCESS -> { - binding.pbCai.visibility = View.INVISIBLE - binding.clCreateAbhaId.visibility = View.VISIBLE - binding.clDownloadAbha.visibility = View.GONE - binding.clVerifyMobileOtp.visibility = View.INVISIBLE - binding.clError.visibility = View.INVISIBLE - } - - State.DOWNLOAD_SUCCESS -> { - binding.pbCai.visibility = View.INVISIBLE - binding.clCreateAbhaId.visibility = View.VISIBLE - binding.clError.visibility = View.INVISIBLE - } - - State.DOWNLOAD_ERROR -> { - binding.pbCai.visibility = View.INVISIBLE - binding.clCreateAbhaId.visibility = View.VISIBLE - binding.clDownloadAbha.visibility = View.GONE - binding.clVerifyMobileOtp.visibility = View.VISIBLE - binding.tvErrorTextVerify.visibility = View.VISIBLE - startResendTimer() - } - - State.ERROR_INTERNAL -> {} - } - } - viewModel.errorMessage.observe(viewLifecycleOwner) { it?.let { binding.tvErrorTextVerify.text = it @@ -228,20 +223,47 @@ class CreateAbhaFragment : Fragment() { } } - viewModel.cardBase64.observe(viewLifecycleOwner) { + viewModel.byteImage.observe(viewLifecycleOwner) { it?.let { showFileNotification(it) } } binding.btnDownloadAbhaYes.setOnClickListener { - viewModel.generateOtp() - binding.clDownloadAbha.visibility = View.GONE + viewModel.printAbhaCard() } binding.resendOtp.setOnClickListener { viewModel.generateOtp() startResendTimer() } + + lifecycleScope.launchWhenStarted { + viewModel.uiEvent.collect { event -> + when (event) { + is UIEvent.ShowDialog -> { + showDialog(event.message,event.s) + } + + } + } + } + + viewModel.state.observe(viewLifecycleOwner) { + if (it.name == "ABHA_GENERATE_SUCCESS"){ + val timestamp = System.currentTimeMillis() + analyticsHelper.logCustomTimestampEvent("map_ben_to_health_id_reponse",timestamp) + } + if (it.name == "DOWNLOAD_SUCCESS") { + binding.clDownloadAbha.visibility = View.INVISIBLE + Snackbar.make( + requireView(), + getString(R.string.str_abha_card_downloaded), Snackbar.LENGTH_INDEFINITE + ).setAction(getString(R.string.ok)) { + requireActivity().finish() + }.show() + } + + } } override fun onDestroy() { @@ -251,9 +273,9 @@ class CreateAbhaFragment : Fragment() { _binding = null } - private fun showFileNotification(fileStr: String) { + private fun showFileNotification(fileStr: ResponseBody) { val fileName = - "${viewModel.hidResponse.value?.name}_${System.currentTimeMillis()}.png" + "${benId}_${System.currentTimeMillis()}.png" val notificationManager = requireContext().getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @@ -266,8 +288,7 @@ class CreateAbhaFragment : Fragment() { val directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) val file = File(directory, fileName) - val data: ByteArray = Base64.decode(fileStr, 0) - FileOutputStream(file).use { stream -> stream.write(data) } + FileOutputStream(file).use { stream -> stream.write(fileStr.bytes()) } MediaScannerConnection.scanFile( requireContext(), arrayOf(file.toString()), @@ -354,6 +375,13 @@ class CreateAbhaFragment : Fragment() { "Downloading $fileName ", Snackbar.LENGTH_SHORT ).show() } + is Operation.State.SUCCESS -> { + binding.clDownloadAbha.visibility = View.INVISIBLE + Snackbar.make( + binding.root, + "Downloading $fileName ", Snackbar.LENGTH_SHORT + ).show() + } is Operation.State.FAILURE -> { Toast.makeText(context, "Failed to download , Please retry", Toast.LENGTH_SHORT) @@ -361,5 +389,28 @@ class CreateAbhaFragment : Fragment() { } } } + + + } + + private fun showDialog(message: String,surname:String) { + val dialog = Dialog(requireContext(), android.R.style.Theme_DeviceDefault_Light_NoActionBar_Fullscreen) + dialog.setContentView(R.layout.dialog_abha_error) + dialog.setCancelable(false) + val window = dialog.window + window?.setLayout( + (resources.displayMetrics.widthPixels * 0.85).toInt(), + (resources.displayMetrics.heightPixels * 0.55).toInt() + ) + window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window?.setGravity(Gravity.CENTER) + val tvMessage = dialog.findViewById(R.id.tvMessage) + val btnOk = dialog.findViewById